diff options
462 files changed, 12497 insertions, 3168 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 8bd3ef4f4d1a..637c726a9bd1 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -36,10 +36,14 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.UidObserver; +import android.app.compat.CompatChanges; import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; +import android.compat.annotation.ChangeId; +import android.compat.annotation.Disabled; +import android.compat.annotation.Overridable; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -132,6 +136,27 @@ public final class QuotaController extends StateController { return (int) (val ^ (val >>> 32)); } + /** + * When enabled this change id overrides the default quota policy enforcement to the jobs + * running in the foreground process state. + */ + // TODO: b/379681266 - Might need some refactoring for a better app-compat strategy. + @VisibleForTesting + @ChangeId + @Disabled // Disabled by default + @Overridable // The change can be overridden in user build + static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS = 341201311L; + + /** + * When enabled this change id overrides the default quota policy enforcement policy + * the jobs started when app was in the TOP state. + */ + @VisibleForTesting + @ChangeId + @Disabled // Disabled by default + @Overridable // The change can be overridden in user build. + static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS = 374323858L; + @VisibleForTesting static class ExecutionStats { /** @@ -622,7 +647,9 @@ public final class QuotaController extends StateController { } final int uid = jobStatus.getSourceUid(); - if (!Flags.enforceQuotaPolicyToTopStartedJobs() && mTopAppCache.get(uid)) { + if ((!Flags.enforceQuotaPolicyToTopStartedJobs() + || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, + uid)) && mTopAppCache.get(uid)) { if (DEBUG) { Slog.d(TAG, jobStatus.toShortString() + " is top started job"); } @@ -659,7 +686,9 @@ public final class QuotaController extends StateController { timer.stopTrackingJob(jobStatus); } } - if (!Flags.enforceQuotaPolicyToTopStartedJobs()) { + if (!Flags.enforceQuotaPolicyToTopStartedJobs() + || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, + jobStatus.getSourceUid())) { mTopStartedJobs.remove(jobStatus); } } @@ -772,7 +801,13 @@ public final class QuotaController extends StateController { /** @return true if the job was started while the app was in the TOP state. */ private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) { - return !Flags.enforceQuotaPolicyToTopStartedJobs() && mTopStartedJobs.contains(jobStatus); + if (!Flags.enforceQuotaPolicyToTopStartedJobs() + || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, + jobStatus.getSourceUid())) { + return mTopStartedJobs.contains(jobStatus); + } + + return false; } /** Returns the maximum amount of time this job could run for. */ @@ -2634,9 +2669,13 @@ public final class QuotaController extends StateController { } @VisibleForTesting - int getProcessStateQuotaFreeThreshold() { - return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP : - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + int getProcessStateQuotaFreeThreshold(int uid) { + if (Flags.enforceQuotaPolicyToFgsJobs() + && !CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) { + return ActivityManager.PROCESS_STATE_BOUND_TOP; + } + + return ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; } private class QcHandler extends Handler { @@ -2776,7 +2815,7 @@ public final class QuotaController extends StateController { isQuotaFree = true; } else { final boolean reprocess; - if (procState <= getProcessStateQuotaFreeThreshold()) { + if (procState <= getProcessStateQuotaFreeThreshold(uid)) { reprocess = !mForegroundUids.get(uid); mForegroundUids.put(uid, true); isQuotaFree = true; diff --git a/core/api/current.txt b/core/api/current.txt index a03f8c5c167f..5a7f2bdb2949 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4614,6 +4614,7 @@ package android.app { method public void reportFullyDrawn(); method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent); method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>); + method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public final void requestOpenInBrowserEducation(); method public final void requestPermissions(@NonNull String[], int); method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public final void requestPermissions(@NonNull String[], int, int); method public final void requestShowKeyboardShortcuts(); @@ -4639,7 +4640,6 @@ package android.app { method public void setInheritShowWhenLocked(boolean); method public void setIntent(android.content.Intent); method @FlaggedApi("android.security.content_uri_permission_apis") public void setIntent(@Nullable android.content.Intent, @Nullable android.app.ComponentCaller); - method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public final void setLimitSystemEducationDialogs(boolean); method public void setLocusContext(@Nullable android.content.LocusId, @Nullable android.os.Bundle); method public final void setMediaController(android.media.session.MediaController); method public void setPictureInPictureParams(@NonNull android.app.PictureInPictureParams); @@ -5110,8 +5110,11 @@ package android.app { } public class AppOpsManager { - method @Deprecated public int checkOp(@NonNull String, int, @NonNull String); - method @Deprecated public int checkOpNoThrow(@NonNull String, int, @NonNull String); + method @FlaggedApi("android.permission.flags.check_op_overload_api_enabled") public int checkOp(@NonNull String, int, @NonNull String); + method @FlaggedApi("android.permission.flags.check_op_overload_api_enabled") public int checkOp(@NonNull String, int, @NonNull String, @Nullable String); + method @FlaggedApi("android.permission.flags.check_op_overload_api_enabled") public int checkOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String); + method @FlaggedApi("android.permission.flags.check_op_overload_api_enabled") public int checkOpNoThrow(@NonNull String, int, @NonNull String); + method @FlaggedApi("android.permission.flags.check_op_overload_api_enabled") public int checkOpRawNoThrow(@NonNull String, int, @NonNull String, @Nullable String); method @Deprecated public void checkPackage(int, @NonNull String); method @Deprecated public void finishOp(@NonNull String, int, @NonNull String); method public void finishOp(@NonNull String, int, @NonNull String, @Nullable String); @@ -5140,10 +5143,10 @@ package android.app { method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener); method public void stopWatchingActive(@NonNull android.app.AppOpsManager.OnOpActiveChangedListener); method public void stopWatchingMode(@NonNull android.app.AppOpsManager.OnOpChangedListener); - method public int unsafeCheckOp(@NonNull String, int, @NonNull String); - method public int unsafeCheckOpNoThrow(@NonNull String, int, @NonNull String); - method public int unsafeCheckOpRaw(@NonNull String, int, @NonNull String); - method public int unsafeCheckOpRawNoThrow(@NonNull String, int, @NonNull String); + method @Deprecated @FlaggedApi("android.permission.flags.check_op_overload_api_enabled") public int unsafeCheckOp(@NonNull String, int, @NonNull String); + method @Deprecated @FlaggedApi("android.permission.flags.check_op_overload_api_enabled") public int unsafeCheckOpNoThrow(@NonNull String, int, @NonNull String); + method @Deprecated @FlaggedApi("android.permission.flags.check_op_overload_api_enabled") public int unsafeCheckOpRaw(@NonNull String, int, @NonNull String); + method @Deprecated @FlaggedApi("android.permission.flags.check_op_overload_api_enabled") public int unsafeCheckOpRawNoThrow(@NonNull String, int, @NonNull String); field public static final int MODE_ALLOWED = 0; // 0x0 field public static final int MODE_DEFAULT = 3; // 0x3 field public static final int MODE_ERRORED = 2; // 0x2 @@ -6235,6 +6238,7 @@ package android.app { } public class KeyguardManager { + method @FlaggedApi("android.app.device_unlock_listener") @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void addDeviceLockedStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.DeviceLockedStateListener); method @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void addKeyguardLockedStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.KeyguardLockedStateListener); method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence); method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); @@ -6244,10 +6248,15 @@ package android.app { method public boolean isKeyguardLocked(); method public boolean isKeyguardSecure(); method @Deprecated public android.app.KeyguardManager.KeyguardLock newKeyguardLock(String); + method @FlaggedApi("android.app.device_unlock_listener") @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void removeDeviceLockedStateListener(@NonNull android.app.KeyguardManager.DeviceLockedStateListener); method @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void removeKeyguardLockedStateListener(@NonNull android.app.KeyguardManager.KeyguardLockedStateListener); method public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable android.app.KeyguardManager.KeyguardDismissCallback); } + @FlaggedApi("android.app.device_unlock_listener") @java.lang.FunctionalInterface public static interface KeyguardManager.DeviceLockedStateListener { + method public void onDeviceLockedStateChanged(boolean); + } + public abstract static class KeyguardManager.KeyguardDismissCallback { ctor public KeyguardManager.KeyguardDismissCallback(); method public void onDismissCancelled(); @@ -13390,6 +13399,7 @@ package android.content.pm { field public static final String FEATURE_BACKUP = "android.software.backup"; field public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth"; field public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le"; + field @FlaggedApi("com.android.ranging.flags.ranging_cs_enabled") public static final String FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING = "android.hardware.bluetooth_le.channel_sounding"; field public static final String FEATURE_CAMERA = "android.hardware.camera"; field public static final String FEATURE_CAMERA_ANY = "android.hardware.camera.any"; field public static final String FEATURE_CAMERA_AR = "android.hardware.camera.ar"; @@ -23180,7 +23190,6 @@ package android.media { method public boolean isVendor(); field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_MEMORY_SAFE = 1; // 0x1 field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_SANDBOXED = 0; // 0x0 - field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 2; // 0x2 } public static final class MediaCodecInfo.AudioCapabilities { @@ -24071,7 +24080,6 @@ package android.media { field public static final int COLOR_TRANSFER_ST2084 = 6; // 0x6 field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_MEMORY_SAFE = 2; // 0x2 field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_SANDBOXED = 1; // 0x1 - field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 4; // 0x4 field public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode"; field public static final String KEY_AAC_DRC_ATTENUATION_FACTOR = "aac-drc-cut-level"; field public static final String KEY_AAC_DRC_BOOST_FACTOR = "aac-drc-boost-level"; @@ -27049,6 +27057,86 @@ package android.media.projection { } +package android.media.quality { + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public class MediaQualityContract { + } + + public static final class MediaQualityContract.PictureQuality { + field public static final String PARAMETER_BRIGHTNESS = "brightness"; + field public static final String PARAMETER_CONTRAST = "contrast"; + field public static final String PARAMETER_SATURATION = "saturation"; + field public static final String PARAMETER_SHARPNESS = "sharpness"; + } + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager { + method public void createPictureProfile(@NonNull android.media.quality.PictureProfile); + method @NonNull public java.util.List<android.media.quality.PictureProfile> getAvailablePictureProfiles(); + method @NonNull public java.util.List<android.media.quality.ParamCapability> getParamCapabilities(@NonNull java.util.List<java.lang.String>); + method @Nullable public android.media.quality.PictureProfile getPictureProfile(int, @NonNull String); + method public boolean isAutoPictureQualityEnabled(); + method public boolean isSuperResolutionEnabled(); + method public void registerPictureProfileCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.PictureProfileCallback); + method public void removePictureProfile(@NonNull String); + method public void unregisterPictureProfileCallback(@NonNull android.media.quality.MediaQualityManager.PictureProfileCallback); + method public void updatePictureProfile(@NonNull String, @NonNull android.media.quality.PictureProfile); + } + + public abstract static class MediaQualityManager.PictureProfileCallback { + ctor public MediaQualityManager.PictureProfileCallback(); + method public void onError(int); + method public void onParamCapabilitiesChanged(@Nullable String, @NonNull java.util.List<android.media.quality.ParamCapability>); + method public void onPictureProfileAdded(@NonNull String, @NonNull android.media.quality.PictureProfile); + method public void onPictureProfileRemoved(@NonNull String, @NonNull android.media.quality.PictureProfile); + method public void onPictureProfileUpdated(@NonNull String, @NonNull android.media.quality.PictureProfile); + } + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class ParamCapability implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.os.Bundle getCapabilities(); + method @NonNull public String getParamName(); + method public int getParamType(); + method public boolean isSupported(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final String CAPABILITY_DEFAULT = "default"; + field public static final String CAPABILITY_ENUM = "enum"; + field public static final String CAPABILITY_MAX = "max"; + field public static final String CAPABILITY_MIN = "min"; + field @NonNull public static final android.os.Parcelable.Creator<android.media.quality.ParamCapability> CREATOR; + field public static final int TYPE_DOUBLE = 3; // 0x3 + field public static final int TYPE_INT = 1; // 0x1 + field public static final int TYPE_LONG = 2; // 0x2 + field public static final int TYPE_STRING = 4; // 0x4 + } + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class PictureProfile implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getInputId(); + method @NonNull public String getName(); + method @Nullable public String getPackageName(); + method @NonNull public android.os.PersistableBundle getParameters(); + method @Nullable public String getProfileId(); + method public int getProfileType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.quality.PictureProfile> CREATOR; + field public static final int ERROR_DUPLICATE = 2; // 0x2 + field public static final int ERROR_INVALID_ARGUMENT = 3; // 0x3 + field public static final int ERROR_NOT_ALLOWLISTED = 4; // 0x4 + field public static final int ERROR_NO_PERMISSION = 1; // 0x1 + field public static final int ERROR_UNKNOWN = 0; // 0x0 + field public static final int TYPE_APPLICATION = 2; // 0x2 + field public static final int TYPE_SYSTEM = 1; // 0x1 + } + + public static final class PictureProfile.Builder { + ctor public PictureProfile.Builder(@NonNull String); + ctor public PictureProfile.Builder(@NonNull android.media.quality.PictureProfile); + method @NonNull public android.media.quality.PictureProfile build(); + method @NonNull public android.media.quality.PictureProfile.Builder setParameters(@NonNull android.os.PersistableBundle); + } + +} + package android.media.session { public final class MediaController { @@ -33720,7 +33808,7 @@ package android.os { } public interface IBinder { - method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default void addFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback) throws android.os.RemoteException; + method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default void addFrozenStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.os.IBinder.FrozenStateChangeCallback) throws android.os.RemoteException; method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException; method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException; method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException; @@ -34351,6 +34439,7 @@ package android.os { method public void finishBroadcast(); method public Object getBroadcastCookie(int); method public E getBroadcastItem(int); + method @FlaggedApi("android.os.binder_frozen_state_change_callback") @Nullable public java.util.concurrent.Executor getExecutor(); method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getFrozenCalleePolicy(); method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getMaxQueueSize(); method public Object getRegisteredCallbackCookie(int); @@ -34371,6 +34460,7 @@ package android.os { @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final class RemoteCallbackList.Builder<E extends android.os.IInterface> { ctor public RemoteCallbackList.Builder(int); method @NonNull public android.os.RemoteCallbackList<E> build(); + method @NonNull public android.os.RemoteCallbackList.Builder setExecutor(@NonNull java.util.concurrent.Executor); method @NonNull public android.os.RemoteCallbackList.Builder setInterfaceDiedCallback(@NonNull android.os.RemoteCallbackList.Builder.InterfaceDiedCallback<E>); method @NonNull public android.os.RemoteCallbackList.Builder setMaxQueueSize(int); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 25434e88aadd..14a6c8c4928d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -103,6 +103,7 @@ package android { field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"; field @Deprecated public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED"; + field @FlaggedApi("android.media.audio.concurrent_audio_record_bypass_permission") public static final String BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION = "android.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION"; field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION"; field public static final String CALL_AUDIO_INTERCEPTION = "android.permission.CALL_AUDIO_INTERCEPTION"; field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED"; @@ -229,6 +230,7 @@ package android { field public static final String MANAGE_ROTATION_RESOLVER = "android.permission.MANAGE_ROTATION_RESOLVER"; field public static final String MANAGE_SAFETY_CENTER = "android.permission.MANAGE_SAFETY_CENTER"; field public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI"; + field @FlaggedApi("android.security.secure_lockdown") public static final String MANAGE_SECURE_LOCK_DEVICE = "android.permission.MANAGE_SECURE_LOCK_DEVICE"; field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY"; field public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE"; field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER"; @@ -3869,6 +3871,7 @@ package android.content { field public static final String APP_INTEGRITY_SERVICE = "app_integrity"; field public static final String APP_PREDICTION_SERVICE = "app_prediction"; field public static final String AUDIO_DEVICE_VOLUME_SERVICE = "audio_device_volume"; + field @FlaggedApi("android.security.secure_lockdown") public static final String AUTHENTICATION_POLICY_SERVICE = "authentication_policy"; field public static final String BACKUP_SERVICE = "backup"; field public static final String BATTERY_STATS_SERVICE = "batterystats"; field public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000 @@ -5242,12 +5245,15 @@ package android.hardware.contexthub { @FlaggedApi("android.chre.flags.offload_api") public class HubDiscoveryInfo { method @NonNull public android.hardware.contexthub.HubEndpointInfo getHubEndpointInfo(); + method @Nullable public android.hardware.contexthub.HubServiceInfo getHubServiceInfo(); } @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint { method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback(); method @Nullable public android.hardware.contexthub.IHubEndpointMessageCallback getMessageCallback(); + method @NonNull public java.util.Collection<android.hardware.contexthub.HubServiceInfo> getServiceInfoCollection(); method @Nullable public String getTag(); + method public int getVersion(); } public static final class HubEndpoint.Builder { @@ -5257,6 +5263,7 @@ package android.hardware.contexthub { method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback); method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.IHubEndpointMessageCallback); method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointMessageCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setServiceInfoCollection(@NonNull java.util.Collection<android.hardware.contexthub.HubServiceInfo>); method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String); } @@ -5264,9 +5271,18 @@ package android.hardware.contexthub { method public int describeContents(); method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier(); method @NonNull public String getName(); + method @NonNull public java.util.Collection<java.lang.String> getRequiredPermissions(); + method @NonNull public java.util.Collection<android.hardware.contexthub.HubServiceInfo> getServiceInfoCollection(); method @Nullable public String getTag(); + method public int getType(); + method public int getVersion(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.contexthub.HubEndpointInfo> CREATOR; + field public static final int TYPE_APP = 2; // 0x2 + field public static final int TYPE_FRAMEWORK = 1; // 0x1 + field public static final int TYPE_HUB_ENDPOINT = 5; // 0x5 + field public static final int TYPE_NANOAPP = 4; // 0x4 + field public static final int TYPE_NATIVE = 3; // 0x3 } public static class HubEndpointInfo.HubEndpointIdentifier { @@ -5276,6 +5292,7 @@ package android.hardware.contexthub { @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable { method public void close(); + method @Nullable public android.hardware.contexthub.HubServiceInfo getServiceInfo(); method @NonNull public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage); } @@ -5302,9 +5319,30 @@ package android.hardware.contexthub { method @NonNull public android.hardware.contexthub.HubMessage.DeliveryParams setResponseRequired(boolean); } + @FlaggedApi("android.chre.flags.offload_api") public final class HubServiceInfo implements android.os.Parcelable { + ctor public HubServiceInfo(@NonNull String, int, int, int, @NonNull android.os.ParcelableHolder); + method public int describeContents(); + method @NonNull public android.os.ParcelableHolder getExtendedInfo(); + method public int getFormat(); + method public int getMajorVersion(); + method public int getMinorVersion(); + method @NonNull public String getServiceDescriptor(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.contexthub.HubServiceInfo> CREATOR; + field public static final int FORMAT_AIDL = 1; // 0x1 + field public static final int FORMAT_CUSTOM = 0; // 0x0 + field public static final int FORMAT_PW_RPC_PROTOBUF = 2; // 0x2 + } + + public static final class HubServiceInfo.Builder { + ctor public HubServiceInfo.Builder(@NonNull String, int, int, int); + method @NonNull public android.hardware.contexthub.HubServiceInfo build(); + method @NonNull public android.hardware.contexthub.HubServiceInfo.Builder setExtendedInfo(@Nullable android.os.Parcelable); + } + @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback { method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int); - method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo); + method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable android.hardware.contexthub.HubServiceInfo); method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession); field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4 field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3 @@ -6310,6 +6348,7 @@ package android.hardware.location { method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> disableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> enableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long); method @FlaggedApi("android.chre.flags.offload_api") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public java.util.List<android.hardware.contexthub.HubDiscoveryInfo> findEndpoints(long); + method @FlaggedApi("android.chre.flags.offload_api") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public java.util.List<android.hardware.contexthub.HubDiscoveryInfo> findEndpoints(@NonNull String); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int[] findNanoAppOnHub(int, @NonNull android.hardware.location.NanoAppFilter); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int[] getContextHubHandles(); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubInfo getContextHubInfo(int); @@ -6318,6 +6357,7 @@ package android.hardware.location { method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary); method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull android.hardware.contexthub.HubServiceInfo); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo); method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback); method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler); @@ -8111,6 +8151,25 @@ package android.media.musicrecognition { } +package android.media.quality { + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager { + method @NonNull public java.util.List<java.lang.String> getPictureProfileAllowList(); + method @NonNull public java.util.List<java.lang.String> getPictureProfilePackageNames(); + method @NonNull public java.util.List<android.media.quality.PictureProfile> getPictureProfilesByPackage(@NonNull String); + method public void setAutoPictureQualityEnabled(boolean); + method public void setPictureProfileAllowList(@NonNull java.util.List<java.lang.String>); + method public void setSuperResolutionEnabled(boolean); + } + + public static final class PictureProfile.Builder { + method @NonNull public android.media.quality.PictureProfile.Builder setInputId(@NonNull String); + method @NonNull public android.media.quality.PictureProfile.Builder setPackageName(@NonNull String); + method @NonNull public android.media.quality.PictureProfile.Builder setProfileType(int); + } + +} + package android.media.session { public final class MediaSessionManager { @@ -8403,18 +8462,96 @@ package android.media.tv { @FlaggedApi("android.media.tv.flags.tif_extension_standardization") public final class TvInputServiceExtensionManager { method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public int registerExtensionIBinder(@NonNull String, @NonNull android.os.IBinder); - field public static final String IBROADCAST_TIME = "android.media.tv.extension.time.BroadcastTime"; + field public static final String IANALOG_ATTRIBUTE_INTERFACE = "android.media.tv.extension.analog.IAnalogAttributeInterface"; + field public static final String IANALOG_AUDIO_INFO = "android.media.tv.extension.signal.IAnalogAudioInfo"; + field public static final String IAUDIO_SIGNAL_INFO = "android.media.tv.extension.signal.IAudioSignalInfo"; + field public static final String IAUDIO_SIGNAL_INFO_LISTENER = "android.media.tv.extension.signal.IAudioSignalInfoListener"; + field public static final String IBROADCAST_TIME = "android.media.tv.extension.time.IBroadcastTime"; + field public static final String ICAM_APP_INFO_LISTENER = "android.media.tv.extension.cam.ICamAppInfoListener"; field public static final String ICAM_APP_INFO_SERVICE = "android.media.tv.extension.cam.ICamAppInfoService"; + field public static final String ICAM_DRM_INFO_LISTENER = "android.media.tv.extension.cam.ICamDrmInfoListener"; + field public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = "android.media.tv.extension.cam.ICamHostControlAskReleaseReplyCallback"; + field public static final String ICAM_HOST_CONTROL_INFO_LISTENER = "android.media.tv.extension.cam.ICamHostControlInfoListener"; + field public static final String ICAM_HOST_CONTROL_SERVICE = "android.media.tv.extension.cam.ICamHostControlService"; + field public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG = "android.media.tv.extension.cam.ICamHostControlTuneQuietlyFlag"; + field public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = "android.media.tv.extension.cam.ICamHostControlTuneQuietlyFlagListener"; + field public static final String ICAM_INFO_LISTENER = "android.media.tv.extension.cam.ICamInfoListener"; + field public static final String ICAM_MONITORING_SERVICE = "android.media.tv.extension.cam.ICamMonitoringService"; + field public static final String ICAM_PIN_CAPABILITY_LISTENER = "android.media.tv.extension.cam.ICamPinCapabilityListener"; + field public static final String ICAM_PIN_SERVICE = "android.media.tv.extension.cam.ICamPinService"; + field public static final String ICAM_PIN_STATUS_LISTENER = "android.media.tv.extension.cam.ICamPinStatusListener"; + field public static final String ICAM_PROFILE_INTERFACE = "android.media.tv.extension.cam.ICamProfileInterface"; + field public static final String ICHANNEL_LIST_TRANSFER = "android.media.tv.extension.servicedb.IChannelListTransfer"; + field public static final String ICHANNEL_TUNED_INTERFACE = "android.media.tv.extension.tune.IChannelTunedInterface"; + field public static final String ICHANNEL_TUNED_LISTENER = "android.media.tv.extension.tune.IChannelTunedListener"; + field public static final String ICI_OPERATOR_INTERFACE = "android.media.tv.extension.cam.ICiOperatorInterface"; + field public static final String ICI_OPERATOR_LISTENER = "android.media.tv.extension.cam.ICiOperatorListener"; field public static final String ICLIENT_TOKEN = "android.media.tv.extension.clienttoken.IClientToken"; + field public static final String ICONTENT_CONTROL_SERVICE = "android.media.tv.extension.cam.IContentControlService"; field public static final String IDATA_SERVICE_SIGNAL_INFO = "android.media.tv.extension.teletext.IDataServiceSignalInfo"; + field public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = "android.media.tv.extension.teletext.IDataServiceSignalInfoListener"; + field public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = "android.media.tv.extension.pvr.IDeleteRecordedContentsCallback"; + field public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = "android.media.tv.extension.rating.IDownloadableRatingTableMonitor"; + field public static final String IENTER_MENU_ERROR_CALLBACK = "android.media.tv.extension.cam.IEnterMenuErrorCallback"; + field public static final String IEVENT_DOWNLOAD = "android.media.tv.extension.event.IEventDownload"; + field public static final String IEVENT_DOWNLOAD_LISTENER = "android.media.tv.extension.event.IEventDownloadListener"; + field public static final String IEVENT_DOWNLOAD_SESSION = "android.media.tv.extension.event.IEventDownloadSession"; field public static final String IEVENT_MONITOR = "android.media.tv.extension.event.IEventMonitor"; + field public static final String IEVENT_MONITOR_LISTENER = "android.media.tv.extension.event.IEventMonitorListener"; + field public static final String IFAVORITE_NETWORK = "android.media.tv.extension.scan.IFavoriteNetwork"; + field public static final String IFAVORITE_NETWORK_LISTENER = "android.media.tv.extension.scan.IFavoriteNetworkListener"; + field public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = "android.media.tv.extension.pvr.IGetInfoRecordedContentsCallback"; + field public static final String IHDMI_SIGNAL_INFO_LISTENER = "android.media.tv.extension.signal.IHdmiSignalInfoListener"; field public static final String IHDMI_SIGNAL_INTERFACE = "android.media.tv.extension.signal.IHdmiSignalInterface"; + field public static final String IHDPLUS_INFO = "android.media.tv.extension.scan.IHDPlusInfo"; + field public static final String ILCNV2_CHANNEL_LIST = "android.media.tv.extension.scan.ILcnV2ChannelList"; + field public static final String ILCNV2_CHANNEL_LIST_LISTENER = "android.media.tv.extension.scan.ILcnV2ChannelListListener"; + field public static final String ILCN_CONFLICT = "android.media.tv.extension.scan.ILcnConflict"; + field public static final String ILCN_CONFLICT_LISTENER = "android.media.tv.extension.scan.ILcnConflictListener"; + field public static final String IMMI_INTERFACE = "android.media.tv.extension.cam.IMmiInterface"; + field public static final String IMMI_SESSION = "android.media.tv.extension.cam.IMmiSession"; + field public static final String IMMI_STATUS_CALLBACK = "android.media.tv.extension.cam.IMmiStatusCallback"; + field public static final String IMUX_TUNE = "android.media.tv.extension.tune.IMuxTune"; + field public static final String IMUX_TUNE_SESSION = "android.media.tv.extension.tune.IMuxTuneSession"; field public static final String IOAD_UPDATE_INTERFACE = "android.media.tv.extension.oad.IOadUpdateInterface"; + field public static final String IOPERATOR_DETECTION = "android.media.tv.extension.scan.IOperatorDetection"; + field public static final String IOPERATOR_DETECTION_LISTENER = "android.media.tv.extension.scan.IOperatorDetectionListener"; + field public static final String IPMT_RATING_INTERFACE = "android.media.tv.extension.rating.IPmtRatingInterface"; + field public static final String IPMT_RATING_LISTENER = "android.media.tv.extension.rating.IPmtRatingListener"; + field public static final String IPROGRAM_INFO = "android.media.tv.extension.rating.IProgramInfo"; + field public static final String IPROGRAM_INFO_LISTENER = "android.media.tv.extension.rating.IProgramInfoListener"; field public static final String IRATING_INTERFACE = "android.media.tv.extension.rating.IRatingInterface"; field public static final String IRECORDED_CONTENTS = "android.media.tv.extension.pvr.IRecordedContents"; + field public static final String IREGION_CHANNEL_LIST = "android.media.tv.extension.scan.IRegionChannelList"; + field public static final String IREGION_CHANNEL_LIST_LISTENER = "android.media.tv.extension.scan.IRegionChannelListListener"; + field public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = "android.media.tv.extension.scanbsu.IScanBackgroundServiceUpdate"; + field public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = "android.media.tv.extension.scanbsu.IScanBackgroundServiceUpdateListener"; field public static final String ISCAN_INTERFACE = "android.media.tv.extension.scan.IScanInterface"; + field public static final String ISCAN_LISTENER = "android.media.tv.extension.scan.IScanListener"; + field public static final String ISCAN_SAT_SEARCH = "android.media.tv.extension.scan.IScanSatSearch"; + field public static final String ISCAN_SESSION = "android.media.tv.extension.scan.IScanSession"; field public static final String ISCREEN_MODE_SETTINGS = "android.media.tv.extension.screenmode.IScreenModeSettings"; + field public static final String ISERVICE_LIST = "android.media.tv.extension.servicedb.IServiceList"; + field public static final String ISERVICE_LIST_EDIT = "android.media.tv.extension.servicedb.IServiceListEdit"; field public static final String ISERVICE_LIST_EDIT_LISTENER = "android.media.tv.extension.servicedb.IServiceListEditListener"; + field public static final String ISERVICE_LIST_EXPORT_LISTENER = "android.media.tv.extension.servicedb.IServiceListExportListener"; + field public static final String ISERVICE_LIST_EXPORT_SESSION = "android.media.tv.extension.servicedb.IServiceListExportSession"; + field public static final String ISERVICE_LIST_IMPORT_LISTENER = "android.media.tv.extension.servicedb.IServiceListImportListener"; + field public static final String ISERVICE_LIST_IMPORT_SESSION = "android.media.tv.extension.servicedb.IServiceListImportSession"; + field public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = "android.media.tv.extension.servicedb.IServiceListSetChannelListListener"; + field public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = "android.media.tv.extension.servicedb.IServiceListSetChannelListSession"; + field public static final String ISERVICE_LIST_TRANSFER_INTERFACE = "android.media.tv.extension.servicedb.IServiceListTransferInterface"; + field public static final String ITARGET_REGION = "android.media.tv.extension.scan.ITargetRegion"; + field public static final String ITARGET_REGION_LISTENER = "android.media.tv.extension.scan.ITargetRegionListener"; + field public static final String ITELETEXT_PAGE_SUB_CODE = "android.media.tv.extension.teletext.ITeletextPageSubCode"; + field public static final String ITKGS_INFO = "android.media.tv.extension.scan.ITkgsInfo"; + field public static final String ITKGS_INFO_LISTENER = "android.media.tv.extension.scan.ITkgsInfoListener"; + field public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = "android.media.tv.extension.signal.ITunerFrontendSignalInfoInterface"; + field public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = "android.media.tv.extension.signal.ITunerFrontendSignalInfoListener"; + field public static final String IVBI_RATING_INTERFACE = "android.media.tv.extension.rating.IVbiRatingInterface"; + field public static final String IVBI_RATING_LISTENER = "android.media.tv.extension.rating.IVbiRatingListener"; + field public static final String IVIDEO_SIGNAL_INFO = "android.media.tv.extension.signal.IVideoSignalInfo"; + field public static final String IVIDEO_SIGNAL_INFO_LISTENER = "android.media.tv.extension.signal.IVideoSignalInfoListener"; field public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2; // 0x2 field public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1; // 0x1 field public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3; // 0x3 @@ -12724,6 +12861,36 @@ package android.security.advancedprotection { } +package android.security.authenticationpolicy { + + @FlaggedApi("android.security.secure_lockdown") public final class AuthenticationPolicyManager { + method @FlaggedApi("android.security.secure_lockdown") @RequiresPermission(android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE) public int disableSecureLockDevice(@NonNull android.security.authenticationpolicy.DisableSecureLockDeviceParams); + method @FlaggedApi("android.security.secure_lockdown") @RequiresPermission(android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE) public int enableSecureLockDevice(@NonNull android.security.authenticationpolicy.EnableSecureLockDeviceParams); + field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_ALREADY_ENABLED = 6; // 0x6 + field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_INSUFFICIENT_BIOMETRICS = 5; // 0x5 + field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_INVALID_PARAMS = 3; // 0x3 + field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_NO_BIOMETRICS_ENROLLED = 4; // 0x4 + field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_UNKNOWN = 0; // 0x0 + field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_UNSUPPORTED = 2; // 0x2 + field @FlaggedApi("android.security.secure_lockdown") public static final int SUCCESS = 1; // 0x1 + } + + @FlaggedApi("android.security.secure_lockdown") public final class DisableSecureLockDeviceParams implements android.os.Parcelable { + ctor public DisableSecureLockDeviceParams(@NonNull String); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.security.authenticationpolicy.DisableSecureLockDeviceParams> CREATOR; + } + + @FlaggedApi("android.security.secure_lockdown") public final class EnableSecureLockDeviceParams implements android.os.Parcelable { + ctor public EnableSecureLockDeviceParams(@NonNull String); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.security.authenticationpolicy.EnableSecureLockDeviceParams> CREATOR; + } + +} + package android.security.forensic { @FlaggedApi("android.security.afl_api") public class ForensicManager { @@ -15146,6 +15313,32 @@ package android.telephony { method @NonNull public android.telephony.CellIdentityWcdma sanitizeLocationInfo(); } + @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") public final class CellularIdentifierDisclosure implements android.os.Parcelable { + method public int describeContents(); + method public int getCellularIdentifier(); + method public int getNasProtocolMessage(); + method @NonNull public String getPlmn(); + method public boolean isEmergency(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CELLULAR_IDENTIFIER_IMEI = 2; // 0x2 + field public static final int CELLULAR_IDENTIFIER_IMSI = 1; // 0x1 + field public static final int CELLULAR_IDENTIFIER_SUCI = 3; // 0x3 + field public static final int CELLULAR_IDENTIFIER_UNKNOWN = 0; // 0x0 + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellularIdentifierDisclosure> CREATOR; + field public static final int NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST = 1; // 0x1 + field public static final int NAS_PROTOCOL_MESSAGE_AUTHENTICATION_AND_CIPHERING_RESPONSE = 6; // 0x6 + field public static final int NAS_PROTOCOL_MESSAGE_CM_REESTABLISHMENT_REQUEST = 9; // 0x9 + field public static final int NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST = 10; // 0xa + field public static final int NAS_PROTOCOL_MESSAGE_DEREGISTRATION_REQUEST = 8; // 0x8 + field public static final int NAS_PROTOCOL_MESSAGE_DETACH_REQUEST = 3; // 0x3 + field public static final int NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE = 2; // 0x2 + field public static final int NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION = 11; // 0xb + field public static final int NAS_PROTOCOL_MESSAGE_LOCATION_UPDATE_REQUEST = 5; // 0x5 + field public static final int NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST = 7; // 0x7 + field public static final int NAS_PROTOCOL_MESSAGE_TRACKING_AREA_UPDATE_REQUEST = 4; // 0x4 + field public static final int NAS_PROTOCOL_MESSAGE_UNKNOWN = 0; // 0x0 + } + public final class DataFailCause { field @Deprecated public static final int VSNCP_APN_UNATHORIZED = 2238; // 0x8be } @@ -15597,6 +15790,75 @@ package android.telephony { field public static final int USER_NOT_MEMBER_OF_CUG = 87; // 0x57 } + @FlaggedApi("com.android.internal.telephony.flags.security_algorithms_update_indications") public final class SecurityAlgorithmUpdate implements android.os.Parcelable { + method public int describeContents(); + method public int getConnectionEvent(); + method public int getEncryption(); + method public int getIntegrity(); + method public boolean isUnprotectedEmergency(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 11; // 0xb + field public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5; // 0x5 + field public static final int CONNECTION_EVENT_CS_SIGNALLING_3G = 2; // 0x2 + field public static final int CONNECTION_EVENT_CS_SIGNALLING_GSM = 0; // 0x0 + field public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 10; // 0xa + field public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4; // 0x4 + field public static final int CONNECTION_EVENT_PS_SIGNALLING_3G = 3; // 0x3 + field public static final int CONNECTION_EVENT_PS_SIGNALLING_GPRS = 1; // 0x1 + field public static final int CONNECTION_EVENT_VOLTE_RTP = 8; // 0x8 + field public static final int CONNECTION_EVENT_VOLTE_RTP_SOS = 9; // 0x9 + field public static final int CONNECTION_EVENT_VOLTE_SIP = 6; // 0x6 + field public static final int CONNECTION_EVENT_VOLTE_SIP_SOS = 7; // 0x7 + field public static final int CONNECTION_EVENT_VONR_RTP = 14; // 0xe + field public static final int CONNECTION_EVENT_VONR_RTP_SOS = 15; // 0xf + field public static final int CONNECTION_EVENT_VONR_SIP = 12; // 0xc + field public static final int CONNECTION_EVENT_VONR_SIP_SOS = 13; // 0xd + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SecurityAlgorithmUpdate> CREATOR; + field public static final int SECURITY_ALGORITHM_A50 = 0; // 0x0 + field public static final int SECURITY_ALGORITHM_A51 = 1; // 0x1 + field public static final int SECURITY_ALGORITHM_A52 = 2; // 0x2 + field public static final int SECURITY_ALGORITHM_A53 = 3; // 0x3 + field public static final int SECURITY_ALGORITHM_A54 = 4; // 0x4 + field public static final int SECURITY_ALGORITHM_AES_CBC = 71; // 0x47 + field public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73; // 0x49 + field public static final int SECURITY_ALGORITHM_AES_GCM = 69; // 0x45 + field public static final int SECURITY_ALGORITHM_AES_GMAC = 70; // 0x46 + field public static final int SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128 = 101; // 0x65 + field public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72; // 0x48 + field public static final int SECURITY_ALGORITHM_EEA0 = 41; // 0x29 + field public static final int SECURITY_ALGORITHM_EEA1 = 42; // 0x2a + field public static final int SECURITY_ALGORITHM_EEA2 = 43; // 0x2b + field public static final int SECURITY_ALGORITHM_EEA3 = 44; // 0x2c + field public static final int SECURITY_ALGORITHM_ENCR_AES_CBC = 100; // 0x64 + field public static final int SECURITY_ALGORITHM_ENCR_AES_GCM_16 = 99; // 0x63 + field public static final int SECURITY_ALGORITHM_GEA0 = 14; // 0xe + field public static final int SECURITY_ALGORITHM_GEA1 = 15; // 0xf + field public static final int SECURITY_ALGORITHM_GEA2 = 16; // 0x10 + field public static final int SECURITY_ALGORITHM_GEA3 = 17; // 0x11 + field public static final int SECURITY_ALGORITHM_GEA4 = 18; // 0x12 + field public static final int SECURITY_ALGORITHM_GEA5 = 19; // 0x13 + field public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75; // 0x4b + field public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74; // 0x4a + field public static final int SECURITY_ALGORITHM_IMS_NULL = 67; // 0x43 + field public static final int SECURITY_ALGORITHM_NEA0 = 55; // 0x37 + field public static final int SECURITY_ALGORITHM_NEA1 = 56; // 0x38 + field public static final int SECURITY_ALGORITHM_NEA2 = 57; // 0x39 + field public static final int SECURITY_ALGORITHM_NEA3 = 58; // 0x3a + field public static final int SECURITY_ALGORITHM_ORYX = 124; // 0x7c + field public static final int SECURITY_ALGORITHM_OTHER = 114; // 0x72 + field public static final int SECURITY_ALGORITHM_RTP = 85; // 0x55 + field public static final int SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG = 66; // 0x42 + field public static final int SECURITY_ALGORITHM_SIP_NULL = 68; // 0x44 + field public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87; // 0x57 + field public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88; // 0x58 + field public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89; // 0x59 + field public static final int SECURITY_ALGORITHM_SRTP_NULL = 86; // 0x56 + field public static final int SECURITY_ALGORITHM_UEA0 = 29; // 0x1d + field public static final int SECURITY_ALGORITHM_UEA1 = 30; // 0x1e + field public static final int SECURITY_ALGORITHM_UEA2 = 31; // 0x1f + field public static final int SECURITY_ALGORITHM_UNKNOWN = 113; // 0x71 + } + public class ServiceState implements android.os.Parcelable { method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int); method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int); @@ -15821,6 +16083,7 @@ package android.telephony { field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; // 0x4 field public static final int EVENT_CALL_STATE_CHANGED = 6; // 0x6 field public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; // 0x11 + field @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED = 47; // 0x2f field @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_CELL_INFO_CHANGED = 11; // 0xb field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_LOCATION_CHANGED = 5; // 0x5 field public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19; // 0x13 @@ -15845,6 +16108,7 @@ package android.telephony { field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13; // 0xd field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24; // 0x18 field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_REGISTRATION_FAILURE = 31; // 0x1f + field @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SECURITY_ALGORITHMS_CHANGED = 46; // 0x2e field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1 field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9 field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2 @@ -15863,6 +16127,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>); } + @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") public static interface TelephonyCallback.CellularIdentifierDisclosedListener { + method public void onCellularIdentifierDisclosedChanged(@NonNull android.telephony.CellularIdentifierDisclosure); + } + public static interface TelephonyCallback.DataEnabledListener { method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onDataEnabledChanged(boolean, int); } @@ -15901,6 +16169,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int); } + @FlaggedApi("com.android.internal.telephony.flags.security_algorithms_update_indications") public static interface TelephonyCallback.SecurityAlgorithmsListener { + method public void onSecurityAlgorithmsChanged(@NonNull android.telephony.SecurityAlgorithmUpdate); + } + @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public static interface TelephonyCallback.SimultaneousCellularCallingSupportListener { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSimultaneousCellularCallingSubscriptionsChanged(@NonNull java.util.Set<java.lang.Integer>); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 8fd2cd55b8a8..ad1d937a3d10 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1805,6 +1805,7 @@ package android.hardware.input { method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int); method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByDescriptor(@NonNull String); method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByPort(@NonNull String); + method public void resetLockedModifierState(); field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL } @@ -3398,6 +3399,10 @@ package android.telephony { ctor public BarringInfo.BarringServiceInfo(int, boolean, int, int); } + @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") public final class CellularIdentifierDisclosure implements android.os.Parcelable { + ctor public CellularIdentifierDisclosure(int, int, @NonNull String, boolean); + } + public class MbmsDownloadSession implements java.lang.AutoCloseable { field public static final String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = "mbms-download-service-override"; } @@ -3425,6 +3430,10 @@ package android.telephony { ctor @Deprecated public PreciseDataConnectionState(int, int, int, @NonNull String, @Nullable android.net.LinkProperties, int); } + @FlaggedApi("com.android.internal.telephony.flags.security_algorithms_update_indications") public final class SecurityAlgorithmUpdate implements android.os.Parcelable { + ctor public SecurityAlgorithmUpdate(int, int, int, boolean); + } + public class ServiceState implements android.os.Parcelable { method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo); method public int getDataNetworkType(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 419eb7dac5f0..38aea64386a0 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1270,27 +1270,22 @@ public class Activity extends ContextThemeWrapper } /** - * To make users aware of system features such as the app header menu and its various - * functionalities, educational dialogs are shown to demonstrate how to find and utilize these - * features. Using this method, an activity can specify if it wants these educational dialogs to - * be shown. When set to {@code true}, these dialogs are not completely blocked; however, the - * system will be notified that they should not be shown unless necessary. If this API is not - * called, the system's educational dialogs are not limited by default. - * - * <p>This method can be utilized when activities have states where showing an - * educational dialog would be disruptive to the user. For example, if a game application is - * expecting prompt user input, this method can be used to limit educational dialogs such as the - * dialogs that showcase the app header's features which, in this instance, would disrupt the - * user's experience if shown.</p> - * - * <p>Note that educational dialogs may be shown soon after this activity is launched, so - * this method must be called early if the intent is to limit the dialogs from the start.</p> + * Requests to show the “Open in browser” education. “Open in browser” is a feature + * within the app header that allows users to switch from an app to the web. The feature + * is made available when an application is opened by a user clicking a link or when a + * link is provided by an application. Links can be provided by utilizing + * {@link AssistContent#EXTRA_AUTHENTICATING_USER_WEB_URI} or + * {@link AssistContent#setWebUri}. + * + * <p>This method should be utilized when an activity wants to nudge the user to switch + * to the web application in cases where the web may provide the user with a better + * experience. Note that this method does not guarantee that the education will be shown.</p> */ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION) - public final void setLimitSystemEducationDialogs(boolean limitSystemEducationDialogs) { + public final void requestOpenInBrowserEducation() { try { ActivityTaskManager - .getService().setLimitSystemEducationDialogs(mToken, limitSystemEducationDialogs); + .getService().requestOpenInBrowserEducation(mToken); } catch (RemoteException e) { // Empty } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index ce0ec602e612..34765781d105 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -8934,12 +8934,21 @@ public class AppOpsManager { } /** - * Do a quick check for whether an application might be able to perform an operation. - * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String, - * String, String)} or {@link #startOp(String, int, String, String, String)} for your actual - * security checks. This function can just be used for a quick check to see if an operation has - * been disabled for the application, as an early reject of some work. This does not modify the - * time stamp or other data about the operation. + * Check whether an application might be able to perform an operation. + * <p> + * For platform versions before {@link android.os.Build.VERSION_CODES#BAKLAVA}, this is + * <em>not</em> a security check; you must use {@link #noteOp(String, int, String, String, + * String)} or {@link #startOp(String, int, String, String, String)} for your actual security + * checks. This function can just be used for a quick check to see if an operation has been + * disabled for the application, as an early reject of some work. + * <p> + * For platform versions equal to or after {@link android.os.Build.VERSION_CODES#BAKLAVA}, this + * is no longer an unsafe check, and it does the same security check as {@link #noteOp(String, + * int, String, String, String)} and {@link #startOp(String, int, String, String, String)}. + * However, it's preferred to use {@link #checkOp(String, int, String)}, since the word "unsafe" + * in the name of this API is no longer accurate. + * <p> + * This API does not modify the time stamp or other data about the operation. * * @param op The operation to check. One of the OPSTR_* constants. * @param uid The user id of the application attempting to perform the operation. @@ -8948,31 +8957,108 @@ public class AppOpsManager { * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without * causing the app to crash). * @throws SecurityException If the app has been configured to crash on this op. + * + * @deprecated Use {@link #checkOp(String, int, String)} */ + @Deprecated + @FlaggedApi(android.permission.flags.Flags.FLAG_CHECK_OP_OVERLOAD_API_ENABLED) public int unsafeCheckOp(@NonNull String op, int uid, @NonNull String packageName) { return checkOp(strOpToOp(op), uid, packageName); } /** - * @deprecated Renamed to {@link #unsafeCheckOp(String, int, String)}. + * Check whether an application can perform an operation. + * <p> + * For platform versions before {@link android.os.Build.VERSION_CODES#BAKLAVA}, this is + * <em>not</em> a security check; you must use {@link #noteOp(String, int, String, String, + * String)} or {@link #startOp(String, int, String, String, String)} for your actual security + * checks. This function can just be used for a quick check to see if an operation has been + * disabled for the application, as an early reject of some work. + * <p> + * For platform versions equal to or after {@link android.os.Build.VERSION_CODES#BAKLAVA}, it + * does the same security check as {@link #noteOp(String, int, String, String, String)} and + * {@link #startOp(String, int, String, String, String)}, and should be preferred to use. + * <p> + * This API does not modify the time stamp or other data about the operation. + * + * @param op The operation to check. One of the OPSTR_* constants. + * @param uid The uid of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. */ - @Deprecated + @FlaggedApi(android.permission.flags.Flags.FLAG_CHECK_OP_OVERLOAD_API_ENABLED) public int checkOp(@NonNull String op, int uid, @NonNull String packageName) { return checkOp(strOpToOp(op), uid, packageName); } /** - * Like {@link #checkOp} but instead of throwing a {@link SecurityException} it - * returns {@link #MODE_ERRORED}. + * Like {@link #unsafeCheckOp(String, int, String)} but instead of throwing a + * {@link SecurityException} it returns {@link #MODE_ERRORED}. + * + * @deprecated Use {@link #checkOpNoThrow(String, int, String)} */ + @Deprecated + @FlaggedApi(android.permission.flags.Flags.FLAG_CHECK_OP_OVERLOAD_API_ENABLED) public int unsafeCheckOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) { return checkOpNoThrow(strOpToOp(op), uid, packageName); } /** - * @deprecated Renamed to {@link #unsafeCheckOpNoThrow(String, int, String)}. + * Check whether an application can perform an operation. It does the same security check as + * {@link #noteOp(String, int, String, String, String)} and {@link #startOp(String, int, String, + * String, String)}, but does not modify the time stamp or other data about the operation. + * + * @param op The operation to check. One of the OPSTR_* constants. + * @param uid The uid of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @param attributionTag The {@link Context#createAttributionContext attribution tag} of the + * calling context or {@code null} for default attribution + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED} + * if it is not allowed and should be silently ignored (without causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. */ - @Deprecated + @FlaggedApi(android.permission.flags.Flags.FLAG_CHECK_OP_OVERLOAD_API_ENABLED) + public int checkOp(@NonNull String op, int uid, @NonNull String packageName, + @Nullable String attributionTag) { + int mode = checkOpNoThrow(strOpToOp(op), uid, packageName, attributionTag, + Context.DEVICE_ID_DEFAULT); + if (mode == MODE_ERRORED) { + throw new SecurityException(buildSecurityExceptionMsg(strOpToOp(op), uid, packageName)); + } + return mode; + } + + /** + * Like {@link #checkOp(String, int, String, String)} but instead of throwing a + * {@link SecurityException} it returns {@link #MODE_ERRORED}. + */ + @FlaggedApi(android.permission.flags.Flags.FLAG_CHECK_OP_OVERLOAD_API_ENABLED) + public int checkOpNoThrow(@NonNull String op, int uid, @NonNull String packageName, + @Nullable String attributionTag) { + return checkOpNoThrow(strOpToOp(op), uid, packageName, attributionTag, + Context.DEVICE_ID_DEFAULT); + } + + /** + * Like {@link #checkOp(String, int, String, String)} but returns the <em>raw</em> mode + * associated with the op. Does not throw a security exception, does not translate + * {@link #MODE_FOREGROUND}. + */ + @FlaggedApi(android.permission.flags.Flags.FLAG_CHECK_OP_OVERLOAD_API_ENABLED) + public int checkOpRawNoThrow(@NonNull String op, int uid, @NonNull String packageName, + @Nullable String attributionTag) { + return checkOpRawNoThrow(strOpToOp(op), uid, packageName, attributionTag, + Context.DEVICE_ID_DEFAULT); + } + + /** + * Like {@link #checkOp(String, int, String)} but instead of throwing a + * {@link SecurityException} it returns {@link #MODE_ERRORED}. + */ + @FlaggedApi(android.permission.flags.Flags.FLAG_CHECK_OP_OVERLOAD_API_ENABLED) public int checkOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) { return checkOpNoThrow(strOpToOp(op), uid, packageName); } @@ -8980,16 +9066,23 @@ public class AppOpsManager { /** * Like {@link #checkOp} but returns the <em>raw</em> mode associated with the op. * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}. + * + * @deprecated Use {@link #checkOpRawNoThrow(String, int, String, String)} instead */ + @Deprecated + @FlaggedApi(android.permission.flags.Flags.FLAG_CHECK_OP_OVERLOAD_API_ENABLED) public int unsafeCheckOpRaw(@NonNull String op, int uid, @NonNull String packageName) { return unsafeCheckOpRawNoThrow(op, uid, packageName); } /** - * Like {@link #unsafeCheckOpNoThrow(String, int, String)} but returns the <em>raw</em> - * mode associated with the op. Does not throw a security exception, does not translate - * {@link #MODE_FOREGROUND}. + * Like {@link #checkOp} but returns the <em>raw</em> mode associated with the op. + * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}. + * + * @deprecated Use {@link #checkOpRawNoThrow(String, int, String, String)} instead */ + @Deprecated + @FlaggedApi(android.permission.flags.Flags.FLAG_CHECK_OP_OVERLOAD_API_ENABLED) public int unsafeCheckOpRawNoThrow(@NonNull String op, int uid, @NonNull String packageName) { return unsafeCheckOpRawNoThrow(strOpToOp(op), uid, packageName); } @@ -9000,8 +9093,9 @@ public class AppOpsManager { * @hide */ public int unsafeCheckOpRawNoThrow(int op, @NonNull AttributionSource attributionSource) { - return unsafeCheckOpRawNoThrow(op, attributionSource.getUid(), - attributionSource.getPackageName(), attributionSource.getDeviceId()); + return checkOpRawNoThrow(op, attributionSource.getUid(), + attributionSource.getPackageName(), attributionSource.getAttributionTag(), + attributionSource.getDeviceId()); } /** @@ -9022,20 +9116,20 @@ public class AppOpsManager { * @hide */ public int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName) { - return unsafeCheckOpRawNoThrow(op, uid, packageName, Context.DEVICE_ID_DEFAULT); + return checkOpRawNoThrow(op, uid, packageName, null, Context.DEVICE_ID_DEFAULT); } - private int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName, - int virtualDeviceId) { + private int checkOpRawNoThrow(int op, int uid, @NonNull String packageName, + @Nullable String attributionTag, int virtualDeviceId) { try { int mode; if (isAppOpModeCachingEnabled(op)) { mode = sAppOpModeCache.query( - new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null, + new AppOpModeQuery(op, uid, packageName, virtualDeviceId, attributionTag, "unsafeCheckOpRawNoThrow")); } else { mode = mService.checkOperationRawForDevice( - op, uid, packageName, null, virtualDeviceId); + op, uid, packageName, attributionTag, virtualDeviceId); } return mode; } catch (RemoteException e) { @@ -9434,10 +9528,12 @@ public class AppOpsManager { } /** - * Do a quick check for whether an application might be able to perform an operation. - * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String, - * String, String)} or {@link #startOp(int, int, String, boolean, String, String)} for your - * actual security checks, which also ensure that the given uid and package name are consistent. + * Check whether an application can perform an operation. + * <p> + * For platform versions before {@link android.os.Build.VERSION_CODES#BAKLAVA}, this is + * <em>not</em> a security check; you must use {@link #noteOp(String, int, String, String, + * String)} or {@link #startOp(int, int, String, boolean, String, String)} for your actual + * security checks, which also ensure that the given uid and package name are consistent. * This function can just be used for a quick check to see if an operation has been disabled for * the application, as an early reject of some work. This does not modify the time stamp or * other data about the operation. @@ -9453,6 +9549,13 @@ public class AppOpsManager { * as {@link #MODE_ALLOWED}.</li> * </ul> * + * <p> + * For platform versions equal to or after {@link android.os.Build.VERSION_CODES#BAKLAVA}, it + * does the same security check as {@link #noteOp(String, int, String, String, String)} and + * {@link #startOp(String, int, String, String, String)}. + * <p> + * This API does not modify the time stamp or other data about the operation. + * * @param op The operation to check. One of the OP_* constants. * @param uid The user id of the application attempting to perform the operation. * @param packageName The name of the application attempting to perform the operation. @@ -9464,29 +9567,11 @@ public class AppOpsManager { */ @UnsupportedAppUsage public int checkOp(int op, int uid, String packageName) { - try { - int mode; - if (isAppOpModeCachingEnabled(op)) { - mode = sAppOpModeCache.query( - new AppOpModeQuery(op, uid, packageName, Context.DEVICE_ID_DEFAULT, null, - "checkOp")); - if (mode == MODE_FOREGROUND) { - // We only cache raw mode. If the mode is FOREGROUND, we need another binder - // call to fetch translated value based on the process state. - mode = mService.checkOperationForDevice(op, uid, packageName, - Context.DEVICE_ID_DEFAULT); - } - } else { - mode = mService.checkOperationForDevice(op, uid, packageName, - Context.DEVICE_ID_DEFAULT); - } - if (mode == MODE_ERRORED) { - throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); - } - return mode; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + int mode = checkOpNoThrow(op, uid, packageName, null, Context.DEVICE_ID_DEFAULT); + if (mode == MODE_ERRORED) { + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } + return mode; } /** @@ -9499,7 +9584,7 @@ public class AppOpsManager { */ public int checkOpNoThrow(int op, AttributionSource attributionSource) { return checkOpNoThrow(op, attributionSource.getUid(), attributionSource.getPackageName(), - attributionSource.getDeviceId()); + attributionSource.getAttributionTag(), attributionSource.getDeviceId()); } /** @@ -9512,23 +9597,26 @@ public class AppOpsManager { */ @UnsupportedAppUsage public int checkOpNoThrow(int op, int uid, String packageName) { - return checkOpNoThrow(op, uid, packageName, Context.DEVICE_ID_DEFAULT); + return checkOpNoThrow(op, uid, packageName, null, Context.DEVICE_ID_DEFAULT); } - private int checkOpNoThrow(int op, int uid, String packageName, int virtualDeviceId) { + private int checkOpNoThrow(int op, int uid, String packageName, @Nullable String attributionTag, + int virtualDeviceId) { try { int mode; if (isAppOpModeCachingEnabled(op)) { mode = sAppOpModeCache.query( - new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null, + new AppOpModeQuery(op, uid, packageName, virtualDeviceId, attributionTag, "checkOpNoThrow")); if (mode == MODE_FOREGROUND) { // We only cache raw mode. If the mode is FOREGROUND, we need another binder // call to fetch translated value based on the process state. - mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId); + mode = mService.checkOperationForDevice(op, uid, packageName, attributionTag, + virtualDeviceId); } } else { - mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId); + mode = mService.checkOperationForDevice(op, uid, packageName, attributionTag, + virtualDeviceId); } return mode; } catch (RemoteException e) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index a8412fa66609..0668958b2d5c 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -864,7 +864,8 @@ interface IActivityManager { /** * Suppress or reenable the rate limit on foreground service notification deferral. - * This is for use within CTS and is protected by android.permission.WRITE_DEVICE_CONFIG. + * This is for use within CTS and is protected by android.permission.WRITE_DEVICE_CONFIG + * and WRITE_ALLOWLISTED_DEVICE_CONFIG. * * @param enable false to suppress rate-limit policy; true to reenable it. */ diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index ec7b72ec677e..c6f62a21641d 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -242,8 +242,8 @@ interface IActivityTaskManager { boolean supportsLocalVoiceInteraction(); - // Sets whether system educational dialogs should be limited - void setLimitSystemEducationDialogs(IBinder appToken, boolean limitSystemEducationDialogs); + // Requests the "Open in browser" education to be shown + void requestOpenInBrowserEducation(IBinder appToken); // Get device configuration ConfigurationInfo getDeviceConfigurationInfo(); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 62820ad5a4d6..67f7bee4028e 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -18,6 +18,7 @@ package android.app; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -52,7 +53,9 @@ import android.view.IOnKeyguardExitResult; import android.view.IWindowManager; import android.view.WindowManagerGlobal; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.IDeviceLockedStateListener; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardLockedStateListener; import com.android.internal.util.Preconditions; @@ -253,6 +256,26 @@ public class KeyguardManager { private final ArrayMap<KeyguardLockedStateListener, Executor> mKeyguardLockedStateListeners = new ArrayMap<>(); + private final IDeviceLockedStateListener mIDeviceLockedStateListener = + new IDeviceLockedStateListener.Stub() { + @Override + public void onDeviceLockedStateChanged(boolean isDeviceLocked) { + if (!Flags.deviceUnlockListener()) { + return; + } + synchronized (mDeviceLockedStateListeners) { + mDeviceLockedStateListeners.forEach((listener, executor) -> { + executor.execute( + () -> listener.onDeviceLockedStateChanged(isDeviceLocked)); + }); + } + } + }; + + @GuardedBy("mDeviceLockedStateListeners") + private final ArrayMap<DeviceLockedStateListener, Executor> + mDeviceLockedStateListeners = new ArrayMap<>(); + /** * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics * if enrolled) for the current user of the device. The caller is expected to launch this @@ -1370,4 +1393,77 @@ public class KeyguardManager { } } } + + + /** + * Listener for device locked state changes. + */ + @FunctionalInterface + @FlaggedApi(Flags.FLAG_DEVICE_UNLOCK_LISTENER) + public interface DeviceLockedStateListener { + /** + * Callback function that executes when the device locked state changes. + */ + void onDeviceLockedStateChanged(boolean isDeviceLocked); + } + + + /** + * Registers a listener to execute when the device locked state changes. + * + * @param executor The {@link Executor} where the {@code listener} will be invoked + * @param listener The listener to add to receive device locked state changes. + * + * @see #isDeviceLocked() + * @see #removeDeviceLockedStateListener(DeviceLockedStateListener) + */ + @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) + @FlaggedApi(Flags.FLAG_DEVICE_UNLOCK_LISTENER) + public void addDeviceLockedStateListener(@NonNull @CallbackExecutor Executor executor, + @NonNull DeviceLockedStateListener listener) { + if (!Flags.deviceUnlockListener()) { + return; + } + + synchronized (mDeviceLockedStateListeners) { + mDeviceLockedStateListeners.put(listener, executor); + if (mDeviceLockedStateListeners.size() > 1) { + return; + } + try { + mTrustManager.registerDeviceLockedStateListener(mIDeviceLockedStateListener, + mContext.getDeviceId()); + } catch (RemoteException re) { + Log.d(TAG, "TrustManager service died", re); + } + } + } + + /** + * Unregisters a listener that executes when the device locked state changes. + * + * @param listener The listener to remove. + * + * @see #isDeviceLocked() + * @see #addDeviceLockedStateListener(Executor, DeviceLockedStateListener) + */ + @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) + @FlaggedApi(Flags.FLAG_DEVICE_UNLOCK_LISTENER) + public void removeDeviceLockedStateListener(@NonNull DeviceLockedStateListener listener) { + if (!Flags.deviceUnlockListener()) { + return; + } + + synchronized (mDeviceLockedStateListeners) { + mDeviceLockedStateListeners.remove(listener); + if (!mDeviceLockedStateListeners.isEmpty()) { + return; + } + try { + mTrustManager.unregisterDeviceLockedStateListener(mIDeviceLockedStateListener); + } catch (RemoteException re) { + Log.d(TAG, "TrustManager service died", re); + } + } + } } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index cfe0ff9440e7..ff73382c43e6 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -811,20 +811,32 @@ public class Notification implements Parcelable } private static boolean isStandardLayout(int layoutId) { - // TODO: b/359128724 - Add to static list when inlining the flag. + if (Flags.notificationsRedesignTemplates()) { + return switch (layoutId) { + case R.layout.notification_2025_template_collapsed_base, + R.layout.notification_2025_template_heads_up_base, + R.layout.notification_2025_template_header, + R.layout.notification_template_material_big_base, + R.layout.notification_template_material_big_picture, + R.layout.notification_template_material_big_text, + R.layout.notification_template_material_inbox, + R.layout.notification_template_material_messaging, + R.layout.notification_template_material_big_messaging, + R.layout.notification_template_material_conversation, + R.layout.notification_template_material_media, + R.layout.notification_template_material_big_media, + R.layout.notification_template_material_call, + R.layout.notification_template_material_big_call, + R.layout.notification_template_header -> true; + case R.layout.notification_template_material_progress -> Flags.apiRichOngoing(); + default -> false; + }; + } if (Flags.apiRichOngoing()) { if (layoutId == R.layout.notification_template_material_progress) { return true; } } - // TODO: b/378660052 - Add to static list when inlining the flag. - if (Flags.notificationsRedesignTemplates()) { - switch(layoutId) { - case R.layout.notification_2025_template_collapsed_base: - case R.layout.notification_2025_template_header: - return true; - } - } return STANDARD_LAYOUTS.contains(layoutId); } @@ -7505,7 +7517,11 @@ public class Notification implements Parcelable } private int getHeadsUpBaseLayoutResource() { - return R.layout.notification_template_material_heads_up_base; + if (Flags.notificationsRedesignTemplates()) { + return R.layout.notification_2025_template_heads_up_base; + } else { + return R.layout.notification_template_material_heads_up_base; + } } private int getCompactHeadsUpBaseLayoutResource() { @@ -9520,7 +9536,6 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.icon, View.GONE); contentView.setViewVisibility(R.id.conversation_face_pile, View.GONE); contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE); - contentView.setBoolean(R.id.conversation_icon, "setApplyCircularCrop", true); contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon); } else if (mIsGroupConversation) { contentView.setViewVisibility(R.id.icon, View.GONE); @@ -11548,11 +11563,9 @@ public class Notification implements Parcelable contentView.setBundle(R.id.progress, "setProgressModel", model.toBundle()); - if (mTrackerIcon != null) { - contentView.setIcon(R.id.progress, - "setProgressTrackerIcon", - mTrackerIcon); - } + contentView.setIcon(R.id.progress, + "setProgressTrackerIcon", + mTrackerIcon); return contentView; } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index c49b02210dd4..c310f95814dc 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -16,6 +16,8 @@ package android.app; +import static android.Manifest.permission.POST_NOTIFICATIONS; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.service.notification.Flags.notificationClassification; import android.annotation.CallbackExecutor; @@ -1597,11 +1599,15 @@ public class NotificationManager { * Returns whether notifications from the calling package are enabled. */ public boolean areNotificationsEnabled() { - INotificationManager service = getService(); - try { - return service.areNotificationsEnabled(mContext.getPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + if (Flags.nmBinderPerfPermissionCheck()) { + return mContext.checkSelfPermission(POST_NOTIFICATIONS) == PERMISSION_GRANTED; + } else { + INotificationManager service = getService(); + try { + return service.areNotificationsEnabled(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } } diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 675152fbbbb6..e218418336c5 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -314,6 +314,14 @@ public class PropertyInvalidatedCache<Query, Result> { @GuardedBy("mLock") private long mMisses = 0; + // This counter tracks the number of times {@link #recompute} returned a null value. Null + // results are cached, or not, depending on instantiation arguments. Caching nulls when they + // should not be cached is a functional error. Failing to cache nulls that can be cached is a + // performance error. A non-zero value here means the cache should be examined to be sure + // that nulls are correctly cached, or not. + @GuardedBy("mLock") + private long mNulls = 0; + @GuardedBy("mLock") private long[] mSkips = new long[MAX_RESERVED_NONCE + 1]; @@ -374,6 +382,11 @@ public class PropertyInvalidatedCache<Query, Result> { private final String mCacheName; /** + * True if nulls are valid returns from recompute(). + */ + private final boolean mCacheNullResults; + + /** * The function that computes a Result, given a Query. This function is called on a * cache miss. */ @@ -509,6 +522,19 @@ public class PropertyInvalidatedCache<Query, Result> { } /** + * Return true if the entry is in the cache. + */ + boolean containsKey(Query query) { + final int uid = callerUid(); + var map = mCache.get(uid); + if (map != null) { + return map.containsKey(query); + } else { + return false; + } + } + + /** * Remove an entry from the cache. */ void remove(Query query) { @@ -1101,7 +1127,7 @@ public class PropertyInvalidatedCache<Query, Result> { * @hide */ public static record Args(@NonNull String mModule, @Nullable String mApi, - int mMaxEntries, boolean mIsolateUids, boolean mTestMode) { + int mMaxEntries, boolean mIsolateUids, boolean mTestMode, boolean mCacheNulls) { // Validation: the module must be one of the known module strings and the maxEntries must // be positive. @@ -1119,24 +1145,29 @@ public class PropertyInvalidatedCache<Query, Result> { null, // api 32, // maxEntries true, // isolateUids - false // testMode + false, // testMode + true // allowNulls ); } public Args api(@NonNull String api) { - return new Args(mModule, api, mMaxEntries, mIsolateUids, mTestMode); + return new Args(mModule, api, mMaxEntries, mIsolateUids, mTestMode, mCacheNulls); } public Args maxEntries(int val) { - return new Args(mModule, mApi, val, mIsolateUids, mTestMode); + return new Args(mModule, mApi, val, mIsolateUids, mTestMode, mCacheNulls); } public Args isolateUids(boolean val) { - return new Args(mModule, mApi, mMaxEntries, val, mTestMode); + return new Args(mModule, mApi, mMaxEntries, val, mTestMode, mCacheNulls); } public Args testMode(boolean val) { - return new Args(mModule, mApi, mMaxEntries, mIsolateUids, val); + return new Args(mModule, mApi, mMaxEntries, mIsolateUids, val, mCacheNulls); + } + + public Args cacheNulls(boolean val) { + return new Args(mModule, mApi, mMaxEntries, mIsolateUids, mTestMode, val); } } @@ -1153,6 +1184,7 @@ public class PropertyInvalidatedCache<Query, Result> { @Nullable QueryHandler<Query, Result> computer) { mPropertyName = createPropertyName(args.mModule, args.mApi); mCacheName = cacheName; + mCacheNullResults = args.mCacheNulls && Flags.picCacheNulls(); mNonce = getNonceHandler(mPropertyName); mMaxEntries = args.mMaxEntries; mCache = new CacheMap<>(args.mIsolateUids, args.mTestMode); @@ -1491,12 +1523,24 @@ public class PropertyInvalidatedCache<Query, Result> { } return recompute(query); } + + final boolean cacheHit; final Result cachedResult; synchronized (mLock) { if (currentNonce == mLastSeenNonce) { cachedResult = mCache.get(query); - - if (cachedResult != null) mHits++; + if (cachedResult == null) { + if (mCacheNullResults) { + cacheHit = mCache.containsKey(query); + } else { + cacheHit = false; + } + } else { + cacheHit = true; + } + if (cacheHit) { + mHits++; + } } else { if (DEBUG) { Log.d(TAG, formatSimple( @@ -1506,16 +1550,18 @@ public class PropertyInvalidatedCache<Query, Result> { } clear(); mLastSeenNonce = currentNonce; + cacheHit = false; cachedResult = null; } } + // Cache hit --- but we're not quite done yet. A value in the cache might need to // be augmented in a "refresh" operation. The refresh operation can combine the // old and the new nonce values. In order to make sure the new parts of the value // are consistent with the old, possibly-reused parts, we check the property value // again after the refresh and do the whole fetch again if the property invalidated // us while we were refreshing. - if (cachedResult != null) { + if (cacheHit) { final Result refreshedResult = refresh(cachedResult, query); if (refreshedResult != cachedResult) { if (DEBUG) { @@ -1550,6 +1596,7 @@ public class PropertyInvalidatedCache<Query, Result> { } return maybeCheckConsistency(query, cachedResult); } + // Cache miss: make the value from scratch. if (DEBUG) { Log.d(TAG, "cache miss for " + cacheName() + " " + queryToString(query)); @@ -1558,8 +1605,13 @@ public class PropertyInvalidatedCache<Query, Result> { synchronized (mLock) { // If someone else invalidated the cache while we did the recomputation, don't // update the cache with a potentially stale result. - if (mLastSeenNonce == currentNonce && result != null) { - mCache.put(query, result); + if (mLastSeenNonce == currentNonce) { + if (result != null || mCacheNullResults) { + mCache.put(query, result); + } + if (result == null) { + mNulls++; + } } mMisses++; } @@ -1977,8 +2029,8 @@ public class PropertyInvalidatedCache<Query, Result> { pw.println(formatSimple(" Cache Name: %s", cacheName())); pw.println(formatSimple(" Property: %s", mPropertyName)); pw.println(formatSimple( - " Hits: %d, Misses: %d, Skips: %d, Clears: %d", - mHits, mMisses, getSkipsLocked(), mClears)); + " Hits: %d, Misses: %d, Skips: %d, Clears: %d, Nulls: %d", + mHits, mMisses, getSkipsLocked(), mClears, mNulls)); // Print all the skip reasons. pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 53a7dad76788..6a23349bf8aa 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -241,6 +241,8 @@ import android.security.advancedprotection.AdvancedProtectionManager; import android.security.advancedprotection.IAdvancedProtectionService; import android.security.attestationverification.AttestationVerificationManager; import android.security.attestationverification.IAttestationVerificationManagerService; +import android.security.authenticationpolicy.AuthenticationPolicyManager; +import android.security.authenticationpolicy.IAuthenticationPolicyService; import android.security.forensic.ForensicManager; import android.security.forensic.IForensicService; import android.security.keystore.KeyStoreManager; @@ -1025,6 +1027,25 @@ public final class SystemServiceRegistry { } }); + registerService(Context.AUTHENTICATION_POLICY_SERVICE, + AuthenticationPolicyManager.class, + new CachedServiceFetcher<AuthenticationPolicyManager>() { + @Override + public AuthenticationPolicyManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + if (!android.security.Flags.secureLockdown()) { + throw new ServiceNotFoundException( + Context.AUTHENTICATION_POLICY_SERVICE); + } + + final IBinder binder = ServiceManager.getServiceOrThrow( + Context.AUTHENTICATION_POLICY_SERVICE); + final IAuthenticationPolicyService service = + IAuthenticationPolicyService.Stub.asInterface(binder); + return new AuthenticationPolicyManager(ctx.getOuterContext(), service); + } + }); + registerService(Context.TV_INTERACTIVE_APP_SERVICE, TvInteractiveAppManager.class, new CachedServiceFetcher<TvInteractiveAppManager>() { @Override diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index aac963ade726..01cc9d82d56d 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -340,10 +340,10 @@ public class TaskInfo { public int requestedVisibleTypes; /** - * Whether the top activity has requested to limit educational dialogs shown by the system. + * The timestamp of the top activity's last request to show the "Open in Browser" education. * @hide */ - public boolean isTopActivityLimitSystemEducationDialogs; + public long topActivityRequestOpenInBrowserEducationTimestamp; /** * Encapsulate specific App Compat information. @@ -493,8 +493,8 @@ public class TaskInfo { && Objects.equals(capturedLink, that.capturedLink) && capturedLinkTimestamp == that.capturedLinkTimestamp && requestedVisibleTypes == that.requestedVisibleTypes - && isTopActivityLimitSystemEducationDialogs - == that.isTopActivityLimitSystemEducationDialogs + && topActivityRequestOpenInBrowserEducationTimestamp + == that.topActivityRequestOpenInBrowserEducationTimestamp && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo) && Objects.equals(topActivityMainWindowFrame, that.topActivityMainWindowFrame); } @@ -571,7 +571,7 @@ public class TaskInfo { capturedLink = source.readTypedObject(Uri.CREATOR); capturedLinkTimestamp = source.readLong(); requestedVisibleTypes = source.readInt(); - isTopActivityLimitSystemEducationDialogs = source.readBoolean(); + topActivityRequestOpenInBrowserEducationTimestamp = source.readLong(); appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR); topActivityMainWindowFrame = source.readTypedObject(Rect.CREATOR); } @@ -627,7 +627,7 @@ public class TaskInfo { dest.writeTypedObject(capturedLink, flags); dest.writeLong(capturedLinkTimestamp); dest.writeInt(requestedVisibleTypes); - dest.writeBoolean(isTopActivityLimitSystemEducationDialogs); + dest.writeLong(topActivityRequestOpenInBrowserEducationTimestamp); dest.writeTypedObject(appCompatTaskInfo, flags); dest.writeTypedObject(topActivityMainWindowFrame, flags); } @@ -672,8 +672,8 @@ public class TaskInfo { + " capturedLink=" + capturedLink + " capturedLinkTimestamp=" + capturedLinkTimestamp + " requestedVisibleTypes=" + requestedVisibleTypes - + " isTopActivityLimitSystemEducationDialogs=" - + isTopActivityLimitSystemEducationDialogs + + " topActivityRequestOpenInBrowserEducationTimestamp=" + + topActivityRequestOpenInBrowserEducationTimestamp + " appCompatTaskInfo=" + appCompatTaskInfo + " topActivityMainWindowFrame=" + topActivityMainWindowFrame + "}"; diff --git a/core/java/android/app/jank/FrameOverrunHistogram.java b/core/java/android/app/jank/FrameOverrunHistogram.java index e28ac126a90a..3ad6531a46bf 100644 --- a/core/java/android/app/jank/FrameOverrunHistogram.java +++ b/core/java/android/app/jank/FrameOverrunHistogram.java @@ -39,7 +39,7 @@ public class FrameOverrunHistogram { * Create a new instance of FrameOverrunHistogram. */ public FrameOverrunHistogram() { - mBucketCounts = new int[sBucketEndpoints.length - 1]; + mBucketCounts = new int[sBucketEndpoints.length]; } /** diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java index 7525d0402ee4..7ceaeb3fb070 100644 --- a/core/java/android/app/jank/JankDataProcessor.java +++ b/core/java/android/app/jank/JankDataProcessor.java @@ -59,7 +59,6 @@ public class JankDataProcessor { * @param appUid the uid of the app. */ public void processJankData(List<JankData> jankData, String activityName, int appUid) { - mCurrentBatchCount++; // add all the previous and active states to the pending states list. mStateTracker.retrieveAllStates(mPendingStates); @@ -79,9 +78,8 @@ public class JankDataProcessor { } } // At this point we have attributed all frames to a state. - if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) { - logMetricCounts(); - } + incrementBatchCountAndMaybeLogStats(); + // return the StatData object back to the pool to be reused. jankDataProcessingComplete(); } @@ -91,7 +89,73 @@ public class JankDataProcessor { * stats */ public void mergeJankStats(AppJankStats jankStats, String activityName) { - // TODO b/377572463 Add Merging Logic + // Each state has a key which is a combination of widget category, widget id and widget + // state, this key is also used to identify pending stats, a pending stat is essentially a + // state with frames associated with it. + String stateKey = mStateTracker.getStateKey(jankStats.getWidgetCategory(), + jankStats.getWidgetId(), jankStats.getWidgetState()); + + if (mPendingJankStats.containsKey(stateKey)) { + mergeExistingStat(stateKey, jankStats); + } else { + mergeNewStat(stateKey, activityName, jankStats); + } + + incrementBatchCountAndMaybeLogStats(); + } + + private void mergeExistingStat(String stateKey, AppJankStats jankStat) { + PendingJankStat pendingStat = mPendingJankStats.get(stateKey); + + pendingStat.mJankyFrames += jankStat.getJankyFrameCount(); + pendingStat.mTotalFrames += jankStat.getTotalFrameCount(); + + mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, + jankStat.getFrameOverrunHistogram().getBucketCounters()); + } + + private void mergeNewStat(String stateKey, String activityName, AppJankStats jankStats) { + // Check if we have space for a new stat + if (mPendingJankStats.size() > MAX_IN_MEMORY_STATS) { + return; + } + + PendingJankStat pendingStat = mPendingJankStatsPool.acquire(); + if (pendingStat == null) { + pendingStat = new PendingJankStat(); + + } + pendingStat.clearStats(); + + pendingStat.mActivityName = activityName; + pendingStat.mUid = jankStats.getUid(); + pendingStat.mWidgetId = jankStats.getWidgetId(); + pendingStat.mWidgetCategory = jankStats.getWidgetCategory(); + pendingStat.mWidgetState = jankStats.getWidgetState(); + pendingStat.mTotalFrames = jankStats.getTotalFrameCount(); + pendingStat.mJankyFrames = jankStats.getJankyFrameCount(); + + mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, + jankStats.getFrameOverrunHistogram().getBucketCounters()); + + mPendingJankStats.put(stateKey, pendingStat); + } + + private void mergeOverrunHistograms(int[] mergeTarget, int[] mergeSource) { + // The length of each histogram should be identical, if they are not then its possible the + // buckets are not in sync, these records should not be recorded. + if (mergeTarget.length != mergeSource.length) return; + + for (int i = 0; i < mergeTarget.length; i++) { + mergeTarget[i] += mergeSource[i]; + } + } + + private void incrementBatchCountAndMaybeLogStats() { + mCurrentBatchCount++; + if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) { + logMetricCounts(); + } } /** diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java index 202281f98c97..469521668d25 100644 --- a/core/java/android/app/jank/JankTracker.java +++ b/core/java/android/app/jank/JankTracker.java @@ -89,7 +89,12 @@ public class JankTracker { * stats */ public void mergeAppJankStats(AppJankStats appJankStats) { - mJankDataProcessor.mergeJankStats(appJankStats, mActivityName); + getHandler().post(new Runnable() { + @Override + public void run() { + mJankDataProcessor.mergeJankStats(appJankStats, mActivityName); + } + }); } public void setActivityName(@NonNull String activityName) { diff --git a/core/java/android/app/jank/StateTracker.java b/core/java/android/app/jank/StateTracker.java index c86d5a5cff20..21bb5e8280ee 100644 --- a/core/java/android/app/jank/StateTracker.java +++ b/core/java/android/app/jank/StateTracker.java @@ -180,7 +180,11 @@ public class StateTracker { } } - private String getStateKey(String widgetCategory, String widgetId, String widgetState) { + /** + * Returns a concatenated string of the inputs. This key can be used to retrieve both pending + * stats and the state that was used to create the pending stat. + */ + public String getStateKey(String widgetCategory, String widgetId, String widgetState) { return widgetCategory + widgetId + widgetState; } diff --git a/core/java/android/app/keyguard.aconfig b/core/java/android/app/keyguard.aconfig new file mode 100644 index 000000000000..9cd1c1579416 --- /dev/null +++ b/core/java/android/app/keyguard.aconfig @@ -0,0 +1,10 @@ +package: "android.app" +container: "system" + +flag { + namespace: "wallet_integration" + name: "device_unlock_listener" + is_exported: true + description: "Enable listener API for device unlock." + bug: "296195355" +}
\ No newline at end of file diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 2e3d5e15e037..8b6840c1b552 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -277,6 +277,13 @@ flag { } flag { + name: "nm_binder_perf_permission_check" + namespace: "systemui" + description: "Use PermissionManager for areNotificationsEnabled() instead of NMS" + bug: "362981561" +} + +flag { name: "no_sbnholder" namespace: "systemui" description: "removes sbnholder from NLS" diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig index 61b53f97fea1..359c84eeb559 100644 --- a/core/java/android/app/performance.aconfig +++ b/core/java/android/app/performance.aconfig @@ -35,3 +35,10 @@ flag { bug: "373752556" } +flag { + namespace: "system_performance" + name: "pic_cache_nulls" + is_fixed_read_only: true + description: "Cache null returns from binder calls" + bug: "372923336" +} diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index 730bb73da3bb..ffa54881cb9d 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -18,6 +18,7 @@ package android.app.trust; import android.app.trust.ITrustListener; import android.hardware.biometrics.BiometricSourceType; +import com.android.internal.policy.IDeviceLockedStateListener; /** * System private API to comunicate with trust service. @@ -43,4 +44,8 @@ interface ITrustManager { boolean isActiveUnlockRunning(int userId); @EnforcePermission("ACCESS_FINE_LOCATION") boolean isInSignificantPlace(); + @EnforcePermission("SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE") + void registerDeviceLockedStateListener(in IDeviceLockedStateListener listener, int deviceId); + @EnforcePermission("SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE") + void unregisterDeviceLockedStateListener(in IDeviceLockedStateListener listener); } diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 1ef83cdf3f85..8c8970ea5834 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -31,6 +31,8 @@ import android.os.Message; import android.os.RemoteException; import android.util.ArrayMap; +import com.android.internal.policy.IDeviceLockedStateListener; + import java.util.ArrayList; import java.util.List; @@ -259,6 +261,35 @@ public class TrustManager { } /** + * Registers a listener for device lock state events. + * + * Requires the {@link android.Manifest.permission#SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE} + * permission. + */ + public void registerDeviceLockedStateListener(final IDeviceLockedStateListener listener, + int deviceId) { + try { + mService.registerDeviceLockedStateListener(listener, deviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters a listener for device lock state events. + * + * Requires the {@link android.Manifest.permission#SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE} + * permission. + */ + public void unregisterDeviceLockedStateListener(final IDeviceLockedStateListener listener) { + try { + mService.unregisterDeviceLockedStateListener(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @return whether {@param userId} has enabled and configured trust agents. Ignores short-term * unavailability of trust due to {@link LockPatternUtils.StrongAuthTracker}. */ diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 6086f2455a31..19cd2e69cfdf 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -18,6 +18,7 @@ package android.content; import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS; +import static android.security.Flags.FLAG_SECURE_LOCKDOWN; import android.annotation.AttrRes; import android.annotation.CallbackExecutor; @@ -4256,6 +4257,7 @@ public abstract class Context { FINGERPRINT_SERVICE, //@hide: FACE_SERVICE, BIOMETRIC_SERVICE, + AUTHENTICATION_POLICY_SERVICE, MEDIA_ROUTER_SERVICE, TELEPHONY_SERVICE, TELEPHONY_SUBSCRIPTION_SERVICE, @@ -4437,6 +4439,9 @@ public abstract class Context { * web domain approval state. * <dt> {@link #DISPLAY_HASH_SERVICE} ("display_hash") * <dd> A {@link android.view.displayhash.DisplayHashManager} for management of display hashes. + * <dt> {@link #AUTHENTICATION_POLICY_SERVICE} ("authentication_policy") + * <dd> A {@link android.security.authenticationpolicy.AuthenticationPolicyManager} + * for managing authentication related policies on the device. * </dl> * * <p>Note: System services obtained via this API may be closely associated with @@ -4521,8 +4526,9 @@ public abstract class Context { * @see android.content.pm.verify.domain.DomainVerificationManager * @see #DISPLAY_HASH_SERVICE * @see android.view.displayhash.DisplayHashManager + * @see #AUTHENTICATION_POLICY_SERVICE + * @see android.security.authenticationpolicy.AuthenticationPolicyManager */ - // TODO(b/347269120): Re-add @Nullable public abstract Object getSystemService(@ServiceName @NonNull String name); /** @@ -4543,7 +4549,8 @@ public abstract class Context { * {@link android.os.BatteryManager}, {@link android.app.job.JobScheduler}, * {@link android.app.usage.NetworkStatsManager}, * {@link android.content.pm.verify.domain.DomainVerificationManager}, - * {@link android.view.displayhash.DisplayHashManager}. + * {@link android.view.displayhash.DisplayHashManager} + * {@link android.security.authenticationpolicy.AuthenticationPolicyManager}. * </p> * * <p> @@ -4568,7 +4575,6 @@ public abstract class Context { */ @SuppressWarnings("unchecked") @RavenwoodKeep - // TODO(b/347269120): Re-add @Nullable public final <T> T getSystemService(@NonNull Class<T> serviceClass) { // Because subclasses may override getSystemService(String) we cannot // perform a lookup by class alone. We must first map the class to its @@ -5183,6 +5189,18 @@ public abstract class Context { public static final String AUTH_SERVICE = "auth"; /** + * Use with {@link #getSystemService(String)} to retrieve an {@link + * android.security.authenticationpolicy.AuthenticationPolicyManager}. + * @see #getSystemService + * @see android.security.authenticationpolicy.AuthenticationPolicyManager + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public static final String AUTHENTICATION_POLICY_SERVICE = "authentication_policy"; + + /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.hardware.fingerprint.FingerprintManager} for handling management * of fingerprints. diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 23d17cb5ce50..413eb9886392 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -960,7 +960,6 @@ public class ContextWrapper extends Context { } @Override - // TODO(b/347269120): Re-add @Nullable public Object getSystemService(String name) { return mBase.getSystemService(name); } diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index ce52825ddb73..b10f5e4fe66c 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1320,23 +1320,23 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { 264301586L; // buganizer id /** - * Excludes the packages the override is applied to from the camera compatibility treatment - * in free-form windowing mode for fixed-orientation apps. + * Includes the packages the override is applied to in the camera compatibility treatment in + * free-form windowing mode for fixed-orientation apps. * * <p>In free-form windowing mode, the compatibility treatment emulates running on a portrait * device by letterboxing the app window and changing the camera characteristics to what apps * commonly expect in a portrait device: 90 and 270 degree sensor rotation for back and front * cameras, respectively, and setting display rotation to 0. * - * <p>Use this flag to disable the compatibility treatment for apps that do not respond well to - * the treatment. + * <p>Use this flag to enable the compatibility treatment for apps in which camera doesn't work + * well in freeform windowing. * * @hide */ @ChangeId @Overridable @Disabled - public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT = + public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT = 314961188L; /** diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 37295ac94823..5b305b466dd6 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3200,6 +3200,16 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device is capable of ranging with + * other devices using channel sounding via Bluetooth Low Energy radio. + */ + @FlaggedApi(com.android.ranging.flags.Flags.FLAG_RANGING_CS_ENABLED) + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING = + "android.hardware.bluetooth_le.channel_sounding"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device has a camera facing away * from the screen. */ diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index e9e8578af787..05c8f31df5d6 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -537,6 +537,9 @@ public class ApkLiteParseUtils { hasBindDeviceAdminPermission); break; case TAG_USES_SDK_LIBRARY: + if (!android.content.pm.Flags.sdkDependencyInstaller()) { + break; + } String usesSdkLibName = parser.getAttributeValue( ANDROID_RES_NAMESPACE, "name"); long usesSdkLibVersionMajor = parser.getAttributeIntValue( diff --git a/core/java/android/hardware/contexthub/HubDiscoveryInfo.java b/core/java/android/hardware/contexthub/HubDiscoveryInfo.java index 875c4b4182be..581040dbfa56 100644 --- a/core/java/android/hardware/contexthub/HubDiscoveryInfo.java +++ b/core/java/android/hardware/contexthub/HubDiscoveryInfo.java @@ -18,6 +18,7 @@ package android.hardware.contexthub; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.chre.flags.Flags; import android.hardware.location.ContextHubManager; @@ -26,31 +27,47 @@ import android.hardware.location.ContextHubManager; * Class that represents the result of from an hub endpoint discovery. * * <p>The type is returned from an endpoint discovery query via {@link - * ContextHubManager#findEndpoints}. Application may use the values {@link #getHubEndpointInfo} to - * retrieve the {@link HubEndpointInfo} that describes the endpoint that matches the query. The - * class provides flexibility in returning more information (e.g. service provided by the endpoint) - * in addition to the information about the endpoint. + * ContextHubManager#findEndpoints}. + * + * <p>Application may use the values {@link #getHubEndpointInfo} to retrieve the {@link + * HubEndpointInfo} that describes the endpoint that matches the query. + * + * <p>Application may use the values {@link #getHubServiceInfo()} to retrieve the {@link + * HubServiceInfo} that describes the service that matches the query. * * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_OFFLOAD_API) public class HubDiscoveryInfo { - // TODO(b/375487784): Add ServiceInfo to the result. - android.hardware.contexthub.HubEndpointInfo mEndpointInfo; + @NonNull private final HubEndpointInfo mEndpointInfo; + @Nullable private final HubServiceInfo mServiceInfo; - /** - * Constructor for internal use. - * - * @hide - */ - public HubDiscoveryInfo(android.hardware.contexthub.HubEndpointInfo endpointInfo) { + /** @hide */ + public HubDiscoveryInfo(@NonNull HubEndpointInfo endpointInfo) { mEndpointInfo = endpointInfo; + mServiceInfo = null; } - /** Get the {@link android.hardware.contexthub.HubEndpointInfo} for the endpoint found. */ + /** @hide */ + public HubDiscoveryInfo( + @NonNull HubEndpointInfo endpointInfo, @NonNull HubServiceInfo serviceInfo) { + mEndpointInfo = endpointInfo; + mServiceInfo = serviceInfo; + } + + /** Get the {@link HubEndpointInfo} for the endpoint found. */ @NonNull public HubEndpointInfo getHubEndpointInfo() { return mEndpointInfo; } + + /** + * Get the {@link HubServiceInfo} for the endpoint found. The value will be null if there is no + * service info specified in the query. + */ + @Nullable + public HubServiceInfo getHubServiceInfo() { + return mServiceInfo; + } } diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java index 823cc7ddbe24..078b4d4629e0 100644 --- a/core/java/android/hardware/contexthub/HubEndpoint.java +++ b/core/java/android/hardware/contexthub/HubEndpoint.java @@ -31,6 +31,10 @@ import android.util.SparseArray; import androidx.annotation.GuardedBy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Executor; /** @@ -57,7 +61,10 @@ public class HubEndpoint { private final IContextHubEndpointCallback mServiceCallback = new IContextHubEndpointCallback.Stub() { @Override - public void onSessionOpenRequest(int sessionId, HubEndpointInfo initiator) + public void onSessionOpenRequest( + int sessionId, + HubEndpointInfo initiator, + @Nullable HubServiceInfo serviceInfo) throws RemoteException { HubEndpointSession activeSession; synchronized (mLock) { @@ -78,20 +85,24 @@ public class HubEndpoint { processSessionOpenRequestResult( sessionId, initiator, + serviceInfo, mLifecycleCallback.onSessionOpenRequest( - initiator))); + initiator, serviceInfo))); } } private void processSessionOpenRequestResult( - int sessionId, HubEndpointInfo initiator, HubEndpointSessionResult result) { + int sessionId, + HubEndpointInfo initiator, + @Nullable HubServiceInfo serviceInfo, + HubEndpointSessionResult result) { if (result == null) { throw new IllegalArgumentException( "HubEndpointSessionResult shouldn't be null."); } if (result.isAccepted()) { - acceptSession(sessionId, initiator); + acceptSession(sessionId, initiator, serviceInfo); } else { Log.i( TAG, @@ -105,7 +116,10 @@ public class HubEndpoint { } } - private void acceptSession(int sessionId, HubEndpointInfo initiator) { + private void acceptSession( + int sessionId, + HubEndpointInfo initiator, + @Nullable HubServiceInfo serviceInfo) { if (mServiceToken == null || mAssignedHubEndpointInfo == null) { // No longer registered? return; @@ -129,7 +143,8 @@ public class HubEndpoint { sessionId, HubEndpoint.this, mAssignedHubEndpointInfo, - initiator); + initiator, + serviceInfo); try { // oneway call to notify system service that the request is completed mServiceToken.openSessionRequestComplete(sessionId); @@ -331,7 +346,7 @@ public class HubEndpoint { } /** @hide */ - public void openSession(HubEndpointInfo destinationInfo) { + public void openSession(HubEndpointInfo destinationInfo, @Nullable HubServiceInfo serviceInfo) { // TODO(b/378974199): Consider refactor these assertions if (mServiceToken == null || mAssignedHubEndpointInfo == null) { // No longer registered? @@ -341,7 +356,7 @@ public class HubEndpoint { HubEndpointSession newSession; try { // Request system service to assign session id. - int sessionId = mServiceToken.openSession(destinationInfo); + int sessionId = mServiceToken.openSession(destinationInfo, serviceInfo); // Save the newly created session synchronized (mLock) { @@ -350,7 +365,8 @@ public class HubEndpoint { sessionId, HubEndpoint.this, destinationInfo, - mAssignedHubEndpointInfo); + mAssignedHubEndpointInfo, + serviceInfo); mActiveSessions.put(sessionId, newSession); } } catch (RemoteException e) { @@ -407,11 +423,20 @@ public class HubEndpoint { } } + public int getVersion() { + return mPendingHubEndpointInfo.getVersion(); + } + @Nullable public String getTag() { return mPendingHubEndpointInfo.getTag(); } + @NonNull + public Collection<HubServiceInfo> getServiceInfoCollection() { + return mPendingHubEndpointInfo.getServiceInfoCollection(); + } + @Nullable public IHubEndpointLifecycleCallback getLifecycleCallback() { return mLifecycleCallback; @@ -433,16 +458,31 @@ public class HubEndpoint { @Nullable private IHubEndpointMessageCallback mMessageCallback; @NonNull private Executor mMessageCallbackExecutor; + private int mVersion; @Nullable private String mTag; + private List<HubServiceInfo> mServiceInfos = Collections.emptyList(); + /** Create a builder for {@link HubEndpoint} */ public Builder(@NonNull Context context) { mPackageName = context.getPackageName(); + mVersion = (int) context.getApplicationInfo().longVersionCode; mLifecycleCallbackExecutor = context.getMainExecutor(); mMessageCallbackExecutor = context.getMainExecutor(); } /** + * Set the version for the endpoint. Default is 0. + * + * @hide + */ + @NonNull + public Builder setVersion(int version) { + mVersion = version; + return this; + } + + /** * Set a tag string. The tag can be used to further identify the creator of the endpoint. * Endpoints created by the same package share the same name but should have different tags. */ @@ -493,11 +533,23 @@ public class HubEndpoint { return this; } + /** + * Add a service to the available services from this endpoint. The {@link HubServiceInfo} + * object can be built with {@link HubServiceInfo.Builder}. + */ + @NonNull + public Builder setServiceInfoCollection( + @NonNull Collection<HubServiceInfo> hubServiceInfos) { + // Make a copy first + mServiceInfos = new ArrayList<>(hubServiceInfos); + return this; + } + /** Build the {@link HubEndpoint} object. */ @NonNull public HubEndpoint build() { return new HubEndpoint( - new HubEndpointInfo(mPackageName, mTag), + new HubEndpointInfo(mPackageName, mVersion, mTag, mServiceInfos), mLifecycleCallback, mLifecycleCallbackExecutor, mMessageCallback, diff --git a/core/java/android/hardware/contexthub/HubEndpointInfo.java b/core/java/android/hardware/contexthub/HubEndpointInfo.java index ed8ff2929aff..b1d55239ac43 100644 --- a/core/java/android/hardware/contexthub/HubEndpointInfo.java +++ b/core/java/android/hardware/contexthub/HubEndpointInfo.java @@ -17,13 +17,22 @@ package android.hardware.contexthub; 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.chre.flags.Flags; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -102,39 +111,92 @@ public final class HubEndpointInfo implements Parcelable { } } + /** This endpoint is from the Android framework */ + public static final int TYPE_FRAMEWORK = 1; + + /** This endpoint is from an Android app */ + public static final int TYPE_APP = 2; + + /** This endpoint is from an Android native program. */ + public static final int TYPE_NATIVE = 3; + + /** This endpoint is from a nanoapp. */ + public static final int TYPE_NANOAPP = 4; + + /** This endpoint is a generic endpoint served by a hub (not from a nanoapp). */ + public static final int TYPE_HUB_ENDPOINT = 5; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TYPE_FRAMEWORK, + TYPE_APP, + TYPE_NATIVE, + TYPE_NANOAPP, + TYPE_HUB_ENDPOINT, + }) + public @interface EndpointType {} + private final HubEndpointIdentifier mId; + @EndpointType private final int mType; private final String mName; + private final int mVersion; @Nullable private final String mTag; - // TODO(b/375487784): Add Service/version and other information to this object + @NonNull private final List<String> mRequiredPermissions; + @NonNull private final List<HubServiceInfo> mHubServiceInfos; /** @hide */ public HubEndpointInfo(android.hardware.contexthub.EndpointInfo endpointInfo) { mId = new HubEndpointIdentifier(endpointInfo.id.hubId, endpointInfo.id.id); + mType = endpointInfo.type; mName = endpointInfo.name; + mVersion = endpointInfo.version; mTag = endpointInfo.tag; + mRequiredPermissions = Arrays.asList(endpointInfo.requiredPermissions); + mHubServiceInfos = new ArrayList<>(endpointInfo.services.length); + for (int i = 0; i < endpointInfo.services.length; i++) { + mHubServiceInfos.set(i, new HubServiceInfo(endpointInfo.services[i])); + } } /** @hide */ - public HubEndpointInfo(String name, @Nullable String tag) { + public HubEndpointInfo( + String name, + int version, + @Nullable String tag, + @NonNull List<HubServiceInfo> hubServiceInfos) { mId = HubEndpointIdentifier.invalid(); + mType = TYPE_APP; mName = name; + mVersion = version; mTag = tag; + mRequiredPermissions = Collections.emptyList(); + mHubServiceInfos = hubServiceInfos; } private HubEndpointInfo(Parcel in) { long hubId = in.readLong(); long endpointId = in.readLong(); + mId = new HubEndpointIdentifier(hubId, endpointId); + mType = in.readInt(); mName = in.readString(); + mVersion = in.readInt(); mTag = in.readString(); - - mId = new HubEndpointIdentifier(hubId, endpointId); + mRequiredPermissions = new ArrayList<>(); + in.readStringList(mRequiredPermissions); + mHubServiceInfos = new ArrayList<>(); + in.readTypedList(mHubServiceInfos, HubServiceInfo.CREATOR); } /** Parcel implementation details */ @Override public int describeContents() { - return 0; + int flags = 0; + for (HubServiceInfo serviceInfo : mHubServiceInfos) { + flags |= serviceInfo.describeContents(); + } + return flags; } /** Parcel implementation details */ @@ -142,8 +204,12 @@ public final class HubEndpointInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeLong(mId.getHub()); dest.writeLong(mId.getEndpoint()); + dest.writeInt(mType); dest.writeString(mName); + dest.writeInt(mVersion); dest.writeString(mTag); + dest.writeStringList(mRequiredPermissions); + dest.writeTypedList(mHubServiceInfos, flags); } /** Get a unique identifier for this endpoint. */ @@ -152,6 +218,18 @@ public final class HubEndpointInfo implements Parcelable { return mId; } + /** + * Get the type of this endpoint. Application may use this field to get more information about + * who registered this endpoint for diagnostic purposes. + * + * <p>Type can be one of {@link HubEndpointInfo#TYPE_APP}, {@link + * HubEndpointInfo#TYPE_FRAMEWORK}, {@link HubEndpointInfo#TYPE_NANOAPP}, {@link + * HubEndpointInfo#TYPE_NATIVE} or {@link HubEndpointInfo#TYPE_HUB_ENDPOINT}. + */ + public int getType() { + return mType; + } + /** Get the human-readable name of this endpoint (for debugging purposes). */ @NonNull public String getName() { @@ -159,6 +237,32 @@ public final class HubEndpointInfo implements Parcelable { } /** + * Get the version of this endpoint. + * + * <p>Monotonically increasing version number. The two sides of an endpoint session can use this + * version number to identify the other side and determine compatibility with each other. The + * interpretation of the version number is specific to the implementation of an endpoint. + * + * <p>The version number should not be used to compare endpoints implementation freshness for + * different endpoint types. + * + * <p>Depending on type of the endpoint, the following values (and formats) are used: + * + * <ol> + * <li>{@link #TYPE_FRAMEWORK}: android.os.Build.VERSION.SDK_INT_FULL + * <li>{@link #TYPE_APP}: versionCode + * <li>{@link #TYPE_NATIVE}: unspecified format (supplied by endpoint code) + * <li>{@link #TYPE_NANOAPP}: nanoapp version, typically following 0xMMmmpppp scheme where MM + * = major version, mm = minor version, pppp = patch version + * <li>{@link #TYPE_HUB_ENDPOINT}: unspecified format (supplied by endpoint code), following + * nanoapp versioning scheme is recommended + * </ol> + */ + public int getVersion() { + return mVersion; + } + + /** * Get the tag that further identifies the submodule that created this endpoint. For example, a * single application could provide multiple endpoints. These endpoints will share the same * name, but will have different tags. This tag can be used to identify the submodule within the @@ -169,6 +273,36 @@ public final class HubEndpointInfo implements Parcelable { return mTag; } + /** + * Get the list of required permissions in order to talk to this endpoint. + * + * <p>This list is enforced by the Context Hub Service. The app would need to have the required + * permissions list to open a session with this particular endpoint. Otherwise this will be + * rejected by as permission failures. + * + * <p>This is mostly for allowing app to check what permission it needs first internally. App + * will need to request permissions grant at runtime if not already granted. See {@link + * android.content.Context#checkPermission} for more details. + * + * <p>See {@link android.Manifest.permission} for a list of standard Android permissions as + * possible values. + */ + @SuppressLint("RequiresPermission") + @NonNull + public Collection<String> getRequiredPermissions() { + return Collections.unmodifiableList(mRequiredPermissions); + } + + /** + * Get the list of services provided by this endpoint. + * + * <p>See {@link HubServiceInfo} for more information. + */ + @NonNull + public Collection<HubServiceInfo> getServiceInfoCollection() { + return Collections.unmodifiableList(mHubServiceInfos); + } + @Override public String toString() { StringBuilder out = new StringBuilder(); diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java index 5d6e2b5643d8..cf952cbdbfdc 100644 --- a/core/java/android/hardware/contexthub/HubEndpointSession.java +++ b/core/java/android/hardware/contexthub/HubEndpointSession.java @@ -18,6 +18,7 @@ package android.hardware.contexthub; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.chre.flags.Flags; import android.hardware.location.ContextHubTransaction; @@ -42,6 +43,7 @@ public class HubEndpointSession implements AutoCloseable { @NonNull private final HubEndpoint mHubEndpoint; @NonNull private final HubEndpointInfo mInitiator; @NonNull private final HubEndpointInfo mDestination; + @Nullable private final HubServiceInfo mServiceInfo; private final AtomicBoolean mIsClosed = new AtomicBoolean(true); @@ -50,11 +52,13 @@ public class HubEndpointSession implements AutoCloseable { int id, @NonNull HubEndpoint hubEndpoint, @NonNull HubEndpointInfo destination, - @NonNull HubEndpointInfo initiator) { + @NonNull HubEndpointInfo initiator, + @Nullable HubServiceInfo serviceInfo) { mId = id; mHubEndpoint = hubEndpoint; mDestination = destination; mInitiator = initiator; + mServiceInfo = serviceInfo; } /** @@ -123,6 +127,21 @@ public class HubEndpointSession implements AutoCloseable { } } + /** + * Get the {@link HubServiceInfo} associated with this session. Null value indicates that there + * is no service associated to this session. + * + * <p>For hub initiated sessions, the object was previously used in as an argument for open + * request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. + * + * <p>For app initiated sessions, the object was previously used in an open request in {@link + * android.hardware.location.ContextHubManager#openSession} + */ + @Nullable + public HubServiceInfo getServiceInfo() { + return mServiceInfo; + } + @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.aidl b/core/java/android/hardware/contexthub/HubServiceInfo.aidl new file mode 100644 index 000000000000..98b1bbab8b60 --- /dev/null +++ b/core/java/android/hardware/contexthub/HubServiceInfo.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 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 android.hardware.contexthub; + +/** + * @hide + */ +parcelable HubServiceInfo; diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.java b/core/java/android/hardware/contexthub/HubServiceInfo.java new file mode 100644 index 000000000000..c7fe77c4a0f1 --- /dev/null +++ b/core/java/android/hardware/contexthub/HubServiceInfo.java @@ -0,0 +1,263 @@ +/* + * Copyright 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 android.hardware.contexthub; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.chre.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelableHolder; + +import androidx.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Objects; + +/** + * A class describing services provided by endpoints. + * + * <p>An endpoint can provide zero or more service. See {@link + * HubEndpoint.Builder#setServiceInfoCollection(Collection)} and {@link + * HubEndpointInfo#getServiceInfoCollection()}. + * + * <p>An endpoint session can be service-less or associated to one service.See {@link + * HubEndpointSession#getServiceInfo()}. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public final class HubServiceInfo implements Parcelable { + /** Customized format for messaging. Fully customized and opaque messaging format. */ + public static final int FORMAT_CUSTOM = 0; + + /** + * Binder-based messaging. The host endpoint is defining this service in Stable AIDL. Messages + * between endpoints that uses this service will be using the binder marhsalling format. + */ + public static final int FORMAT_AIDL = 1; + + /** + * Pigweed RPC messaging with Protobuf. This endpoint is a Pigweed RPC. Messages between + * endpoints will use Pigweed RPC marshalling format (protobuf). + */ + public static final int FORMAT_PW_RPC_PROTOBUF = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FORMAT_CUSTOM, + FORMAT_AIDL, + FORMAT_PW_RPC_PROTOBUF, + }) + public @interface ServiceFormat {} + + @NonNull private final String mServiceDescriptor; + + @ServiceFormat private final int mFormat; + private final int mMajorVersion; + private final int mMinorVersion; + + @NonNull private final ParcelableHolder mExtendedInfo; + + /** @hide */ + public HubServiceInfo(android.hardware.contexthub.Service service) { + mServiceDescriptor = service.serviceDescriptor; + mFormat = service.format; + mMajorVersion = service.majorVersion; + mMinorVersion = service.minorVersion; + mExtendedInfo = service.extendedInfo; + } + + private HubServiceInfo(Parcel in) { + mServiceDescriptor = Objects.requireNonNull(in.readString()); + mFormat = in.readInt(); + mMajorVersion = in.readInt(); + mMinorVersion = in.readInt(); + mExtendedInfo = ParcelableHolder.CREATOR.createFromParcel(in); + } + + public HubServiceInfo( + @NonNull String serviceDescriptor, + @ServiceFormat int format, + int majorVersion, + int minorVersion, + @NonNull ParcelableHolder extendedInfo) { + mServiceDescriptor = serviceDescriptor; + mFormat = format; + mMajorVersion = majorVersion; + mMinorVersion = minorVersion; + mExtendedInfo = extendedInfo; + } + + /** Get the unique identifier of this service. See {@link Builder} for more information. */ + @NonNull + public String getServiceDescriptor() { + return mServiceDescriptor; + } + + /** + * Get the type of the service. + * + * <p>The value can be one of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link + * HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}. + */ + public int getFormat() { + return mFormat; + } + + /** Get the major version of this service. */ + public int getMajorVersion() { + return mMajorVersion; + } + + /** Get the minor version of this service. */ + public int getMinorVersion() { + return mMinorVersion; + } + + /** Get the {@link ParcelableHolder} for the extended information about the service. */ + @NonNull + public ParcelableHolder getExtendedInfo() { + return mExtendedInfo; + } + + /** Parcel implementation details */ + @Override + public int describeContents() { + // Passthrough describeContents flags for mExtendedInfo because we don't have FD otherwise. + return mExtendedInfo.describeContents(); + } + + /** Parcel implementation details */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mServiceDescriptor); + dest.writeInt(mFormat); + dest.writeInt(mMajorVersion); + dest.writeInt(mMinorVersion); + mExtendedInfo.writeToParcel(dest, flags); + } + + /** Builder for a {@link HubServiceInfo} object. */ + public static final class Builder { + @NonNull private final String mServiceDescriptor; + + @ServiceFormat private final int mFormat; + private final int mMajorVersion; + private final int mMinorVersion; + + private final ParcelableHolder mExtendedInfo = + new ParcelableHolder(Parcelable.PARCELABLE_STABILITY_VINTF); + + /** + * Create a builder for {@link HubServiceInfo} with a service descriptor. + * + * <p>Service descriptor should uniquely identify the interface (scoped to type). Convention + * of the descriptor depend on interface type. + * + * <p>Examples: + * + * <ol> + * <li>AOSP-defined AIDL: android.hardware.something.IFoo/default + * <li>Vendor-defined AIDL: com.example.something.IBar/default + * <li>Pigweed RPC with Protobuf: com.example.proto.ExampleService + * </ol> + * + * @param serviceDescriptor The service descriptor. + * @param format One of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link + * HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}. + * @param majorVersion Breaking changes should be a major version bump. + * @param minorVersion Monotonically increasing minor version. + * @throws IllegalArgumentException if one or more fields are not valid. + */ + public Builder( + @NonNull String serviceDescriptor, + @ServiceFormat int format, + int majorVersion, + int minorVersion) { + if (format != FORMAT_CUSTOM + && format != FORMAT_AIDL + && format != FORMAT_PW_RPC_PROTOBUF) { + throw new IllegalArgumentException("Invalid format type."); + } + mFormat = format; + + if (majorVersion < 0) { + throw new IllegalArgumentException( + "Major version cannot be set to negative number."); + } + mMajorVersion = majorVersion; + + if (minorVersion < 0) { + throw new IllegalArgumentException( + "Minor version cannot be set to negative number."); + } + mMinorVersion = minorVersion; + + if (serviceDescriptor.isBlank()) { + throw new IllegalArgumentException("Invalid service descriptor."); + } + mServiceDescriptor = serviceDescriptor; + } + + /** + * Set the extended information of this service. + * + * @param extendedInfo Parcelable with extended information about this service. The + * parcelable needs to have at least VINTF stability. Null can be used to clear a + * previously set value. + * @throws android.os.BadParcelableException if the parcelable cannot be used. + */ + @NonNull + public Builder setExtendedInfo(@Nullable Parcelable extendedInfo) { + mExtendedInfo.setParcelable(extendedInfo); + return this; + } + + /** + * Build the {@link HubServiceInfo} object. + * + * @throws IllegalStateException if the Builder is missing required info. + */ + @NonNull + public HubServiceInfo build() { + if (mMajorVersion < 0 || mMinorVersion < 0) { + throw new IllegalStateException("Major and minor version must be set."); + } + return new HubServiceInfo( + mServiceDescriptor, mFormat, mMajorVersion, mMinorVersion, mExtendedInfo); + } + } + + /** Parcel implementation details */ + @NonNull + public static final Parcelable.Creator<HubServiceInfo> CREATOR = + new Parcelable.Creator<>() { + public HubServiceInfo createFromParcel(Parcel in) { + return new HubServiceInfo(in); + } + + public HubServiceInfo[] newArray(int size) { + return new HubServiceInfo[size]; + } + }; +} diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl index a67b8de42993..1c98b4b3f4f5 100644 --- a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl +++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl @@ -18,6 +18,7 @@ package android.hardware.contexthub; import android.hardware.contexthub.HubEndpointInfo; import android.hardware.contexthub.HubMessage; +import android.hardware.contexthub.HubServiceInfo; import android.hardware.location.IContextHubTransactionCallback; /** @@ -37,7 +38,7 @@ interface IContextHubEndpoint { * @throws IllegalArgumentException If the HubEndpointInfo is not valid. * @throws IllegalStateException If there are too many opened sessions. */ - int openSession(in HubEndpointInfo destination); + int openSession(in HubEndpointInfo destination, in @nullable HubServiceInfo serviceInfo); /** * Request system service to close a specific session diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl index 7f5c6017c452..1ae5fb9d28c1 100644 --- a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl +++ b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl @@ -18,6 +18,7 @@ package android.hardware.contexthub; import android.hardware.contexthub.HubEndpointInfo; import android.hardware.contexthub.HubMessage; +import android.hardware.contexthub.HubServiceInfo; /** * @hide @@ -28,8 +29,9 @@ oneway interface IContextHubEndpointCallback { * * @param sessionId An integer identifying the session, assigned by the initiator * @param initiator HubEndpointInfo representing the requester + * @param serviceInfo Nullable HubServiceInfo representing the service associated with this session */ - void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator); + void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable HubServiceInfo serviceInfo); /** * Request from system service to close a specific session diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java index 5bd3c0ea23dc..46884393b49b 100644 --- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java +++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java @@ -19,6 +19,7 @@ package android.hardware.contexthub; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.chre.flags.Flags; @@ -55,9 +56,12 @@ public interface IHubEndpointLifecycleCallback { * Called when an endpoint is requesting a session be opened with another endpoint. * * @param requester The {@link HubEndpointInfo} object representing the requester + * @param serviceInfo The {@link HubServiceInfo} object representing the service associated with + * this session. Null indicates that there is no service associated with this session. */ @NonNull - HubEndpointSessionResult onSessionOpenRequest(@NonNull HubEndpointInfo requester); + HubEndpointSessionResult onSessionOpenRequest( + @NonNull HubEndpointInfo requester, @Nullable HubServiceInfo serviceInfo); /** * Called when a communication session is opened and ready to be used. diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 399184cfaecb..68b6cfc012fc 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -470,6 +470,31 @@ public abstract class DisplayManagerInternal { */ public abstract boolean isDisplayReadyForMirroring(int displayId); + + /** + * Used by the window manager to override the per-display screen brightness based on the + * current foreground activity. + * + * The key of the array is the displayId. If a displayId is missing from the array, this is + * equivalent to clearing any existing brightness overrides for that display. + * + * This method must only be called by the window manager. + */ + public abstract void setScreenBrightnessOverrideFromWindowManager( + SparseArray<DisplayBrightnessOverrideRequest> brightnessOverrides); + + /** + * Describes a request for overriding the brightness of a single display. + */ + public static class DisplayBrightnessOverrideRequest { + // An override of the screen brightness. + // Set to PowerManager.BRIGHTNESS_INVALID if there's no override. + public float brightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + + // Tag used to identify the app window requesting the brightness override. + public CharSequence tag; + } + /** * Describes the requested power state of the display. * @@ -505,11 +530,11 @@ public abstract class DisplayManagerInternal { // nearby, turning it off temporarily until the object is moved away. public boolean useProximitySensor; - // An override of the screen brightness. + // A global override of the screen brightness, applied to all displays. // Set to PowerManager.BRIGHTNESS_INVALID if there's no override. public float screenBrightnessOverride; - // Tag used to identify the app window requesting the brightness override. + // Tag used to identify the reason for the global brightness override. public CharSequence screenBrightnessOverrideTag; // An override of the screen auto-brightness adjustment factor in the range -1 (dimmer) to diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 3284761eb273..ed510e467f82 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -281,4 +281,6 @@ interface IInputManager { AidlInputGestureData[] getCustomInputGestures(int userId, int tag); AidlInputGestureData[] getAppLaunchBookmarks(); + + void resetLockedModifierState(); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index f8241925dff0..10224c1be788 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -260,7 +260,7 @@ public final class InputManager { } /** - * Custom input gesture error: Input gesture already exists + * Custom input gesture result success * * @hide */ @@ -1590,6 +1590,21 @@ public final class InputManager { } /** + * Resets locked modifier state (i.e.. Caps Lock, Num Lock, Scroll Lock state) + * + * @hide + */ + @TestApi + @SuppressLint("UnflaggedApi") // @TestApi without associated feature. + public void resetLockedModifierState() { + try { + mIm.resetLockedModifierState(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * A callback used to be notified about battery state changes for an input device. The * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the * listener is successfully registered to provide the initial battery state of the device. diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index b2c3bb89d863..426cd69f76a0 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -37,6 +37,7 @@ import android.hardware.contexthub.ErrorCode; import android.hardware.contexthub.HubDiscoveryInfo; import android.hardware.contexthub.HubEndpoint; import android.hardware.contexthub.HubEndpointInfo; +import android.hardware.contexthub.HubServiceInfo; import android.hardware.contexthub.IHubEndpointLifecycleCallback; import android.os.Handler; import android.os.HandlerExecutor; @@ -693,6 +694,8 @@ public final class ContextHubManager { @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public List<HubDiscoveryInfo> findEndpoints(long endpointId) { + // TODO(b/379323274): Consider improving these getters to avoid racing with nano app load + // timing. try { List<HubEndpointInfo> endpointInfos = mService.findEndpoints(endpointId); List<HubDiscoveryInfo> results = new ArrayList<>(endpointInfos.size()); @@ -707,6 +710,40 @@ public final class ContextHubManager { } /** + * Find a list of endpoints that provides a specific service. + * + * @param serviceDescriptor Statically generated ID for an endpoint. + * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery. + * @throws IllegalArgumentException if the serviceDescriptor is empty/null. + */ + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @NonNull + public List<HubDiscoveryInfo> findEndpoints(@NonNull String serviceDescriptor) { + // TODO(b/379323274): Consider improving these getters to avoid racing with nano app load + // timing. + if (serviceDescriptor.isBlank()) { + throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor); + } + try { + List<HubEndpointInfo> endpointInfos = + mService.findEndpointsWithService(serviceDescriptor); + List<HubDiscoveryInfo> results = new ArrayList<>(endpointInfos.size()); + // Wrap with result type + for (HubEndpointInfo endpointInfo : endpointInfos) { + for (HubServiceInfo serviceInfo : endpointInfo.getServiceInfoCollection()) { + if (serviceInfo.getServiceDescriptor().equals(serviceDescriptor)) { + results.add(new HubDiscoveryInfo(endpointInfo, serviceInfo)); + } + } + } + return results; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Set a callback to receive messages from the context hub * * @param callback Callback object @@ -1052,11 +1089,12 @@ public final class ContextHubManager { } /** - * Use a registered endpoint to connect to another endpoint (destination). + * Use a registered endpoint to connect to another endpoint (destination) without specifying a + * service. * * <p>Context Hub Service will create the endpoint session and notify the registered endpoint. * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback} - * object regarding the lifecycle events of the session + * object regarding the lifecycle events of the session. * * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link * ContextHubManager#registerEndpoint(HubEndpoint)}. @@ -1067,7 +1105,31 @@ public final class ContextHubManager { @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void openSession( @NonNull HubEndpoint hubEndpoint, @NonNull HubEndpointInfo destination) { - hubEndpoint.openSession(destination); + hubEndpoint.openSession(destination, null); + } + + /** + * Use a registered endpoint to connect to another endpoint (destination) for a service + * described by a {@link HubServiceInfo} object. + * + * <p>Context Hub Service will create the endpoint session and notify the registered endpoint. + * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback} + * object regarding the lifecycle events of the session. + * + * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link + * ContextHubManager#registerEndpoint(HubEndpoint)}. + * @param destination {@link HubEndpointInfo} object that represents an endpoint from previous + * endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}). + * @param serviceInfo {@link HubServiceInfo} object that describes the service associated with + * this session. The information will be sent to the destination as part of open request. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + public void openSession( + @NonNull HubEndpoint hubEndpoint, + @NonNull HubEndpointInfo destination, + @NonNull HubServiceInfo serviceInfo) { + hubEndpoint.openSession(destination, serviceInfo); } /** diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl index 512872303291..f9f412446038 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -126,10 +126,14 @@ interface IContextHubService { @EnforcePermission("ACCESS_CONTEXT_HUB") boolean setTestMode(in boolean enable); - // Finds all endpoints that havea specific ID + // Finds all endpoints that has a specific ID @EnforcePermission("ACCESS_CONTEXT_HUB") List<HubEndpointInfo> findEndpoints(long endpointId); + // Finds all endpoints that has a specific service + @EnforcePermission("ACCESS_CONTEXT_HUB") + List<HubEndpointInfo> findEndpointsWithService(String service); + // Register an endpoint with the context hub @EnforcePermission("ACCESS_CONTEXT_HUB") IContextHubEndpoint registerEndpoint(in HubEndpointInfo pendingEndpointInfo, in IContextHubEndpointCallback callback); diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 3b5a99ed089a..01222cdd38b3 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -36,6 +36,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -651,28 +652,39 @@ public final class BinderProxy implements IBinder { private native boolean unlinkToDeathNative(DeathRecipient recipient, int flags); /** - * This list is to hold strong reference to the frozen state callbacks. The callbacks are only - * weakly referenced by JNI so the strong references here are needed to keep the callbacks - * around until the proxy is GC'ed. + * This map is to hold strong reference to the frozen state callbacks. + * + * The callbacks are only weakly referenced by JNI so the strong references here are needed to + * keep the callbacks around until the proxy is GC'ed. + * + * The key is the original callback passed into {@link #addFrozenStateChangeCallback}. The value + * is the wrapped callback created in {@link #addFrozenStateChangeCallback} to dispatch the + * calls on the desired executor. */ - private List<FrozenStateChangeCallback> mFrozenStateChangeCallbacks = - Collections.synchronizedList(new ArrayList<>()); + private Map<FrozenStateChangeCallback, FrozenStateChangeCallback> mFrozenStateChangeCallbacks = + Collections.synchronizedMap(new HashMap<>()); /** * See {@link IBinder#addFrozenStateChangeCallback(FrozenStateChangeCallback)} */ - public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback) + public void addFrozenStateChangeCallback(Executor executor, FrozenStateChangeCallback callback) throws RemoteException { - addFrozenStateChangeCallbackNative(callback); - mFrozenStateChangeCallbacks.add(callback); + FrozenStateChangeCallback wrappedCallback = (who, state) -> + executor.execute(() -> callback.onFrozenStateChanged(who, state)); + addFrozenStateChangeCallbackNative(wrappedCallback); + mFrozenStateChangeCallbacks.put(callback, wrappedCallback); } /** * See {@link IBinder#removeFrozenStateChangeCallback} */ - public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) { - mFrozenStateChangeCallbacks.remove(callback); - return removeFrozenStateChangeCallbackNative(callback); + public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) + throws IllegalArgumentException { + FrozenStateChangeCallback wrappedCallback = mFrozenStateChangeCallbacks.remove(callback); + if (wrappedCallback == null) { + throw new IllegalArgumentException("callback not found"); + } + return removeFrozenStateChangeCallbackNative(wrappedCallback); } private native void addFrozenStateChangeCallbackNative(FrozenStateChangeCallback callback) diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 036ccd84a600..4c9f08d80d4b 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -123,11 +123,6 @@ public final class MessageQueue { // We can lift this restriction in the future after we've made it possible for test authors // to test Looper and MessageQueue without resorting to reflection. - // Holdback study. - if (mUseConcurrent && Flags.messageQueueForceLegacy()) { - mUseConcurrent = false; - } - mQuitAllowed = quitAllowed; mPtr = nativeInit(); } diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index a997f4c86704..8cfd32449537 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -25,6 +26,7 @@ import android.compat.annotation.UnsupportedAppUsage; import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * Base interface for a remotable object, the core part of a lightweight @@ -397,12 +399,31 @@ public interface IBinder { @interface State { } + /** + * Represents the frozen state of the remote process. + * + * While in this state, the remote process won't be able to receive and handle a + * transaction. Therefore, any asynchronous transactions will be buffered and delivered when + * the process is unfrozen, and any synchronous transactions will result in an error. + * + * Buffered transactions may be stale by the time that the process is unfrozen and handles + * them. To avoid overwhelming the remote process with stale events or overflowing their + * buffers, it's best to avoid sending binder transactions to a frozen process. + */ int STATE_FROZEN = 0; + + /** + * Represents the unfrozen state of the remote process. + * + * In this state, the process hosting the object can execute and is not restricted + * by the freezer from using the CPU or responding to binder transactions. + */ int STATE_UNFROZEN = 1; /** * Interface for receiving a callback when the process hosting an IBinder * has changed its frozen state. + * * @param who The IBinder whose hosting process has changed state. * @param state The latest state. */ @@ -427,16 +448,32 @@ public interface IBinder { * <p>You will only receive state change notifications for remote binders, as local binders by * definition can't be frozen without you being frozen too.</p> * + * @param executor The executor on which to run the callback. + * @param callback The callback used to deliver state change notifications. + * * <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support * this feature. */ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - default void addFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) + default void addFrozenStateChangeCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull FrozenStateChangeCallback callback) throws RemoteException { throw new UnsupportedOperationException(); } /** + * Same as {@link #addFrozenStateChangeCallback(Executor, FrozenStateChangeCallback)} except + * that callbacks are invoked on a binder thread. + * + * @hide + */ + default void addFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback) + throws RemoteException { + addFrozenStateChangeCallback(Runnable::run, callback); + } + + /** * Unregister a {@link FrozenStateChangeCallback}. The callback will no longer be invoked when * the hosting process changes its frozen state. */ diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 29ccb85c0bab..9435f4d59a2c 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -98,18 +98,6 @@ public abstract class PowerManagerInternal { } /** - * Used by the window manager to override the screen brightness based on the - * current foreground activity. - * - * This method must only be called by the window manager. - * - * @param brightness The overridden brightness, or Float.NaN to disable the override. - * @param tag Source identifier of the app window that requests the override. - */ - public abstract void setScreenBrightnessOverrideFromWindowManager( - float brightness, CharSequence tag); - - /** * Used by the window manager to override the user activity timeout based on the * current foreground activity. It can only be used to make the timeout shorter * than usual, not longer. diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index 91c482faf7d7..d5630fd46eb4 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -29,6 +30,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -134,6 +136,7 @@ public class RemoteCallbackList<E extends IInterface> { private final @FrozenCalleePolicy int mFrozenCalleePolicy; private final int mMaxQueueSize; + private final Executor mExecutor; private final class Interface implements IBinder.DeathRecipient, IBinder.FrozenStateChangeCallback { @@ -197,7 +200,7 @@ public class RemoteCallbackList<E extends IInterface> { void maybeSubscribeToFrozenCallback() throws RemoteException { if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { try { - mBinder.addFrozenStateChangeCallback(this); + mBinder.addFrozenStateChangeCallback(mExecutor, this); } catch (UnsupportedOperationException e) { // The kernel does not support frozen notifications. In this case we want to // silently fall back to FROZEN_CALLEE_POLICY_UNSET. This is done by simply @@ -237,6 +240,7 @@ public class RemoteCallbackList<E extends IInterface> { private @FrozenCalleePolicy int mFrozenCalleePolicy; private int mMaxQueueSize = DEFAULT_MAX_QUEUE_SIZE; private InterfaceDiedCallback mInterfaceDiedCallback; + private Executor mExecutor; /** * Creates a Builder for {@link RemoteCallbackList}. @@ -285,6 +289,18 @@ public class RemoteCallbackList<E extends IInterface> { } /** + * Sets the executor to be used when invoking callbacks asynchronously. + * + * This is only used when callbacks need to be invoked asynchronously, e.g. when the process + * hosting a callback becomes unfrozen. Callbacks that can be invoked immediately run on the + * same thread that calls {@link #broadcast} synchronously. + */ + public @NonNull Builder setExecutor(@NonNull @CallbackExecutor Executor executor) { + mExecutor = executor; + return this; + } + + /** * For notifying when the process hosting a callback interface has died. * * @param <E> The remote callback interface type. @@ -308,15 +324,21 @@ public class RemoteCallbackList<E extends IInterface> { * @return The built {@link RemoteCallbackList} object. */ public @NonNull RemoteCallbackList<E> build() { + Executor executor = mExecutor; + if (executor == null && mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { + // TODO Throw an exception here once the existing API caller is updated to provide + // an executor. + executor = new HandlerExecutor(Handler.getMain()); + } if (mInterfaceDiedCallback != null) { - return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize) { + return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize, executor) { @Override public void onCallbackDied(E deadInterface, Object cookie) { mInterfaceDiedCallback.onInterfaceDied(this, deadInterface, cookie); } }; } - return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize); + return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize, executor); } } @@ -341,13 +363,23 @@ public class RemoteCallbackList<E extends IInterface> { } /** + * Returns the executor used when invoking callbacks asynchronously. + * + * @return The executor. + */ + @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) + public @Nullable Executor getExecutor() { + return mExecutor; + } + + /** * Creates a RemoteCallbackList with {@link #FROZEN_CALLEE_POLICY_UNSET}. This is equivalent to * <pre> * new RemoteCallbackList.Build(RemoteCallbackList.FROZEN_CALLEE_POLICY_UNSET).build() * </pre> */ public RemoteCallbackList() { - this(FROZEN_CALLEE_POLICY_UNSET, DEFAULT_MAX_QUEUE_SIZE); + this(FROZEN_CALLEE_POLICY_UNSET, DEFAULT_MAX_QUEUE_SIZE, null); } /** @@ -362,10 +394,14 @@ public class RemoteCallbackList<E extends IInterface> { * recipient's process is frozen. Once the limit is reached, the oldest callbacks would be * dropped to keep the size under limit. Ignored except for * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}. + * + * @param executor The executor used when invoking callbacks asynchronously. */ - private RemoteCallbackList(@FrozenCalleePolicy int frozenCalleePolicy, int maxQueueSize) { + private RemoteCallbackList(@FrozenCalleePolicy int frozenCalleePolicy, int maxQueueSize, + @CallbackExecutor Executor executor) { mFrozenCalleePolicy = frozenCalleePolicy; mMaxQueueSize = maxQueueSize; + mExecutor = executor; } /** diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 9b1bf057b815..2ef8764a5221 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -4,15 +4,6 @@ container: "system" # keep-sorted start block=yes newline_separated=yes flag { - # Holdback study for concurrent MessageQueue. - # Do not promote beyond trunkfood. - namespace: "system_performance" - name: "message_queue_force_legacy" - description: "Whether to holdback concurrent MessageQueue (force legacy)." - bug: "336880969" -} - -flag { name: "adpf_gpu_report_actual_work_duration" is_exported: true namespace: "game" diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 0a35fe399531..b5a822b715d5 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -383,7 +383,7 @@ flag { bug: "363318732" } -flag{ +flag { name: "note_op_batching_enabled" is_fixed_read_only: true is_exported: true @@ -409,3 +409,12 @@ flag { description: "This flag is used to short circuit the request for permananently denied permissions" bug: "378923900" } + +flag { + name: "check_op_overload_api_enabled" + is_exported: true + is_fixed_read_only: true + namespace: "permissions" + description: "Add new checkOp APIs that accept attributionTag" + bug: "240617242" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d2a20b65c1ee..a35c9c1cd4ec 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6395,27 +6395,6 @@ public final class Settings { public static final String SCREEN_FLASH_NOTIFICATION_COLOR = "screen_flash_notification_color_global"; - - /** - * A semi-colon separated list of Bluetooth hearing devices' local ambient volume. - * Each entry is encoded as a key=value list, separated by commas. Ex: - * - * "addr=XX:XX:XX:00:11,ambient=20,group_ambient=30;addr=XX:XX:XX:00:22,ambient=50" - * - * The following keys are supported: - * <pre> - * addr (String) - * ambient (int) - * group_ambient (int) - * control_expanded (boolean) - * </pre> - * - * Each entry must contains "addr" attribute, otherwise it'll be ignored. - * @hide - */ - public static final String HEARING_DEVICE_LOCAL_AMBIENT_VOLUME = - "hearing_device_local_ambient_volume"; - /** * IMPORTANT: If you add a new public settings you also have to add it to * PUBLIC_SETTINGS below. If the new setting is hidden you have to add @@ -6560,7 +6539,6 @@ public final class Settings { PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE); PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING); PRIVATE_SETTINGS.add(MOUSE_SWAP_PRIMARY_BUTTON); - PRIVATE_SETTINGS.add(HEARING_DEVICE_LOCAL_AMBIENT_VOLUME); } /** @@ -18138,6 +18116,44 @@ public final class Settings { public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side"; /** + * A semi-colon separated list of Bluetooth hearing devices' local ambient volume data. + * Each entry is encoded as a key=value list, separated by commas. Ex: + * + * "addr=XX:XX:XX:00:11,ambient=20,group_ambient=30;addr=XX:XX:XX:00:22,ambient=50" + * + * The following keys are supported: + * <pre> + * addr (String) + * ambient (int) + * group_ambient (int) + * control_expanded (boolean) + * </pre> + * + * Each entry must contains "addr" attribute, otherwise it'll be ignored. + * @hide + */ + public static final String HEARING_DEVICE_LOCAL_AMBIENT_VOLUME = + "hearing_device_local_ambient_volume"; + + /** + * A semi-colon separated list of Bluetooth hearing devices' notification data. + * Each entry is encoded as a key=value list, separated by commas. Ex: + * + * "addr=XX:XX:XX:00:11,input_changes=1" + * + * The following keys are supported: + * <pre> + * addr (String) + * input_changes (boolean) + * </pre> + * + * Each entry must contains "addr" attribute, otherwise it'll be ignored. + * @hide + */ + public static final String HEARING_DEVICE_LOCAL_NOTIFICATION = + "hearing_device_local_notification"; + + /** * Global settings that shouldn't be persisted. * * @hide diff --git a/core/java/android/security/authenticationpolicy/AuthenticationPolicyManager.java b/core/java/android/security/authenticationpolicy/AuthenticationPolicyManager.java new file mode 100644 index 000000000000..75abd5fa4bb0 --- /dev/null +++ b/core/java/android/security/authenticationpolicy/AuthenticationPolicyManager.java @@ -0,0 +1,237 @@ +/* + * 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 android.security.authenticationpolicy; + +import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE; +import static android.security.Flags.FLAG_SECURE_LOCKDOWN; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * AuthenticationPolicyManager is a centralized interface for managing authentication related + * policies on the device. This includes device locking capabilities to protect users in "at risk" + * environments. + * + * AuthenticationPolicyManager is designed to protect Android users by integrating with apps and + * key system components, such as the lock screen. It is not related to enterprise control surfaces + * and does not offer additional administrative controls. + * + * <p> + * To use this class, call {@link #enableSecureLockDevice} to enable secure lock on the device. + * This will require the caller to have the + * {@link android.Manifest.permission#MANAGE_SECURE_LOCK_DEVICE} permission. + * + * <p> + * To disable secure lock on the device, call {@link #disableSecureLockDevice}. This will require + * the caller to have the {@link android.Manifest.permission#MANAGE_SECURE_LOCK_DEVICE} permission. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_SECURE_LOCKDOWN) +@SystemService(Context.AUTHENTICATION_POLICY_SERVICE) +public final class AuthenticationPolicyManager { + private static final String TAG = "AuthenticationPolicyManager"; + + @NonNull private final IAuthenticationPolicyService mAuthenticationPolicyService; + @NonNull private final Context mContext; + + /** + * Error result code for {@link #enableSecureLockDevice} and {@link + * #disableSecureLockDevice}. + * + * Secure lock device request status unknown. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public static final int ERROR_UNKNOWN = 0; + + /** + * Success result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}. + * + * Secure lock device request successful. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public static final int SUCCESS = 1; + + /** + * Error result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}. + * + * Secure lock device is unsupported. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public static final int ERROR_UNSUPPORTED = 2; + + + /** + * Error result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}. + * + * Invalid secure lock device request params provided. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public static final int ERROR_INVALID_PARAMS = 3; + + + /** + * Error result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}. + * + * Secure lock device is unavailable because there are no biometrics enrolled on the device. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public static final int ERROR_NO_BIOMETRICS_ENROLLED = 4; + + /** + * Error result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}. + * + * Secure lock device is unavailable because the device has no biometric hardware or the + * biometric sensors do not meet + * {@link android.hardware.biometrics.BiometricManager.Authenticators#BIOMETRIC_STRONG} + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public static final int ERROR_INSUFFICIENT_BIOMETRICS = 5; + + /** + * Error result code for {@link #enableSecureLockDevice}. + * + * Secure lock is already enabled. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public static final int ERROR_ALREADY_ENABLED = 6; + + /** + * Communicates the current status of a request to enable secure lock on the device. + * + * @hide + */ + @IntDef(prefix = {"ENABLE_SECURE_LOCK_DEVICE_STATUS_"}, value = { + ERROR_UNKNOWN, + SUCCESS, + ERROR_UNSUPPORTED, + ERROR_INVALID_PARAMS, + ERROR_NO_BIOMETRICS_ENROLLED, + ERROR_INSUFFICIENT_BIOMETRICS, + ERROR_ALREADY_ENABLED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EnableSecureLockDeviceRequestStatus {} + + /** + * Communicates the current status of a request to disable secure lock on the device. + * + * @hide + */ + @IntDef(prefix = {"DISABLE_SECURE_LOCK_DEVICE_STATUS_"}, value = { + ERROR_UNKNOWN, + SUCCESS, + ERROR_UNSUPPORTED, + ERROR_INVALID_PARAMS, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DisableSecureLockDeviceRequestStatus {} + + /** @hide */ + public AuthenticationPolicyManager(@NonNull Context context, + @NonNull IAuthenticationPolicyService authenticationPolicyService) { + mContext = context; + mAuthenticationPolicyService = authenticationPolicyService; + } + + /** + * Called by a privileged component to remotely enable secure lock on the device. + * + * Secure lock is an enhanced security state that restricts access to sensitive data (app + * notifications, widgets, quick settings, assistant, etc) and requires multi-factor + * authentication for device entry, such as + * {@link android.hardware.biometrics.BiometricManager.Authenticators#DEVICE_CREDENTIAL} and + * {@link android.hardware.biometrics.BiometricManager.Authenticators#BIOMETRIC_STRONG}. + * + * If secure lock is already enabled when this method is called, it will return + * {@link ERROR_ALREADY_ENABLED}. + * + * @param params EnableSecureLockDeviceParams for caller to supply params related to the secure + * lock device request + * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the secure lock + * device request + * + * @hide + */ + @EnableSecureLockDeviceRequestStatus + @RequiresPermission(MANAGE_SECURE_LOCK_DEVICE) + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public int enableSecureLockDevice(@NonNull EnableSecureLockDeviceParams params) { + try { + return mAuthenticationPolicyService.enableSecureLockDevice(params); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Called by a privileged component to disable secure lock on the device. + * + * If secure lock is already disabled when this method is called, it will return + * {@link SUCCESS}. + * + * @param params @DisableSecureLockDeviceParams for caller to supply params related to the + * secure lock device request + * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the secure lock + * device request + * + * @hide + */ + @DisableSecureLockDeviceRequestStatus + @RequiresPermission(MANAGE_SECURE_LOCK_DEVICE) + @SystemApi + @FlaggedApi(FLAG_SECURE_LOCKDOWN) + public int disableSecureLockDevice(@NonNull DisableSecureLockDeviceParams params) { + try { + return mAuthenticationPolicyService.disableSecureLockDevice(params); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.aidl b/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.aidl new file mode 100644 index 000000000000..81f7726a500c --- /dev/null +++ b/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.aidl @@ -0,0 +1,21 @@ +/* + * 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 android.security.authenticationpolicy; + +/** + * @hide + */ +parcelable DisableSecureLockDeviceParams;
\ No newline at end of file diff --git a/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.java b/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.java new file mode 100644 index 000000000000..64a3f0f60f96 --- /dev/null +++ b/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.java @@ -0,0 +1,82 @@ +/* + * 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 android.security.authenticationpolicy; + +import static android.security.Flags.FLAG_SECURE_LOCKDOWN; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Parameters related to a request to disable secure lock on the device. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_SECURE_LOCKDOWN) +public final class DisableSecureLockDeviceParams implements Parcelable { + + /** + * Client message associated with the request to disable secure lock on the device. This message + * will be shown on the device when secure lock mode is disabled. + */ + private final @NonNull String mMessage; + + /** + * Creates DisableSecureLockDeviceParams with the given params. + * + * @param message Allows clients to pass in a message with information about the request to + * disable secure lock on the device. This message will be shown to the user when + * secure lock mode is disabled. If an empty string is provided, it will default + * to a system-defined string (e.g. "Secure lock mode has been disabled.") + */ + public DisableSecureLockDeviceParams(@NonNull String message) { + mMessage = message; + } + + private DisableSecureLockDeviceParams(@NonNull Parcel in) { + mMessage = Objects.requireNonNull(in.readString8()); + } + + public static final @NonNull Creator<DisableSecureLockDeviceParams> CREATOR = + new Creator<DisableSecureLockDeviceParams>() { + @Override + public DisableSecureLockDeviceParams createFromParcel(Parcel in) { + return new DisableSecureLockDeviceParams(in); + } + + @Override + public DisableSecureLockDeviceParams[] newArray(int size) { + return new DisableSecureLockDeviceParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mMessage); + } +} diff --git a/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.aidl b/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.aidl new file mode 100644 index 000000000000..9e496f82ec69 --- /dev/null +++ b/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.aidl @@ -0,0 +1,21 @@ +/* + * 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 android.security.authenticationpolicy; + +/** + * @hide + */ +parcelable EnableSecureLockDeviceParams;
\ No newline at end of file diff --git a/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.java b/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.java new file mode 100644 index 000000000000..1d727727ce37 --- /dev/null +++ b/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.java @@ -0,0 +1,82 @@ +/* + * 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 android.security.authenticationpolicy; + +import static android.security.Flags.FLAG_SECURE_LOCKDOWN; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + + +/** + * Parameters related to a request to enable secure lock on the device. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_SECURE_LOCKDOWN) +public final class EnableSecureLockDeviceParams implements Parcelable { + + /** + * Client message associated with the request to enable secure lock on the device. This message + * will be shown on the device when secure lock mode is enabled. + */ + private final @NonNull String mMessage; + + /** + * Creates EnableSecureLockDeviceParams with the given params. + * + * @param message Allows clients to pass in a message with information about the request to + * enable secure lock on the device. This message will be shown to the user when + * secure lock mode is enabled. If an empty string is provided, it will default + * to a system-defined string (e.g. "Device is securely locked remotely.") + */ + public EnableSecureLockDeviceParams(@NonNull String message) { + mMessage = message; + } + + private EnableSecureLockDeviceParams(@NonNull Parcel in) { + mMessage = Objects.requireNonNull(in.readString8()); + } + + public static final @NonNull Creator<EnableSecureLockDeviceParams> CREATOR = + new Creator<EnableSecureLockDeviceParams>() { + @Override + public EnableSecureLockDeviceParams createFromParcel(Parcel in) { + return new EnableSecureLockDeviceParams(in); + } + + @Override + public EnableSecureLockDeviceParams[] newArray(int size) { + return new EnableSecureLockDeviceParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mMessage); + } +} diff --git a/core/java/android/security/authenticationpolicy/IAuthenticationPolicyService.aidl b/core/java/android/security/authenticationpolicy/IAuthenticationPolicyService.aidl new file mode 100644 index 000000000000..5ad4534c257a --- /dev/null +++ b/core/java/android/security/authenticationpolicy/IAuthenticationPolicyService.aidl @@ -0,0 +1,32 @@ +/* + * 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 android.security.authenticationpolicy; + +import android.security.authenticationpolicy.EnableSecureLockDeviceParams; +import android.security.authenticationpolicy.DisableSecureLockDeviceParams; + +/** + * Communication channel from AuthenticationPolicyManager to AuthenticationPolicyService. + * @hide + */ +interface IAuthenticationPolicyService { + @EnforcePermission("MANAGE_SECURE_LOCK_DEVICE") + int enableSecureLockDevice(in EnableSecureLockDeviceParams params); + + @EnforcePermission("MANAGE_SECURE_LOCK_DEVICE") + int disableSecureLockDevice(in DisableSecureLockDeviceParams params); +}
\ No newline at end of file diff --git a/core/java/android/security/authenticationpolicy/OWNERS b/core/java/android/security/authenticationpolicy/OWNERS new file mode 100644 index 000000000000..4310d1a3a9db --- /dev/null +++ b/core/java/android/security/authenticationpolicy/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/security/authenticationpolicy/OWNERS
\ No newline at end of file diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 24328eb1e825..13887781f1ec 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -2532,7 +2532,7 @@ public class ZenModeConfig implements Parcelable { /** Returns whether the rule id corresponds to an implicit rule. */ public static boolean isImplicitRuleId(@NonNull String ruleId) { - return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX); + return ruleId != null && ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX); } private static int[] tryParseHourAndMinute(String value) { diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index 1df3b4332754..c16a510ed729 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -1712,6 +1712,15 @@ public class PhoneStateListener { @NonNull NtnSignalStrength ntnSignalStrength) { // not supported on the deprecated interface - Use TelephonyCallback instead } + + public final void onSecurityAlgorithmsChanged(SecurityAlgorithmUpdate update) { + // not supported on the deprecated interface - Use TelephonyCallback instead + } + + public final void onCellularIdentifierDisclosedChanged( + CellularIdentifierDisclosure disclosure) { + // not supported on the deprecated interface - Use TelephonyCallback instead + } } private void log(String s) { diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index 0d1dc4611343..2c585e640fdd 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -705,6 +705,28 @@ public class TelephonyCallback { public static final int EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED = 45; /** + * Event for changes to mobile network ciphering algorithms. + * See {@link SecurityAlgorithmsListener#onSecurityAlgorithmsChanged} + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS) + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SystemApi + public static final int EVENT_SECURITY_ALGORITHMS_CHANGED = 46; + + /** + * Event for updates to sensitive device identifier disclosures (IMSI, IMEI, unciphered SUCI). + * See {@link CellularIdentifierDisclosedListener#onCellularIdentifierDisclosedChanged} + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS) + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SystemApi + public static final int EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED = 47; + + /** * @hide */ @IntDef(prefix = {"EVENT_"}, value = { @@ -752,7 +774,9 @@ public class TelephonyCallback { EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED, EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED, EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED, - EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED + EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED, + EVENT_SECURITY_ALGORITHMS_CHANGED, + EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED }) @Retention(RetentionPolicy.SOURCE) public @interface TelephonyEvent { @@ -1827,6 +1851,41 @@ public class TelephonyCallback { } /** + * Interface for CellularIdentifierDisclosedListener + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS) + public interface CellularIdentifierDisclosedListener { + /** + * Callback invoked when a device identifier (IMSI, IMEI, or unciphered SUCI) + * is disclosed over the network before a security context is established + * ("pre-authentication"). + * + * @param disclosure details of the identifier disclosure + * See {@link CellularIdentifierDisclosure} for more details + */ + void onCellularIdentifierDisclosedChanged(@NonNull CellularIdentifierDisclosure disclosure); + } + + /** + * Interface for SecurityAlgorithmsListener + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_SECURITY_ALGORITHMS_UPDATE_INDICATIONS) + public interface SecurityAlgorithmsListener { + /** + * Callback invoked when the most recently reported security algorithms has changed, + * per a specified connection event. + * + * @param securityAlgorithmUpdate details of the security algorithm update + * See {@link SecurityAlgorithmUpdate} for more details + */ + void onSecurityAlgorithmsChanged(@NonNull SecurityAlgorithmUpdate securityAlgorithmUpdate); + } + + /** * The callback methods need to be called on the handler thread where * this object was created. If the binder did that for us it'd be nice. * <p> @@ -2302,5 +2361,27 @@ public class TelephonyCallback { () -> listener.onCarrierRoamingNtnSignalStrengthChanged(ntnSignalStrength))); } + + public void onSecurityAlgorithmsChanged(SecurityAlgorithmUpdate update) { + if (!Flags.securityAlgorithmsUpdateIndications()) return; + + SecurityAlgorithmsListener listener = + (SecurityAlgorithmsListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity(() -> mExecutor.execute( + () -> listener.onSecurityAlgorithmsChanged(update))); + } + + public void onCellularIdentifierDisclosedChanged(CellularIdentifierDisclosure disclosure) { + if (!Flags.cellularIdentifierDisclosureIndications()) return; + + CellularIdentifierDisclosedListener listener = + (CellularIdentifierDisclosedListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity(() -> mExecutor.execute( + () -> listener.onCellularIdentifierDisclosedChanged(disclosure))); + } } } diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 90b0bb34c145..4ec429d0c4ad 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -1154,6 +1154,40 @@ public class TelephonyRegistryManager { } } + /** + * Notify external listeners that the radio security algorithms have changed. + * @param slotIndex for the phone object that got updated + * @param subId for which the security algorithm changed + * @param update details of the security algorithm update + * @hide + */ + public void notifySecurityAlgorithmsChanged( + int slotIndex, int subId, SecurityAlgorithmUpdate update) { + try { + sRegistry.notifySecurityAlgorithmsChanged(slotIndex, subId, update); + } catch (RemoteException ex) { + // system server crash + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Notify external listeners of a new cellular identifier disclosure change. + * @param slotIndex for the phone object that the disclosure applies to + * @param subId for which the disclosure applies to + * @param disclosure details of the identifier disclosure + * @hide + */ + public void notifyCellularIdentifierDisclosedChanged( + int slotIndex, int subId, CellularIdentifierDisclosure disclosure) { + try { + sRegistry.notifyCellularIdentifierDisclosedChanged(slotIndex, subId, disclosure); + } catch (RemoteException ex) { + // system server crash + throw ex.rethrowFromSystemServer(); + } + } + /** * Processes potential event changes from the provided {@link TelephonyCallback}. * @@ -1313,6 +1347,15 @@ public class TelephonyRegistryManager { eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED); eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED); } + + if (telephonyCallback instanceof TelephonyCallback.CellularIdentifierDisclosedListener) { + eventList.add(TelephonyCallback.EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED); + } + + if (telephonyCallback instanceof TelephonyCallback.SecurityAlgorithmsListener) { + eventList.add(TelephonyCallback.EVENT_SECURITY_ALGORITHMS_CHANGED); + } + return eventList; } diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 5a28d5f7fc01..80ae3c3d4e73 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -437,16 +437,16 @@ public class ScaleGestureDetector { } } - /** - * Return whether the quick scale gesture, in which the user performs a double tap followed by a - * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}. - */ + /** + * Return whether the quick scale gesture, in which the user performs a double tap followed by a + * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}. + */ public boolean isQuickScaleEnabled() { return mQuickScaleEnabled; } /** - * Sets whether the associates {@link OnScaleGestureListener} should receive + * Sets whether the associated {@link OnScaleGestureListener} should receive * onScale callbacks when the user uses a stylus and presses the button. * Note that this is enabled by default if the app targets API 23 and newer. * diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index eebdeadcdeb2..c7d5a9fe6041 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -253,6 +253,9 @@ flag { namespace: "lse_desktop_experience" description: "Enables enter desktop windowing transition & motion polish changes" bug: "369763947" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -260,6 +263,9 @@ flag { namespace: "lse_desktop_experience" description: "Enables exit desktop windowing transition & motion polish changes" bug: "353650462" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -343,6 +349,9 @@ flag { namespace: "lse_desktop_experience" description: "Enables custom transitions for alt-tab app launches in Desktop Mode." bug: "370735595" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -350,6 +359,9 @@ flag { namespace: "lse_desktop_experience" description: "Enables custom transitions for app launches in Desktop Mode." bug: "375992828" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 96b9dc7cab0e..bb4770768cb1 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -29,14 +29,6 @@ flag { flag { namespace: "window_surfaces" - name: "secure_window_state" - description: "Move SC secure flag to WindowState level" - is_fixed_read_only: true - bug: "308662081" -} - -flag { - namespace: "window_surfaces" name: "trusted_presentation_listener_for_window" is_exported: true description: "Enable trustedPresentationListener on windows public API" diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 3ec70649294b..2cfc680a3fe8 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -152,7 +152,7 @@ interface IAppOpsService { in AttributionSourceState attributionSourceStateState, boolean skipProxyOperation); int checkOperationRawForDevice(int code, int uid, String packageName, @nullable String attributionTag, int virtualDeviceId); - int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId); + int checkOperationForDevice(int code, int uid, String packageName, @nullable String attributionTag, int virtualDeviceId); SyncNotedAppOp noteOperationForDevice(int code, int uid, String packageName, @nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage); diff --git a/core/java/com/android/internal/policy/IDeviceLockedStateListener.aidl b/core/java/com/android/internal/policy/IDeviceLockedStateListener.aidl new file mode 100644 index 000000000000..cc626f699d43 --- /dev/null +++ b/core/java/com/android/internal/policy/IDeviceLockedStateListener.aidl @@ -0,0 +1,21 @@ +/* + * 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.internal.policy; + +oneway interface IDeviceLockedStateListener { + void onDeviceLockedStateChanged(boolean isDeviceLocked); +}
\ No newline at end of file diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 0e85e046e1b6..bf8a56508f54 100644 --- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -20,6 +20,7 @@ import android.telephony.BarringInfo; import android.telephony.CallState; import android.telephony.CellIdentity; import android.telephony.CellInfo; +import android.telephony.CellularIdentifierDisclosure; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.LinkCapacityEstimate; import android.telephony.TelephonyDisplayInfo; @@ -28,6 +29,7 @@ import android.telephony.PhysicalChannelConfig; import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; import android.telephony.satellite.NtnSignalStrength; +import android.telephony.SecurityAlgorithmUpdate; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; @@ -87,4 +89,6 @@ oneway interface IPhoneStateListener { void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible); void onCarrierRoamingNtnAvailableServicesChanged(in int[] availableServices); void onCarrierRoamingNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength); + void onSecurityAlgorithmsChanged(in SecurityAlgorithmUpdate update); + void onCellularIdentifierDisclosedChanged(in CellularIdentifierDisclosure disclosure); } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 0f268d5de62b..a296fbd1cfe4 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -23,6 +23,7 @@ import android.telephony.BarringInfo; import android.telephony.CallQuality; import android.telephony.CellIdentity; import android.telephony.CellInfo; +import android.telephony.CellularIdentifierDisclosure; import android.telephony.LinkCapacityEstimate; import android.telephony.TelephonyDisplayInfo; import android.telephony.ims.ImsReasonInfo; @@ -30,6 +31,7 @@ import android.telephony.PhoneCapability; import android.telephony.PhysicalChannelConfig; import android.telephony.PreciseDataConnectionState; import android.telephony.satellite.NtnSignalStrength; +import android.telephony.SecurityAlgorithmUpdate; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; @@ -132,4 +134,7 @@ interface ITelephonyRegistry { void removeSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg); void notifySatelliteStateChanged(boolean isEnabled); + void notifySecurityAlgorithmsChanged(int phoneId, int subId, in SecurityAlgorithmUpdate update); + void notifyCellularIdentifierDisclosedChanged( + int phoneId, int subId, in CellularIdentifierDisclosure disclosure); } diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 5fc61b00e331..c96e979138dd 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -19,12 +19,7 @@ package com.android.internal.widget; import android.annotation.Nullable; import android.app.Flags; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.util.AttributeSet; @@ -41,7 +36,6 @@ import android.widget.RemoteViews; public class NotificationRowIconView extends CachingIconView { private NotificationIconProvider mIconProvider; - private boolean mApplyCircularCrop = false; private Drawable mAppIcon = null; // Padding, background and colors set on the view prior to being overridden when showing the app @@ -221,84 +215,6 @@ public class NotificationRowIconView extends CachingIconView { } } - @Nullable - @Override - Drawable loadSizeRestrictedIcon(@Nullable Icon icon) { - final Drawable original = super.loadSizeRestrictedIcon(icon); - final Drawable result; - if (mApplyCircularCrop) { - result = makeCircularDrawable(original); - } else { - result = original; - } - - return result; - } - - /** - * Enables circle crop that makes given image circular - */ - @RemotableViewMethod(asyncImpl = "setApplyCircularCropAsync") - public void setApplyCircularCrop(boolean applyCircularCrop) { - mApplyCircularCrop = applyCircularCrop; - } - - /** - * Async version of {@link NotificationRowIconView#setApplyCircularCrop} - */ - public Runnable setApplyCircularCropAsync(boolean applyCircularCrop) { - mApplyCircularCrop = applyCircularCrop; - return () -> { - }; - } - - @Nullable - private Drawable makeCircularDrawable(@Nullable Drawable original) { - if (original == null) { - return original; - } - - final Bitmap source = drawableToBitmap(original); - - int size = Math.min(source.getWidth(), source.getHeight()); - - Bitmap squared = Bitmap.createScaledBitmap(source, size, size, /* filter= */ false); - Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - - final Canvas canvas = new Canvas(result); - final Paint paint = new Paint(); - paint.setShader( - new BitmapShader(squared, BitmapShader.TileMode.CLAMP, - BitmapShader.TileMode.CLAMP)); - paint.setAntiAlias(true); - float radius = size / 2f; - canvas.drawCircle(radius, radius, radius, paint); - return new BitmapDrawable(getResources(), result); - } - - private static Bitmap drawableToBitmap(Drawable drawable) { - if (drawable instanceof BitmapDrawable bitmapDrawable) { - final Bitmap bitmap = bitmapDrawable.getBitmap(); - if (bitmap.getConfig() == Bitmap.Config.HARDWARE) { - return bitmap.copy(Bitmap.Config.ARGB_8888, false); - } else { - return bitmap; - } - } - - int width = drawable.getIntrinsicWidth(); - width = width > 0 ? width : 1; - int height = drawable.getIntrinsicHeight(); - height = height > 0 ? height : 1; - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return bitmap; - } - /** * A provider that allows this view to verify whether it should use the app icon instead of the * icon provided to it via setImageIcon, as well as actually fetching the app icon. It should diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index f9d00edce3fa..5a183925e38e 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -584,14 +584,23 @@ static jboolean android_media_AudioRecord_setInputDevice( return lpRecorder->setInputDevice(device_id) == NO_ERROR; } -static jint android_media_AudioRecord_getRoutedDeviceId( - JNIEnv *env, jobject thiz) { - +static jintArray android_media_AudioRecord_getRoutedDeviceIds(JNIEnv *env, jobject thiz) { sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); - if (lpRecorder == 0) { - return 0; + if (lpRecorder == NULL) { + return NULL; + } + DeviceIdVector deviceIds = lpRecorder->getRoutedDeviceIds(); + jintArray result; + result = env->NewIntArray(deviceIds.size()); + if (result == NULL) { + return NULL; + } + jint *values = env->GetIntArrayElements(result, 0); + for (unsigned int i = 0; i < deviceIds.size(); i++) { + values[i++] = static_cast<jint>(deviceIds[i]); } - return (jint)lpRecorder->getRoutedDeviceId(); + env->ReleaseIntArrayElements(result, values, 0); + return result; } // Enable and Disable Callback methods are synchronized on the Java side @@ -821,8 +830,7 @@ static const JNINativeMethod gMethods[] = { // name, signature, funcPtr {"native_start", "(II)I", (void *)android_media_AudioRecord_start}, {"native_stop", "()V", (void *)android_media_AudioRecord_stop}, - {"native_setup", - "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JII)I", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JII)I", (void *)android_media_AudioRecord_setup}, {"native_finalize", "()V", (void *)android_media_AudioRecord_finalize}, {"native_release", "()V", (void *)android_media_AudioRecord_release}, @@ -846,7 +854,7 @@ static const JNINativeMethod gMethods[] = { {"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_AudioRecord_native_getMetrics}, {"native_setInputDevice", "(I)Z", (void *)android_media_AudioRecord_setInputDevice}, - {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioRecord_getRoutedDeviceId}, + {"native_getRoutedDeviceIds", "()[I", (void *)android_media_AudioRecord_getRoutedDeviceIds}, {"native_enableDeviceCallback", "()V", (void *)android_media_AudioRecord_enableDeviceCallback}, {"native_disableDeviceCallback", "()V", diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 1557f9ed47a5..5d4d1ce20e5d 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -1190,15 +1190,23 @@ static jboolean android_media_AudioTrack_setOutputDevice( } return lpTrack->setOutputDevice(device_id) == NO_ERROR; } - -static jint android_media_AudioTrack_getRoutedDeviceId( - JNIEnv *env, jobject thiz) { - +static jintArray android_media_AudioTrack_getRoutedDeviceIds(JNIEnv *env, jobject thiz) { sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { - return 0; + return NULL; + } + DeviceIdVector deviceIds = lpTrack->getRoutedDeviceIds(); + jintArray result; + result = env->NewIntArray(deviceIds.size()); + if (result == NULL) { + return NULL; + } + jint *values = env->GetIntArrayElements(result, 0); + for (unsigned int i = 0; i < deviceIds.size(); i++) { + values[i++] = static_cast<jint>(deviceIds[i]); } - return (jint)lpTrack->getRoutedDeviceId(); + env->ReleaseIntArrayElements(result, values, 0); + return result; } static void android_media_AudioTrack_enableDeviceCallback( @@ -1503,7 +1511,7 @@ static const JNINativeMethod gMethods[] = { (void *)android_media_AudioTrack_setAuxEffectSendLevel}, {"native_attachAuxEffect", "(I)I", (void *)android_media_AudioTrack_attachAuxEffect}, {"native_setOutputDevice", "(I)Z", (void *)android_media_AudioTrack_setOutputDevice}, - {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId}, + {"native_getRoutedDeviceIds", "()[I", (void *)android_media_AudioTrack_getRoutedDeviceIds}, {"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback}, {"native_disableDeviceCallback", "()V", diff --git a/core/jni/android_media_DeviceCallback.cpp b/core/jni/android_media_DeviceCallback.cpp index a1a035110caf..ccdf633c842a 100644 --- a/core/jni/android_media_DeviceCallback.cpp +++ b/core/jni/android_media_DeviceCallback.cpp @@ -61,21 +61,20 @@ JNIDeviceCallback::~JNIDeviceCallback() } void JNIDeviceCallback::onAudioDeviceUpdate(audio_io_handle_t audioIo, - audio_port_handle_t deviceId) -{ + const DeviceIdVector& deviceIds) { JNIEnv *env = AndroidRuntime::getJNIEnv(); if (env == NULL) { return; } - ALOGV("%s audioIo %d deviceId %d", __FUNCTION__, audioIo, deviceId); - env->CallStaticVoidMethod(mClass, - mPostEventFromNative, - mObject, - AUDIO_NATIVE_EVENT_ROUTING_CHANGE, deviceId, 0, NULL); + ALOGV("%s audioIo %d deviceIds %s", __FUNCTION__, audioIo, toString(deviceIds).c_str()); + // Java should query the new device ids once it gets the event. + // TODO(b/378505346): Pass the deviceIds to Java to avoid race conditions. + env->CallStaticVoidMethod(mClass, mPostEventFromNative, mObject, + AUDIO_NATIVE_EVENT_ROUTING_CHANGE, 0 /*arg1*/, 0 /*arg2*/, + NULL /*obj*/); if (env->ExceptionCheck()) { ALOGW("An exception occurred while notifying an event."); env->ExceptionClear(); } } - diff --git a/core/jni/android_media_DeviceCallback.h b/core/jni/android_media_DeviceCallback.h index 7ae788eaf058..0c9ccc89ba1a 100644 --- a/core/jni/android_media_DeviceCallback.h +++ b/core/jni/android_media_DeviceCallback.h @@ -31,8 +31,7 @@ public: JNIDeviceCallback(JNIEnv* env, jobject thiz, jobject weak_thiz, jmethodID postEventFromNative); ~JNIDeviceCallback(); - virtual void onAudioDeviceUpdate(audio_io_handle_t audioIo, - audio_port_handle_t deviceId); + virtual void onAudioDeviceUpdate(audio_io_handle_t audioIo, const DeviceIdVector& deviceIds); private: void sendEvent(int event); diff --git a/core/res/Android.bp b/core/res/Android.bp index 0e4e22b09e24..26e63bc092fa 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -161,6 +161,7 @@ android_app { "android.app.flags-aconfig", "android.appwidget.flags-aconfig", "android.content.pm.flags-aconfig", + "android.media.audio-aconfig", "android.provider.flags-aconfig", "camera_platform_flags", "android.net.platform.flags-aconfig", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 00464056fd84..8cc7b0b8f942 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5587,7 +5587,8 @@ <permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE" android:protectionLevel="signature" /> - <!-- Allows an application to subscribe to keyguard locked (i.e., showing) state. + <!-- Allows an application to subscribe to device locked and keyguard locked (i.e., showing) + state. <p>Protection level: signature|role <p>Intended for use by ROLE_ASSISTANT and signature apps only. --> @@ -6491,6 +6492,15 @@ <permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" android:protectionLevel="signature|privileged|role" /> + <!-- @SystemApi Allows an application to bypass concurrency restrictions while + recording audio. For example, apps with this permission can continue to record + while a voice call is active.</p> + @FlaggedApi(android.media.audio.Flags.FLAG_CONCURRENT_AUDIO_RECORD_BYPASS_PERMISSION) + @hide --> + <permission android:name="android.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION" + android:featureFlag="android.media.audio.concurrent_audio_record_bypass_permission" + android:protectionLevel="signature|privileged|role" /> + <!-- @SystemApi Allows an application to capture audio for hotword detection. <p>Not for use by third-party applications.</p> @hide --> @@ -8650,6 +8660,17 @@ <permission android:name="android.permission.SETUP_FSVERITY" android:protectionLevel="signature|privileged"/> + <!-- @SystemApi + @FlaggedApi(android.security.Flags.FLAG_SECURE_LOCKDOWN) + Allows an application to lock down the device into an enhanced security state. + <p>Not for use by third-party applications. + <p>Protection level: signature|privileged + @hide + --> + <permission android:name="android.permission.MANAGE_SECURE_LOCK_DEVICE" + android:protectionLevel="signature|privileged" + android:featureFlag="android.security.secure_lockdown" /> + <!-- Allows app to enter trade-in-mode. <p>Protection level: signature|privileged @hide diff --git a/core/res/res/layout/notification_2025_template_heads_up_base.xml b/core/res/res/layout/notification_2025_template_heads_up_base.xml new file mode 100644 index 000000000000..e4ff835a3524 --- /dev/null +++ b/core/res/res/layout/notification_2025_template_heads_up_base.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2014 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 + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:clipChildren="false" + android:tag="headsUp" + > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:orientation="vertical" + > + + <include + layout="@layout/notification_2025_template_collapsed_base" + android:id="@null" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="-20dp" + android:clipChildren="false" + android:orientation="vertical" + > + + <ViewStub + android:layout="@layout/notification_material_reply_text" + android:id="@+id/notification_material_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + + <include + layout="@layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginTop="@dimen/notification_content_margin" + /> + + <include layout="@layout/notification_material_action_list" /> + </LinearLayout> + </LinearLayout> +</FrameLayout> diff --git a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml index 82920bad95cd..149a5a9568f2 100644 --- a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml +++ b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml @@ -34,12 +34,14 @@ android:maxDrawableWidth="@dimen/notification_icon_circle_size" android:maxDrawableHeight="@dimen/notification_icon_circle_size" /> - <com.android.internal.widget.NotificationRowIconView + <com.android.internal.widget.CachingIconView android:id="@+id/conversation_icon" android:layout_width="@dimen/notification_icon_circle_size" android:layout_height="@dimen/notification_icon_circle_size" android:layout_gravity="center_vertical|start" android:layout_marginStart="@dimen/notification_icon_circle_start" + android:background="@drawable/notification_icon_circle" + android:clipToOutline="true" android:maxDrawableWidth="@dimen/notification_icon_circle_size" android:maxDrawableHeight="@dimen/notification_icon_circle_size" android:scaleType="centerCrop" diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a2a19a23d431..5a6b66cc1504 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2390,6 +2390,7 @@ <java-symbol type="layout" name="notification_material_action_list" /> <java-symbol type="layout" name="notification_material_action_tombstone" /> <java-symbol type="layout" name="notification_2025_template_collapsed_base" /> + <java-symbol type="layout" name="notification_2025_template_heads_up_base" /> <java-symbol type="layout" name="notification_2025_template_header" /> <java-symbol type="layout" name="notification_template_material_base" /> <java-symbol type="layout" name="notification_template_material_heads_up_base" /> diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java index fe54aa8d87f0..945147db1ef5 100644 --- a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java +++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java @@ -18,6 +18,8 @@ package com.android.frameworks.coretests.bfscctestapp; import android.app.Service; import android.content.Intent; +import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; @@ -36,6 +38,7 @@ public class BfsccTestAppCmdService extends Service { @Override public void listenTo(IBinder binder) throws RemoteException { binder.addFrozenStateChangeCallback( + new HandlerExecutor(Handler.getMain()), (IBinder who, int state) -> mNotifications.offer(state)); } diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index a2598f69e031..2fc72e1d3994 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -16,6 +16,7 @@ package android.app; +import static android.app.Flags.FLAG_PIC_CACHE_NULLS; import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID; import static android.app.PropertyInvalidatedCache.NONCE_UNSET; import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH; @@ -229,7 +230,12 @@ public class PropertyInvalidatedCacheTests { @Override public String apply(Integer qv) { mRecomputeCount += 1; - return "foo" + qv.toString(); + // Special case for testing caches of nulls. Integers in the range 30-40 return null. + if (qv >= 30 && qv < 40) { + return null; + } else { + return "foo" + qv.toString(); + } } int getRecomputeCount() { @@ -643,4 +649,35 @@ public class PropertyInvalidatedCacheTests { Binder.restoreCallingWorkSource(token); } } + + @RequiresFlagsEnabled(FLAG_PIC_CACHE_NULLS) + @Test + public void testCachingNulls() { + TestCache cache = new TestCache(new Args(MODULE_TEST) + .maxEntries(4).api("testCachingNulls").cacheNulls(true), + new TestQuery()); + cache.invalidateCache(); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + assertEquals(3, cache.getRecomputeCount()); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + assertEquals(3, cache.getRecomputeCount()); + + cache = new TestCache(new Args(MODULE_TEST) + .maxEntries(4).api("testCachingNulls").cacheNulls(false), + new TestQuery()); + cache.invalidateCache(); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + assertEquals(3, cache.getRecomputeCount()); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + // The recompute is 4 because nulls were not cached. + assertEquals(4, cache.getRecomputeCount()); + } } diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java index 0db49a72c51d..ecacdb2bde0b 100644 --- a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java +++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java @@ -33,6 +33,7 @@ import android.content.pm.parsing.result.ParseTypeImpl; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; import android.util.ArraySet; import android.util.PackageUtils; @@ -61,6 +62,7 @@ import java.util.List; import java.util.Set; @Presubmit +@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) public class ApkLiteParseUtilsTest { @Rule diff --git a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java index 195a18a5f521..523fe1a8aa5d 100644 --- a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java +++ b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java @@ -200,7 +200,7 @@ public class BinderFrozenStateChangeNotificationTest { IBinder.FrozenStateChangeCallback callback = (IBinder who, int state) -> results.offer(who); try { - binder.addFrozenStateChangeCallback(callback); + binder.addFrozenStateChangeCallback(new HandlerExecutor(Handler.getMain()), callback); } catch (UnsupportedOperationException e) { return; } @@ -227,7 +227,7 @@ public class BinderFrozenStateChangeNotificationTest { final IBinder.FrozenStateChangeCallback callback = (IBinder who, int state) -> queue.offer(state == IBinder.FrozenStateChangeCallback.STATE_FROZEN); - binder.addFrozenStateChangeCallback(callback); + binder.addFrozenStateChangeCallback(new HandlerExecutor(Handler.getMain()), callback); return callback; } catch (UnsupportedOperationException e) { return null; diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java index 67de25eede42..75118873dd75 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java @@ -42,6 +42,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.FileDescriptor; +import java.util.concurrent.Executor; @SmallTest @RunWith(AndroidJUnit4.class) @@ -125,7 +126,7 @@ public class BinderDeathDispatcherTest { } @Override - public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback) + public void addFrozenStateChangeCallback(Executor e, FrozenStateChangeCallback callback) throws RemoteException { } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index fea7cb4b422c..329e5de15f5e 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -533,6 +533,8 @@ applications that come with the platform <permission name="com.android.cellbroadcastservice.FULL_ACCESS_CELL_BROADCAST_HISTORY" /> <!-- Permission required for ATS test - CarDevicePolicyManagerTest --> <permission name="android.permission.LOCK_DEVICE" /> + <!-- Permission required for AuthenticationPolicyManagerTest --> + <permission name="android.permission.MANAGE_SECURE_LOCK_DEVICE" /> <!-- Permissions required for CTS test - CtsSafetyCenterTestCases --> <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" /> <permission name="android.permission.READ_SAFETY_CENTER_STATUS" /> diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml index 55cda783005f..597a921302b7 100644 --- a/libs/WindowManager/Shell/res/values/styles.xml +++ b/libs/WindowManager/Shell/res/values/styles.xml @@ -29,6 +29,7 @@ <item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:windowAnimationStyle">@null</item> + <item name="android:windowIsTranslucent">true</item> </style> <style name="Animation.ForcedResizable" parent="@android:style/Animation"> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 8bad87480985..6fc6eb783a17 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -70,6 +70,8 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags +import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut +import com.android.wm.shell.Flags.enableFlexibleSplit import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer @@ -102,6 +104,7 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.splitscreen.SplitScreenController @@ -127,6 +130,10 @@ import java.util.Optional import java.util.concurrent.Executor import java.util.function.Consumer import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS +import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION +import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION + /** Handles moving tasks in and out of desktop */ class DesktopTasksController( private val context: Context, @@ -195,6 +202,9 @@ class DesktopTasksController( @VisibleForTesting var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null + @VisibleForTesting + var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener? = null + /** Task id of the task currently being dragged from fullscreen/split. */ val draggingTaskId get() = dragToDesktopTransitionHandler.draggingTaskId @@ -380,6 +390,7 @@ class DesktopTasksController( ) // TODO(343149901): Add DPI changes for task launch val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource) + desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) return true @@ -409,6 +420,7 @@ class DesktopTasksController( addMoveToDesktopChanges(wct, task) val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource) + desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) } @@ -450,6 +462,9 @@ class DesktopTasksController( val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( wct, taskInfo.displayId) val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) + desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( + DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt() + ) transition?.let { taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) @@ -485,9 +500,7 @@ class DesktopTasksController( ): ((IBinder) -> Unit)? { val taskId = taskInfo.taskId desktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId) - if (taskRepository.isOnlyVisibleNonClosingTask(taskId)) { - removeWallpaperActivity(wct) - } + performDesktopExitCleanupIfNeeded(taskId, wct) taskRepository.addClosingTask(displayId, taskId) taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( doesAnyTaskRequireTaskbarRounding( @@ -503,11 +516,7 @@ class DesktopTasksController( val taskId = taskInfo.taskId val displayId = taskInfo.displayId val wct = WindowContainerTransaction() - if (taskRepository.isOnlyVisibleNonClosingTask(taskId)) { - // Perform clean up of the desktop wallpaper activity if the minimized window task is - // the last active task. - removeWallpaperActivity(wct) - } + performDesktopExitCleanupIfNeeded(taskId, wct) // Notify immersive handler as it might need to exit immersive state. val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo) @@ -576,6 +585,11 @@ class DesktopTasksController( position, mOnAnimationFinishedCallback ) + + // handles case where we are moving to full screen without closing all DW tasks. + if (!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)) { + desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + } } /** @@ -1226,6 +1240,21 @@ class DesktopTasksController( } } + /** + * Remove wallpaper activity if task provided is last task and wallpaper activity token is not + * null + */ + private fun performDesktopExitCleanupIfNeeded(taskId: Int, wct: WindowContainerTransaction) { + if (!taskRepository.isOnlyVisibleNonClosingTask(taskId)) { + return + } + desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + if (taskRepository.wallpaperActivityToken != null) { + removeWallpaperActivity(wct) + } + } + + fun releaseVisualIndicator() { val t = SurfaceControl.Transaction() visualIndicator?.releaseVisualIndicator(t) @@ -1486,11 +1515,15 @@ class DesktopTasksController( WINDOWING_MODE_MULTI_WINDOW -> { val splitPosition = splitScreenController .determineNewInstancePosition(callingTaskInfo) + // TODO(b/349828130) currently pass in index_undefined until we can revisit these + // specific cases in the future. + val splitIndex = if (enableFlexibleSplit()) + splitScreenController.determineNewInstanceIndex(callingTaskInfo) else + SPLIT_INDEX_UNDEFINED splitScreenController.startIntent( launchIntent, context.userId, fillIn, splitPosition, options.toBundle(), null /* hideTaskToken */, - true /* forceLaunchNewTask */ - ) + true /* forceLaunchNewTask */, splitIndex) } WINDOWING_MODE_FREEFORM -> { val wct = WindowContainerTransaction() @@ -1662,12 +1695,7 @@ class DesktopTasksController( return null val wct = WindowContainerTransaction() - if (taskRepository.isOnlyVisibleNonClosingTask(task.taskId) - && taskRepository.wallpaperActivityToken != null - ) { - // Remove wallpaper activity when the last active task is removed - removeWallpaperActivity(wct) - } + performDesktopExitCleanupIfNeeded(task.taskId, wct) if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { taskRepository.addClosingTask(task.displayId, task.taskId) @@ -1758,10 +1786,8 @@ class DesktopTasksController( if (useDesktopOverrideDensity()) { wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) } - if (taskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) { - // Remove wallpaper activity when leaving desktop mode - removeWallpaperActivity(wct) - } + + performDesktopExitCleanupIfNeeded(taskInfo.taskId, wct) } private fun cascadeWindow(bounds: Rect, displayLayout: DisplayLayout, displayId: Int) { @@ -1790,10 +1816,8 @@ class DesktopTasksController( // The task's density may have been overridden in freeform; revert it here as we don't // want it overridden in multi-window. wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) - if (taskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) { - // Remove wallpaper activity when leaving desktop mode - removeWallpaperActivity(wct) - } + + performDesktopExitCleanupIfNeeded(taskInfo.taskId, wct) } /** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */ @@ -2331,7 +2355,7 @@ class DesktopTasksController( } } - private val mTaskbarDesktopTaskListener: TaskbarDesktopTaskListener = + private val taskbarDesktopTaskListener: TaskbarDesktopTaskListener = object : TaskbarDesktopTaskListener { override fun onTaskbarCornerRoundingUpdate( hasTasksRequiringTaskbarRounding: Boolean) { @@ -2348,6 +2372,27 @@ class DesktopTasksController( } } + private val desktopModeEntryExitTransitionListener: DesktopModeEntryExitTransitionListener = + object : DesktopModeEntryExitTransitionListener { + override fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) { + ProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: onEnterDesktopModeTransitionStarted transitionTime=%s", + transitionDuration + ) + remoteListener.call { l -> l.onEnterDesktopModeTransitionStarted(transitionDuration) } + } + + override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) { + ProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: onExitDesktopModeTransitionStarted transitionTime=%s", + transitionDuration + ) + remoteListener.call { l -> l.onExitDesktopModeTransitionStarted(transitionDuration) } + } + } + init { remoteListener = SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>( @@ -2355,13 +2400,16 @@ class DesktopTasksController( { c -> run { c.taskRepository.addVisibleTasksListener(listener, c.mainExecutor) - c.taskbarDesktopTaskListener = mTaskbarDesktopTaskListener + c.taskbarDesktopTaskListener = taskbarDesktopTaskListener + c.desktopModeEnterExitTransitionListener = + desktopModeEntryExitTransitionListener } }, { c -> run { c.taskRepository.removeVisibleTasksListener(listener) c.taskbarDesktopTaskListener = null + c.desktopModeEnterExitTransitionListener = null } } ) @@ -2468,6 +2516,15 @@ class DesktopTasksController( fun onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding: Boolean) } + /** Defines interface for entering and exiting desktop windowing mode. */ + interface DesktopModeEntryExitTransitionListener { + /** [transitionDuration] time it takes to run enter desktop mode transition */ + fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) + + /** [transitionDuration] time it takes to run exit desktop mode transition */ + fun onExitDesktopModeTransitionStarted(transitionDuration: Int) + } + /** The positions on a screen that a task can snap to. */ enum class SnapPosition { RIGHT, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java index d537da802efd..b902bb4394b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java @@ -60,8 +60,7 @@ import java.util.function.Supplier; * entering and exiting freeform. */ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionHandler { - @VisibleForTesting - static final int FULLSCREEN_ANIMATION_DURATION = 336; + public static final int FULLSCREEN_ANIMATION_DURATION = 336; private final Context mContext; private final Transitions mTransitions; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl index c2acb87222d1..6002a4dfe0d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl @@ -33,4 +33,10 @@ interface IDesktopTaskListener { * [hasTasksRequiringTaskbarRounding] is true. */ oneway void onTaskbarCornerRoundingUpdate(boolean hasTasksRequiringTaskbarRounding); + + /** Entering desktop mode transition is started, send the signal with transition duration. */ + oneway void onEnterDesktopModeTransitionStarted(int transitionDuration); + + /** Exiting desktop mode transition is started, send the signal with transition duration. */ + oneway void onExitDesktopModeTransitionStarted(int transitionDuration); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java index 5d22c1edf8fe..ae9d21f621de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java @@ -41,6 +41,9 @@ import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP; import static com.android.wm.shell.shared.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -81,6 +84,7 @@ import com.android.wm.shell.draganddrop.anim.HoverAnimProps; import com.android.wm.shell.draganddrop.anim.TwoFiftyFiftyTargetAnimator; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.split.SplitScreenConstants; +import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -219,8 +223,10 @@ public class SplitDragPolicy implements DropTarget { displayRegion.splitHorizontally(startHitRegion, endHitRegion); } - mTargets.add(new Target(TYPE_SPLIT_LEFT, startHitRegion, startBounds, -1)); - mTargets.add(new Target(TYPE_SPLIT_RIGHT, endHitRegion, endBounds, -1)); + mTargets.add(new Target(TYPE_SPLIT_LEFT, startHitRegion, startBounds, + SPLIT_INDEX_0)); + mTargets.add(new Target(TYPE_SPLIT_RIGHT, endHitRegion, endBounds, + SPLIT_INDEX_1)); } else { // TODO(b/349828130), move this into init function and/or the insets updating // callback @@ -287,9 +293,10 @@ public class SplitDragPolicy implements DropTarget { displayRegion.splitVertically(leftHitRegion, rightHitRegion); } - mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds, -1)); + mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds, + SPLIT_INDEX_UNDEFINED)); mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds, - -1)); + SPLIT_INDEX_UNDEFINED)); } else { final Rect topHitRegion = new Rect(); final Rect bottomHitRegion = new Rect(); @@ -308,9 +315,10 @@ public class SplitDragPolicy implements DropTarget { displayRegion.splitHorizontally(topHitRegion, bottomHitRegion); } - mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds, -1)); + mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds, + SPLIT_INDEX_UNDEFINED)); mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds, - -1)); + SPLIT_INDEX_UNDEFINED)); } } } else { @@ -378,9 +386,9 @@ public class SplitDragPolicy implements DropTarget { ? mFullscreenStarter : mSplitscreenStarter; if (mSession.appData != null) { - launchApp(mSession, starter, position, hideTaskToken); + launchApp(mSession, starter, position, hideTaskToken, target.index); } else { - launchIntent(mSession, starter, position, hideTaskToken); + launchIntent(mSession, starter, position, hideTaskToken, target.index); } if (enableFlexibleSplit()) { @@ -392,9 +400,10 @@ public class SplitDragPolicy implements DropTarget { * Launches an app provided by SysUI. */ private void launchApp(DragSession session, Starter starter, @SplitPosition int position, - @Nullable WindowContainerToken hideTaskToken) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d", - position); + @Nullable WindowContainerToken hideTaskToken, @SplitIndex int splitIndex) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Launching app data at position=%d index=%d", + position, splitIndex); final ClipDescription description = session.getClipDescription(); final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK); final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT); @@ -429,7 +438,7 @@ public class SplitDragPolicy implements DropTarget { } } starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */, - position, opts, hideTaskToken); + position, opts, hideTaskToken, splitIndex); } } @@ -437,7 +446,7 @@ public class SplitDragPolicy implements DropTarget { * Launches an intent sender provided by an application. */ private void launchIntent(DragSession session, Starter starter, @SplitPosition int position, - @Nullable WindowContainerToken hideTaskToken) { + @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d", position); final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic(); @@ -452,7 +461,7 @@ public class SplitDragPolicy implements DropTarget { final Bundle opts = baseActivityOpts.toBundle(); starter.startIntent(session.launchableIntent, session.launchableIntent.getCreatorUserHandle().getIdentifier(), - null /* fillIntent */, position, opts, hideTaskToken); + null /* fillIntent */, position, opts, hideTaskToken, index); } @Override @@ -541,7 +550,7 @@ public class SplitDragPolicy implements DropTarget { @Nullable Bundle options, UserHandle user); void startIntent(PendingIntent intent, int userId, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, - @Nullable WindowContainerToken hideTaskToken); + @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index); void enterSplitScreen(int taskId, boolean leftOrTop); /** @@ -592,7 +601,7 @@ public class SplitDragPolicy implements DropTarget { @Override public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent, int position, @Nullable Bundle options, - @Nullable WindowContainerToken hideTaskToken) { + @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) { if (hideTaskToken != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Default starter does not support hide task token"); @@ -641,13 +650,13 @@ public class SplitDragPolicy implements DropTarget { final Rect hitRegion; // The approximate visual region for where the task will start final Rect drawRegion; - int index; + @SplitIndex int index; /** * @param index 0-indexed, represents which position of drop target this object represents, * 0 to N for left to right, top to bottom */ - public Target(@Type int t, Rect hit, Rect draw, int index) { + public Target(@Type int t, Rect hit, Rect draw, @SplitIndex int index) { type = t; hitRegion = hit; drawRegion = draw; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt index 9f532f57961d..54619528f2bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt @@ -22,6 +22,10 @@ import android.graphics.Rect import com.android.wm.shell.R import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.draganddrop.SplitDragPolicy.Target +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0 +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1 +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_2 +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_3 /** * Represents Drop Zone targets and animations for when the system is currently in a 2 app 50/50 @@ -98,7 +102,7 @@ class TwoFiftyFiftyTargetAnimator : DropTargetAnimSupplier { farStartBounds.right + halfDividerWidth, farStartBounds.bottom ), - farStartBounds, 0 + farStartBounds, SPLIT_INDEX_0 ) ) targets.add( @@ -110,7 +114,7 @@ class TwoFiftyFiftyTargetAnimator : DropTargetAnimSupplier { startBounds.right + halfDividerWidth, startBounds.bottom ), - startBounds, 1 + startBounds, SPLIT_INDEX_1 ) ) targets.add( @@ -120,7 +124,7 @@ class TwoFiftyFiftyTargetAnimator : DropTargetAnimSupplier { endBounds.left - halfDividerWidth, endBounds.top, endBounds.right, endBounds.bottom ), - endBounds, 2 + endBounds, SPLIT_INDEX_2 ) ) targets.add( @@ -130,7 +134,7 @@ class TwoFiftyFiftyTargetAnimator : DropTargetAnimSupplier { farEndBounds.left - halfDividerWidth, farEndBounds.top, farEndBounds.right, farEndBounds.bottom ), - farEndBounds, 3 + farEndBounds, SPLIT_INDEX_3 ) ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 417a6558ffcc..1c58dbbf71fd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -1308,6 +1308,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // otherwise a new transition will notify the relevant observers if (returningToApp && allAppsAreTranslucent(mPausingTasks)) { mHomeTransitionObserver.notifyHomeVisibilityChanged(true); + } else if (!toHome && mState == STATE_NEW_TASK + && allAppsAreTranslucent(mOpeningTasks)) { + // We are opening a translucent app. Launcher is still visible so we do nothing. } else if (!toHome) { // For some transitions, we may have notified home activity that it became // visible. We need to notify the observer that we are no longer going home. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 3e6d36ce0ca3..39ed9abcfd26 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -54,10 +54,27 @@ public interface SplitScreen { */ int STAGE_TYPE_SIDE = 1; + /** + * Position independent stage identifier for a given Stage + */ + int STAGE_TYPE_A = 2; + /** + * Position independent stage identifier for a given Stage + */ + int STAGE_TYPE_B = 3; + /** + * Position independent stage identifier for a given Stage + */ + int STAGE_TYPE_C = 4; + @IntDef(prefix = { "STAGE_TYPE_" }, value = { STAGE_TYPE_UNDEFINED, STAGE_TYPE_MAIN, - STAGE_TYPE_SIDE + STAGE_TYPE_SIDE, + // Used for flexible split + STAGE_TYPE_A, + STAGE_TYPE_B, + STAGE_TYPE_C }) @interface StageType {} @@ -128,6 +145,9 @@ public interface SplitScreen { case STAGE_TYPE_UNDEFINED: return "UNDEFINED"; case STAGE_TYPE_MAIN: return "MAIN"; case STAGE_TYPE_SIDE: return "SIDE"; + case STAGE_TYPE_A: return "STAGE_A"; + case STAGE_TYPE_B: return "STAGE_B"; + case STAGE_TYPE_C: return "STAGE_C"; default: return "UNKNOWN(" + stage + ")"; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 6398d31b4f82..4f0f6760a951 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -24,6 +24,7 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.wm.shell.Flags.enableFlexibleSplit; import static com.android.wm.shell.common.MultiInstanceHelper.getComponent; import static com.android.wm.shell.common.MultiInstanceHelper.getShortcutComponent; import static com.android.wm.shell.common.MultiInstanceHelper.samePackage; @@ -33,6 +34,9 @@ import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMes import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -98,6 +102,7 @@ import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; +import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.sysui.KeyguardChangeListener; @@ -325,7 +330,6 @@ public class SplitScreenController implements SplitDragPolicy.Starter, /** * @return an Array of RunningTaskInfo's ordered by leftToRight or topTopBottom */ - @Nullable public ActivityManager.RunningTaskInfo[] getAllTaskInfos() { // TODO(b/349828130) Add the third stage task info and not rely on positions ActivityManager.RunningTaskInfo topLeftTask = getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); @@ -335,7 +339,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, return new ActivityManager.RunningTaskInfo[]{topLeftTask, bottomRightTask}; } - return null; + return new ActivityManager.RunningTaskInfo[0]; } /** Check task is under split or not by taskId. */ @@ -405,7 +409,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, public void prepareEnterSplitScreen(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo taskInfo, int startPosition) { mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition, - false /* resizeAnim */); + false /* resizeAnim */, SPLIT_INDEX_UNDEFINED); } /** @@ -451,6 +455,24 @@ public class SplitScreenController implements SplitDragPolicy.Starter, } } + /** + * Determines which split index a new instance of a task should take. + * @param callingTask The task requesting a new instance. + * @return the split index of the new instance + */ + @SplitIndex + public int determineNewInstanceIndex(@NonNull ActivityManager.RunningTaskInfo callingTask) { + if (!enableFlexibleSplit()) { + throw new IllegalStateException("Use determineNewInstancePosition"); + } + if (callingTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + || getSplitPosition(callingTask.taskId) == SPLIT_POSITION_TOP_OR_LEFT) { + return SPLIT_INDEX_1; + } else { + return SPLIT_INDEX_0; + } + } + public void enterSplitScreen(int taskId, boolean leftOrTop) { enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction()); } @@ -685,7 +707,10 @@ public class SplitScreenController implements SplitDragPolicy.Starter, ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntentWithInstanceId: reason=%d", ENTER_REASON_LAUNCHER); mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER); - startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */); + // TODO(b/349828130) currently pass in index_undefined until we can revisit these + // specific cases in the future. Only focusing on parity with starting intent/task + startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */, + SPLIT_INDEX_UNDEFINED); } private void startIntentAndTask(PendingIntent pendingIntent, int userId1, @@ -775,9 +800,9 @@ public class SplitScreenController implements SplitDragPolicy.Starter, @Override public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, - @Nullable WindowContainerToken hideTaskToken) { + @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) { startIntent(intent, userId1, fillInIntent, position, options, hideTaskToken, - false /* forceLaunchNewTask */); + false /* forceLaunchNewTask */, index); } /** @@ -790,7 +815,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, */ public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, - @Nullable WindowContainerToken hideTaskToken, boolean forceLaunchNewTask) { + @Nullable WindowContainerToken hideTaskToken, boolean forceLaunchNewTask, + @SplitIndex int index) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1, fillInIntent, position); @@ -816,7 +842,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, if (taskInfo != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Found suitable background task=%s", taskInfo); - mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken); + mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken, index); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background"); return; @@ -841,7 +867,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, } } - mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken); + mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken, + index); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 840049412db4..3091be574a53 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static com.android.wm.shell.Flags.enableFlexibleSplit; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN; @@ -55,6 +56,9 @@ import com.android.wm.shell.transition.OneShotRemoteHandler; import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; /** Manages transition animations for split-screen. */ @@ -268,22 +272,21 @@ class SplitScreenTransitions { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, - @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) { + @NonNull Map<WindowContainerToken, SplitDecorManager> rootDecorMap) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playResizeAnimation: transition=%d", info.getDebugId()); initTransition(transition, finishTransaction, finishCallback); + Set<WindowContainerToken> rootDecorKeys = rootDecorMap.keySet(); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) { + if (rootDecorKeys.contains(change.getContainer())) { final SurfaceControl leash = change.getLeash(); startTransaction.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); - SplitDecorManager decor = mainRoot.equals(change.getContainer()) - ? mainDecor : sideDecor; + SplitDecorManager decor = rootDecorMap.get(change.getContainer()); // This is to ensure onFinished be called after all animations ended. ValueAnimator va = new ValueAnimator(); @@ -433,15 +436,22 @@ class SplitScreenTransitions { Transitions.TransitionHandler handler, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishCallback, - @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) { + @Nullable SplitDecorManager mainDecor, @Nullable SplitDecorManager sideDecor, + @Nullable List<SplitDecorManager> decorManagers) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition deduced Resize split screen."); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b", mPendingResize != null); if (mPendingResize != null) { mPendingResize.cancel(null); - mainDecor.cancelRunningAnimations(); - sideDecor.cancelRunningAnimations(); + if (enableFlexibleSplit()) { + for (SplitDecorManager stage : decorManagers) { + stage.cancelRunningAnimations(); + } + } else { + mainDecor.cancelRunningAnimations(); + sideDecor.cancelRunningAnimations(); + } mAnimations.clear(); onFinish(null /* wct */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 81b96cd62b14..f07ae4465341 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -33,6 +33,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; +import static com.android.wm.shell.Flags.enableFlexibleSplit; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; import static com.android.wm.shell.common.split.SplitScreenUtils.isPartiallyOffscreen; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; @@ -43,6 +44,7 @@ import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; import static com.android.wm.shell.shared.TransitionUtil.isOrderOnly; import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1; @@ -51,9 +53,12 @@ import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.shared.split.SplitScreenConstants.splitPositionToString; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_A; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_B; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; @@ -142,6 +147,7 @@ import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.shared.split.SplitBounds; import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; +import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; @@ -153,11 +159,15 @@ import dalvik.annotation.optimization.NeverCompile; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Predicate; /** * Coordinates the staging (visibility, sizing, ...) of the split-screen stages. @@ -178,10 +188,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // entered private static final int DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS = 1000; - private final StageTaskListener mMainStage; - private final StageTaskListener mSideStage; + private StageTaskListener mMainStage; + private StageTaskListener mSideStage; @SplitPosition private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; + private StageOrderOperator mStageOrderOperator; private final int mDisplayId; private SplitLayout mSplitLayout; @@ -335,22 +346,32 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task"); - mMainStage = new StageTaskListener( - mContext, - mTaskOrganizer, - mDisplayId, - this /*stageListenerCallbacks*/, - mSyncQueue, - iconProvider, - mWindowDecorViewModel); - mSideStage = new StageTaskListener( - mContext, - mTaskOrganizer, - mDisplayId, - this /*stageListenerCallbacks*/, - mSyncQueue, - iconProvider, - mWindowDecorViewModel); + if (enableFlexibleSplit()) { + mStageOrderOperator = new StageOrderOperator(mContext, + mTaskOrganizer, + mDisplayId, + this /*stageListenerCallbacks*/, + mSyncQueue, + iconProvider, + mWindowDecorViewModel); + } else { + mMainStage = new StageTaskListener( + mContext, + mTaskOrganizer, + mDisplayId, + this /*stageListenerCallbacks*/, + mSyncQueue, + iconProvider, + mWindowDecorViewModel, STAGE_TYPE_MAIN); + mSideStage = new StageTaskListener( + mContext, + mTaskOrganizer, + mDisplayId, + this /*stageListenerCallbacks*/, + mSyncQueue, + iconProvider, + mWindowDecorViewModel, STAGE_TYPE_SIDE); + } mDisplayController = displayController; mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; @@ -422,24 +443,63 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } public boolean isSplitScreenVisible() { - return mSideStage.mVisible && mMainStage.mVisible; + if (enableFlexibleSplit()) { + return runForActiveStagesAllMatch((stage) -> stage.mVisible); + } else { + return mSideStage.mVisible && mMainStage.mVisible; + } } - private void activateSplit(WindowContainerTransaction wct, boolean includingTopTask) { - mMainStage.activate(wct, includingTopTask); + /** + * @param includingTopTask reparents the current top task into the stage defined by index + * (or mainStage in legacy split) + * @param index the index to move the current visible task into, if undefined will arbitrarily + * choose a stage to launch into + */ + private void activateSplit(WindowContainerTransaction wct, boolean includingTopTask, + int index) { + if (enableFlexibleSplit()) { + mStageOrderOperator.onEnteringSplit(SNAP_TO_2_50_50); + if (index == SPLIT_INDEX_UNDEFINED || !includingTopTask) { + // If we aren't includingTopTask, then the call to activate on the stage is + // effectively a no-op. Previously the stage kept track of the "isActive" state, + // but now that gets set in the "onEnteringSplit" call above. + // + // index == UNDEFINED case might change, but as of now no use case where we activate + // without an index specified. + return; + } + @SplitIndex int oppositeIndex = index == SPLIT_INDEX_0 ? SPLIT_INDEX_1 : SPLIT_INDEX_0; + StageTaskListener activatingStage = mStageOrderOperator.getStageForIndex(oppositeIndex); + activatingStage.activate(wct, includingTopTask); + } else { + mMainStage.activate(wct, includingTopTask); + } } public boolean isSplitActive() { - return mMainStage.isActive(); + if (enableFlexibleSplit()) { + return mStageOrderOperator.isActive(); + } else { + return mMainStage.isActive(); + } } /** * Deactivates main stage by removing the stage from the top level split root (usually when a * task underneath gets removed from the stage root). - * @param reparentToTop whether we want to put the stage root back on top + * @param stageToTop stage which we want to put on top */ - private void deactivateSplit(WindowContainerTransaction wct, boolean reparentToTop) { - mMainStage.deactivate(wct, reparentToTop); + private void deactivateSplit(WindowContainerTransaction wct, @StageType int stageToTop) { + if (enableFlexibleSplit()) { + StageTaskListener stageToDeactivate = mStageOrderOperator.getAllStages().stream() + .filter(stage -> stage.getId() == stageToTop) + .findFirst().orElseThrow(); + stageToDeactivate.deactivate(wct, true /*toTop*/); + mStageOrderOperator.onExitingSplit(); + } else { + mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); + } } /** @return whether this transition-request has the launch-adjacent flag. */ @@ -463,11 +523,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // If one of the splitting tasks support auto-pip, wm-core might reparent the task to TDA // and file a TRANSIT_PIP transition when finishing transitions. // @see com.android.server.wm.RootWindowContainer#moveActivityToPinnedRootTask - if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { - return true; + if (enableFlexibleSplit()) { + return mStageOrderOperator.getActiveStages().stream() + .anyMatch(stage -> stage.getChildCount() == 0); + } else { + return mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0; } - - return false; } /** Checks if `transition` is a pending enter-split transition. */ @@ -477,10 +538,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @StageType int getStageOfTask(int taskId) { - if (mMainStage.containsTask(taskId)) { - return STAGE_TYPE_MAIN; - } else if (mSideStage.containsTask(taskId)) { - return STAGE_TYPE_SIDE; + if (enableFlexibleSplit()) { + StageTaskListener stageTaskListener = mStageOrderOperator.getActiveStages().stream() + .filter(stage -> stage.containsTask(taskId)) + .findFirst().orElse(null); + if (stageTaskListener != null) { + return stageTaskListener.getId(); + } + } else { + if (mMainStage.containsTask(taskId)) { + return STAGE_TYPE_MAIN; + } else if (mSideStage.containsTask(taskId)) { + return STAGE_TYPE_SIDE; + } } return STAGE_TYPE_UNDEFINED; @@ -490,14 +560,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskId) { return true; } - return mMainStage.isRootTaskId(taskId) || mSideStage.isRootTaskId(taskId); + if (enableFlexibleSplit()) { + return mStageOrderOperator.getActiveStages().stream() + .anyMatch((stage) -> stage.isRootTaskId(taskId)); + } else { + return mMainStage.isRootTaskId(taskId) || mSideStage.isRootTaskId(taskId); + } } boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, WindowContainerTransaction wct) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveToStage: task=%d position=%d", task.taskId, stagePosition); - prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */); + // TODO(b/349828130) currently pass in index_undefined until we can revisit these + // specific cases in the future. Only focusing on parity with starting intent/task + prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */, + SPLIT_INDEX_UNDEFINED); mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, isSplitScreenVisible() @@ -595,11 +673,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * same window container transaction as the starting of the intent. */ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options, - @Nullable WindowContainerToken hideTaskToken) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position); + @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d index=%d", + taskId, position, index); mSplitRequest = new SplitRequest(taskId, position); final WindowContainerTransaction wct = new WindowContainerTransaction(); - options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); + options = enableFlexibleSplit() + ? resolveStartStageForIndex(options, null /*wct*/, index) + : resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); if (hideTaskToken != null) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom"); wct.reorder(hideTaskToken, false /* onTop */); @@ -623,7 +704,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // If split screen is not activated, we're expecting to open a pair of apps to split. final int extraTransitType = isSplitActive() ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; - prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering); + prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index); mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, extraTransitType, !mIsDropEntering); @@ -635,13 +716,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * same window container transaction as the starting of the intent. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, - @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken) { + @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken, + @SplitIndex int index) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(), position); mSplitRequest = new SplitRequest(intent.getIntent(), position); final WindowContainerTransaction wct = new WindowContainerTransaction(); - options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); + options = enableFlexibleSplit() + ? resolveStartStageForIndex(options, null /*wct*/, index) + : resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); if (hideTaskToken != null) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom"); wct.reorder(hideTaskToken, false /* onTop */); @@ -666,13 +750,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // If split screen is not activated, we're expecting to open a pair of apps to split. final int extraTransitType = isSplitActive() ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; - prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering); + prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index); mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, extraTransitType, !mIsDropEntering); } - /** Starts 2 tasks in one transition. */ + /** + * Starts 2 tasks in one transition. + * @param taskId1 starts in the mSideStage + * @param taskId2 starts in the mainStage #startWithTask() + */ void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { @@ -687,11 +775,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); - addActivityOptions(options1, mSideStage); + StageTaskListener stageForTask1; + if (enableFlexibleSplit()) { + stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition, + true /*checkAllStagesIfNotActive*/); + } else { + stageForTask1 = mSideStage; + } + addActivityOptions(options1, stageForTask1); prepareTasksForSplitScreen(new int[] {taskId1, taskId2}, wct); wct.startTask(taskId1, options1); - startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId); + startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId, + splitPosition); } /** Start an intent and a task to a split pair in one transition. */ @@ -721,7 +817,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.sendPendingIntent(pendingIntent, fillInIntent, options1); prepareTasksForSplitScreen(new int[] {taskId}, wct); - startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); + startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId, + splitPosition); } /** @@ -765,7 +862,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); prepareTasksForSplitScreen(new int[] {taskId}, wct); - startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId); + startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId, + splitPosition); } /** @@ -795,11 +893,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, */ private void startWithTask(WindowContainerTransaction wct, int mainTaskId, @Nullable Bundle mainOptions, @PersistentSnapPosition int snapPosition, - @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + @Nullable RemoteTransition remoteTransition, InstanceId instanceId, + @SplitPosition int splitPosition) { if (!isSplitActive()) { // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. - activateSplit(wct, false /* reparentToTop */); + // TODO(b/349828130) currently pass in index_undefined until we can revisit these + // specific cases in the future. Only focusing on parity with starting intent/task + activateSplit(wct, false /* reparentToTop */, SPLIT_INDEX_UNDEFINED); } mSplitLayout.setDivideRatio(snapPosition); updateWindowBounds(mSplitLayout, wct); @@ -807,10 +908,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false /* reparentLeafTaskIfRelaunch */); setRootForceTranslucent(false, wct); - + // All callers of this method set the correct activity options on mSideStage, + // so we choose the opposite stage for this method + StageTaskListener stage; + if (enableFlexibleSplit()) { + stage = mStageOrderOperator + .getStageForLegacyPosition(reverseSplitPosition(splitPosition), + false /*checkAllStagesIfNotActive*/); + } else { + stage = mMainStage; + } // Make sure the launch options will put tasks in the corresponding split roots mainOptions = mainOptions != null ? mainOptions : new Bundle(); - addActivityOptions(mainOptions, mMainStage); + addActivityOptions(mainOptions, stage); // Add task launch requests wct.startTask(mainTaskId, mainOptions); @@ -866,7 +976,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!isSplitActive()) { // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. - activateSplit(wct, false /* reparentToTop */); + // TODO(b/349828130) currently pass in index_undefined until we can revisit these + // specific cases in the future. Only focusing on parity with starting intent/task + activateSplit(wct, false /* reparentToTop */, SPLIT_INDEX_UNDEFINED); } setSideStagePosition(splitPosition, wct); @@ -974,6 +1086,31 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.evictInvisibleChildren(wct); } + /** + * @param index for the new stage that will be opening. Ex. if app is dragged to + * index=1, then this will tell the stage at index=1 to launch the task + * in the wct in that stage. This doesn't verify that the non-specified + * indices' stages have their tasks correctly set/re-parented. + */ + Bundle resolveStartStageForIndex(@Nullable Bundle options, + @Nullable WindowContainerTransaction wct, + @SplitIndex int index) { + StageTaskListener oppositeStage; + if (index == SPLIT_INDEX_UNDEFINED) { + // Arbitrarily choose a stage + oppositeStage = mStageOrderOperator.getStageForIndex(SPLIT_INDEX_1); + } else { + oppositeStage = mStageOrderOperator.getStageForIndex(index); + } + if (options == null) { + options = new Bundle(); + } + updateStageWindowBoundsForIndex(wct, index); + addActivityOptions(options, oppositeStage); + + return options; + } + Bundle resolveStartStage(@StageType int stage, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerTransaction wct) { switch (stage) { @@ -1041,20 +1178,35 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return INVALID_TASK_ID; } - return mSideStagePosition == splitPosition - ? mSideStage.getTopVisibleChildTaskId() - : mMainStage.getTopVisibleChildTaskId(); + if (enableFlexibleSplit()) { + StageTaskListener stage = mStageOrderOperator.getStageForLegacyPosition(splitPosition, + true /*checkAllStagesIfNotActive*/); + return stage != null ? stage.getTopVisibleChildTaskId() : INVALID_TASK_ID; + } else { + return mSideStagePosition == splitPosition + ? mSideStage.getTopVisibleChildTaskId() + : mMainStage.getTopVisibleChildTaskId(); + } } void switchSplitPosition(String reason) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "switchSplitPosition"); final SurfaceControl.Transaction t = mTransactionPool.acquire(); mTempRect1.setEmpty(); - final StageTaskListener topLeftStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; - final StageTaskListener bottomRightStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; - + final StageTaskListener topLeftStage; + final StageTaskListener bottomRightStage; + if (enableFlexibleSplit()) { + topLeftStage = mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT, + false /*checkAllStagesIfNotActive*/); + bottomRightStage = mStageOrderOperator + .getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, + false /*checkAllStagesIfNotActive*/); + } else { + topLeftStage = + mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; + bottomRightStage = + mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; + } // Don't allow windows or divider to be focused during animation (mRootTaskInfo is the // parent of all 3 leaves). We don't want the user to be able to tap and focus a window // while it is moving across the screen, because granting focus also recalculates the @@ -1091,9 +1243,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, }); ProtoLog.v(WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason); - mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), - getSideStagePosition(), mSideStage.getTopChildTaskUid(), - mSplitLayout.isLeftRightSplit()); + if (enableFlexibleSplit()) { + // TODO(b/374825718) update logging for 2+ apps + } else { + mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), + getSideStagePosition(), mSideStage.getTopChildTaskUid(), + mSplitLayout.isLeftRightSplit()); + } } void setSideStagePosition(@SplitPosition int sideStagePosition, @@ -1101,8 +1257,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mSideStagePosition == sideStagePosition) return; mSideStagePosition = sideStagePosition; sendOnStagePositionChanged(); + StageTaskListener stage = enableFlexibleSplit() + ? mStageOrderOperator.getStageForLegacyPosition(mSideStagePosition, + true /*checkAllStagesIfNotActive*/) + : mSideStage; - if (mSideStage.mVisible) { + if (stage.mVisible) { + if (wct == null) { + // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds. + onLayoutSizeChanged(mSplitLayout); + } else { + updateWindowBounds(mSplitLayout, wct); + sendOnBoundsChanged(); + } + } + } + + private void updateStageWindowBoundsForIndex(@Nullable WindowContainerTransaction wct, + @SplitIndex int index) { + StageTaskListener stage = mStageOrderOperator.getStageForIndex(index); + if (stage.mVisible) { if (wct == null) { // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds. onLayoutSizeChanged(mSplitLayout); @@ -1152,10 +1326,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void recordLastActiveStage() { if (!isSplitActive() || !isSplitScreenVisible()) { mLastActiveStage = STAGE_TYPE_UNDEFINED; - } else if (mMainStage.isFocused()) { - mLastActiveStage = STAGE_TYPE_MAIN; - } else if (mSideStage.isFocused()) { - mLastActiveStage = STAGE_TYPE_SIDE; + } else if (enableFlexibleSplit()) { + mStageOrderOperator.getActiveStages().stream() + .filter(StageTaskListener::isFocused) + .findFirst() + .ifPresent(stage -> mLastActiveStage = stage.getId()); + } else { + if (mMainStage.isFocused()) { + mLastActiveStage = STAGE_TYPE_MAIN; + } else if (mSideStage.isFocused()) { + mLastActiveStage = STAGE_TYPE_SIDE; + } } } @@ -1212,7 +1393,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.getInvisibleBounds(mTempRect1); if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) { mSideStage.removeAllTasks(wct, false /* toTop */); - deactivateSplit(wct, false /* reparentToTop */); + deactivateSplit(wct, STAGE_TYPE_UNDEFINED); wct.reorder(mRootTaskInfo.token, false /* onTop */); setRootForceTranslucent(true, wct); wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); @@ -1241,7 +1422,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, childrenToTop.fadeOutDecor(() -> { WindowContainerTransaction finishedWCT = new WindowContainerTransaction(); mIsExiting = false; - deactivateSplit(finishedWCT, childrenToTop == mMainStage /* reparentToTop */); + deactivateSplit(finishedWCT, childrenToTop.getId()); mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); setRootForceTranslucent(true, finishedWCT); @@ -1369,8 +1550,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent(recentTasks -> { // Notify recents if we are exiting in a way that breaks the pair, and disable further // updates to splits in the recents until we enter split again - mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); - mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); + if (enableFlexibleSplit()) { + runForActiveStages((stage) -> + stage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId))); + } else { + mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); + mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); + } }); logExit(exitReason); } @@ -1383,15 +1569,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void prepareExitSplitScreen(@StageType int stageToTop, @NonNull WindowContainerTransaction wct) { if (!isSplitActive()) return; - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%d", stageToTop); - mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); - deactivateSplit(wct, stageToTop == STAGE_TYPE_MAIN); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%s", + stageTypeToString(stageToTop)); + if (enableFlexibleSplit()) { + mStageOrderOperator.getActiveStages().stream() + .filter(stage -> stage.getId() != stageToTop) + .forEach(stage -> stage.removeAllTasks(wct, false /*toTop*/)); + } else { + mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); + } + deactivateSplit(wct, stageToTop); } private void prepareEnterSplitScreen(WindowContainerTransaction wct) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen"); prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED, - !mIsDropEntering); + !mIsDropEntering, SPLIT_INDEX_UNDEFINED); } /** @@ -1400,7 +1593,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, */ void prepareEnterSplitScreen(WindowContainerTransaction wct, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, - boolean resizeAnim) { + boolean resizeAnim, @SplitIndex int index) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen: position=%d resize=%b", startPosition, resizeAnim); onSplitScreenEnter(); @@ -1412,7 +1605,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isSplitActive()) { prepareBringSplit(wct, taskInfo, startPosition, resizeAnim); } else { - prepareActiveSplit(wct, taskInfo, startPosition, resizeAnim); + prepareActiveSplit(wct, taskInfo, startPosition, resizeAnim, index); } } @@ -1433,14 +1626,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!mSkipEvictingMainStageChildren) { mMainStage.evictAllChildren(wct); } - mMainStage.reparentTopTask(wct); + // TODO(b/349828130) revisit bring split from BG to FG scenarios + if (enableFlexibleSplit()) { + runForActiveStages(stage -> stage.reparentTopTask(wct)); + } else { + mMainStage.reparentTopTask(wct); + } prepareSplitLayout(wct, resizeAnim); } } + /** + * @param index The index that has already been assigned a stage + */ private void prepareActiveSplit(WindowContainerTransaction wct, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, - boolean resizeAnim) { + boolean resizeAnim, @SplitIndex int index) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareActiveSplit: task=%d isSplitVisible=%b", taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible()); // We handle split visibility itself on shell transition, but sometimes we didn't @@ -1450,7 +1651,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSideStagePosition(startPosition, wct); mSideStage.addTask(taskInfo, wct); } - activateSplit(wct, true /* reparentToTop */); + activateSplit(wct, true /* reparentToTop */, index); prepareSplitLayout(wct, resizeAnim); } @@ -1477,8 +1678,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen"); mSplitLayout.update(null, true /* resetImePosition */); - mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash); - mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash); + if (enableFlexibleSplit()) { + runForActiveStages((stage) -> + stage.getSplitDecorManager().inflate(mContext, stage.mRootLeash)); + } else { + mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash); + mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash); + } setDividerVisibility(true, finishT); // Ensure divider surface are re-parented back into the hierarchy at the end of the // transition. See Transition#buildFinishTransaction for more detail. @@ -1492,6 +1698,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitRequest = null; updateRecentTasksSplitPair(); + if (enableFlexibleSplit()) { + // TODO(b/374825718) log 2+ apps + return; + } mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), getMainStagePosition(), mMainStage.getTopChildTaskUid(), getSideStagePosition(), mSideStage.getTopChildTaskUid(), @@ -1503,6 +1713,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, outBottomOrRightBounds.set(mSplitLayout.getBounds2()); } + private void runForActiveStages(Consumer<StageTaskListener> consumer) { + mStageOrderOperator.getActiveStages().forEach(consumer); + } + + private boolean runForActiveStagesAllMatch(Predicate<StageTaskListener> predicate) { + List<StageTaskListener> activeStages = mStageOrderOperator.getActiveStages(); + return !activeStages.isEmpty() && activeStages.stream().allMatch(predicate); + } + @SplitPosition int getSplitPosition(int taskId) { if (mSideStage.getTopVisibleChildTaskId() == taskId) { @@ -1516,6 +1735,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) { ActivityOptions options = ActivityOptions.fromBundle(opts); if (launchTarget != null) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "addActivityOptions setting launch root for stage=%s", + stageTypeToString(launchTarget.getId())); options.setLaunchRootTask(launchTarget.mRootTaskInfo.token); } // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split @@ -1559,8 +1781,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, listener.onSplitBoundsChanged(mSplitLayout.getRootBounds(), getMainStageBounds(), getSideStageBounds()); } - mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE); - mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN); + if (enableFlexibleSplit()) { + // TODO(b/349828130) replace w/ stageID + mStageOrderOperator.getAllStages().forEach( + stage -> stage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_UNDEFINED) + ); + } else { + mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE); + mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN); + } } private void sendOnStagePositionChanged() { @@ -1584,17 +1813,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean present, boolean visible) { int stage; if (present) { - stage = stageListener == mSideStage ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; + if (enableFlexibleSplit()) { + stage = stageListener.getId(); + } else { + stage = stageListener == mSideStage ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; + } } else { // No longer on any stage stage = STAGE_TYPE_UNDEFINED; } - if (stage == STAGE_TYPE_MAIN) { - mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(), - mSplitLayout.isLeftRightSplit()); - } else if (stage == STAGE_TYPE_SIDE) { - mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(), - mSplitLayout.isLeftRightSplit()); + if (!enableFlexibleSplit()) { + if (stage == STAGE_TYPE_MAIN) { + mLogger.logMainStageAppChange(getMainStagePosition(), + mMainStage.getTopChildTaskUid(), + mSplitLayout.isLeftRightSplit()); + } else if (stage == STAGE_TYPE_SIDE) { + mLogger.logSideStageAppChange(getSideStagePosition(), + mSideStage.getTopChildTaskUid(), + mSplitLayout.isLeftRightSplit()); + } } if (present) { updateRecentTasksSplitPair(); @@ -1622,17 +1859,35 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent(recentTasks -> { Rect topLeftBounds = mSplitLayout.getBounds1(); Rect bottomRightBounds = mSplitLayout.getBounds2(); - int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId(); - int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId(); + int sideStageTopTaskId; + int mainStageTopTaskId; + if (enableFlexibleSplit()) { + List<StageTaskListener> activeStages = mStageOrderOperator.getActiveStages(); + if (activeStages.size() != 2) { + sideStageTopTaskId = mainStageTopTaskId = INVALID_TASK_ID; + } else { + // doesn't matter which one we assign to? What matters is the order of 0 and 1? + mainStageTopTaskId = activeStages.get(0).getTopVisibleChildTaskId(); + sideStageTopTaskId = activeStages.get(1).getTopVisibleChildTaskId(); + } + } else { + mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId(); + sideStageTopTaskId= mSideStage.getTopVisibleChildTaskId(); + } boolean sideStageTopLeft = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; int leftTopTaskId; int rightBottomTaskId; - if (sideStageTopLeft) { - leftTopTaskId = sideStageTopTaskId; - rightBottomTaskId = mainStageTopTaskId; - } else { + if (enableFlexibleSplit()) { leftTopTaskId = mainStageTopTaskId; rightBottomTaskId = sideStageTopTaskId; + } else { + if (sideStageTopLeft) { + leftTopTaskId = sideStageTopTaskId; + rightBottomTaskId = mainStageTopTaskId; + } else { + leftTopTaskId = mainStageTopTaskId; + rightBottomTaskId = sideStageTopTaskId; + } } if (Flags.enableFlexibleTwoAppSplit()) { @@ -1741,29 +1996,59 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @VisibleForTesting @Override public void onRootTaskAppeared() { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b", - mRootTaskInfo, mMainStage.mHasRootTask, mSideStage.mHasRootTask); + if (enableFlexibleSplit()) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s", + mRootTaskInfo); + mStageOrderOperator.getAllStages().forEach(stage -> { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + " onRootStageAppeared stageId=%s hasRoot=%b", + stageTypeToString(stage.getId()), stage.mHasRootTask); + }); + } else { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b", + mRootTaskInfo, mMainStage.mHasRootTask, mSideStage.mHasRootTask); + } + boolean notAllStagesHaveRootTask; + if (enableFlexibleSplit()) { + notAllStagesHaveRootTask = mStageOrderOperator.getAllStages().stream() + .anyMatch((stage) -> !stage.mHasRootTask); + } else { + notAllStagesHaveRootTask = !mMainStage.mHasRootTask + || !mSideStage.mHasRootTask; + } // Wait unit all root tasks appeared. - if (mRootTaskInfo == null - || !mMainStage.mHasRootTask - || !mSideStage.mHasRootTask) { + if (mRootTaskInfo == null || notAllStagesHaveRootTask) { return; } final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true); - wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true); + if (enableFlexibleSplit()) { + mStageOrderOperator.getAllStages().forEach(stage -> + wct.reparent(stage.mRootTaskInfo.token, mRootTaskInfo.token, true)); + } else { + wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true); + wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true); + } - // Make the stages adjacent to each other so they occlude what's behind them. - wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); setRootForceTranslucent(true, wct); - mSplitLayout.getInvisibleBounds(mTempRect1); - wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); + if (!enableFlexibleSplit()) { + //TODO(b/373709676) Need to figure out how adjacentRoots work for flex split + + // Make the stages adjacent to each other so they occlude what's behind them. + wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); + mSplitLayout.getInvisibleBounds(mTempRect1); + wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); + } mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> { - t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top); - }); - mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token); + if (!enableFlexibleSplit()) { + mSyncQueue.runInSync(t -> { + t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top); + }); + mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token); + } else { + // TODO(b/373709676) Need to figure out how adjacentRoots work for flex split + } } @Override @@ -1958,15 +2243,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) { + public void onSnappedToDismiss(boolean closedBottomRightStage, @ExitReason int exitReason) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s", - bottomOrRight, exitReasonToString(exitReason)); - final boolean mainStageToTop = - bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT + closedBottomRightStage, exitReasonToString(exitReason)); + boolean mainStageToTop = + closedBottomRightStage ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; - final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage; - - final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage; + int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + if (enableFlexibleSplit()) { + toTopStage = mStageOrderOperator.getStageForLegacyPosition(closedBottomRightStage + ? SPLIT_POSITION_TOP_OR_LEFT + : SPLIT_POSITION_BOTTOM_OR_RIGHT, + false /*checkAllStagesIfNotActive*/); + dismissTop = toTopStage.getId(); + } final WindowContainerTransaction wct = new WindowContainerTransaction(); toTopStage.resetBounds(wct); prepareExitSplitScreen(dismissTop, wct); @@ -1998,8 +2289,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, updateSurfaceBounds(layout, t, shouldUseParallaxEffect); getMainStageBounds(mTempRect1); getSideStageBounds(mTempRect2); - mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately); - mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately); + if (enableFlexibleSplit()) { + StageTaskListener ltStage = + mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT, + false /*checkAllStagesIfNotActive*/); + StageTaskListener brStage = + mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, + false /*checkAllStagesIfNotActive*/); + ltStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately); + brStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately); + } else { + mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, + mShowDecorImmediately); + mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, + mShowDecorImmediately); + } t.apply(); mTransactionPool.release(t); } @@ -2015,19 +2319,33 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!sizeChanged) { // We still need to resize on decor for ensure all current status clear. final SurfaceControl.Transaction t = mTransactionPool.acquire(); - mMainStage.onResized(t); - mSideStage.onResized(t); + if (enableFlexibleSplit()) { + runForActiveStages(stage -> stage.onResized(t)); + } else { + mMainStage.onResized(t); + mSideStage.onResized(t); + } mTransactionPool.release(t); return; } - + List<SplitDecorManager> decorManagers = new ArrayList<>(); + SplitDecorManager mainDecor = null; + SplitDecorManager sideDecor = null; + if (enableFlexibleSplit()) { + decorManagers = mStageOrderOperator.getActiveStages().stream() + .map(StageTaskListener::getSplitDecorManager) + .toList(); + } else { + mainDecor = mMainStage.getSplitDecorManager(); + sideDecor = mSideStage.getSplitDecorManager(); + } sendOnBoundsChanged(); mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart"); mSplitTransitions.startResizeTransition(wct, this, (aborted) -> { mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed"); }, (finishWct, t) -> { mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"); - }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager()); + }, mainDecor, sideDecor, decorManagers); if (Flags.enableFlexibleTwoAppSplit()) { switch (layout.calculateCurrentSnapPosition()) { @@ -2054,10 +2372,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * @return true if stage bounds actually . */ private boolean updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) { - final StageTaskListener topLeftStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; - final StageTaskListener bottomRightStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; + final StageTaskListener topLeftStage; + final StageTaskListener bottomRightStage; + if (enableFlexibleSplit()) { + topLeftStage = mStageOrderOperator + .getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT, + true /*checkAllStagesIfNotActive*/); + bottomRightStage = mStageOrderOperator + .getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, + true /*checkAllStagesIfNotActive*/); + } else { + topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT + ? mSideStage + : mMainStage; + bottomRightStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT + ? mMainStage + : mSideStage; + } boolean updated = layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateWindowBounds: topLeftStage=%s bottomRightStage=%s", @@ -2067,10 +2398,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t, boolean applyResizingOffset) { - final StageTaskListener topLeftStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; - final StageTaskListener bottomRightStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; + final StageTaskListener topLeftStage; + final StageTaskListener bottomRightStage; + if (enableFlexibleSplit()) { + topLeftStage = mStageOrderOperator + .getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT, + true /*checkAllStagesIfNotActive*/); + bottomRightStage = mStageOrderOperator + .getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, + true /*checkAllStagesIfNotActive*/); + } else { + topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT + ? mSideStage + : mMainStage; + bottomRightStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT + ? mMainStage + : mSideStage; + } (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer, applyResizingOffset); @@ -2085,10 +2429,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - if (mMainStage.containsToken(token)) { - return getMainStagePosition(); - } else if (mSideStage.containsToken(token)) { - return getSideStagePosition(); + if (enableFlexibleSplit()) { + // We could migrate to/return the new INDEX enums here since most callers just care that + // this value isn't SPLIT_POSITION_UNDEFINED, but + // ImePositionProcessor#getImeTargetPosition actually uses the leftTop/bottomRight value + StageTaskListener stageForToken = mStageOrderOperator.getAllStages().stream() + .filter(stage -> stage.containsToken(token)) + .findFirst().orElse(null); + return stageForToken == null + ? SPLIT_POSITION_UNDEFINED + : mStageOrderOperator.getLegacyPositionForStage(stageForToken); + } else { + if (mMainStage.containsToken(token)) { + return getMainStagePosition(); + } else if (mSideStage.containsToken(token)) { + return getSideStagePosition(); + } } return SPLIT_POSITION_UNDEFINED; @@ -2193,19 +2549,41 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1(); } + /** + * TODO(b/349828130) Currently the way this is being used is only to to get the bottomRight + * stage. Eventually we'll need to rename and for now we'll repurpose the method to return + * the bottomRight bounds under the flex split flag + */ private void getSideStageBounds(Rect rect) { - if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) { + if (enableFlexibleSplit()) { + // Split Layout doesn't actually keep track of the bounds based on the stage, + // it only knows that bounds1 is leftTop position and bounds2 is bottomRight position + // We'll then assume this method is to get bounds of bottomRight stage + mSplitLayout.getBounds2(rect); + } else if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) { mSplitLayout.getBounds1(rect); } else { mSplitLayout.getBounds2(rect); } } + /** + * TODO(b/349828130) Currently the way this is being used is only to to get the leftTop + * stage. Eventually we'll need to rename and for now we'll repurpose the method to return + * the leftTop bounds under the flex split flag + */ private void getMainStageBounds(Rect rect) { - if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) { - mSplitLayout.getBounds2(rect); - } else { + if (enableFlexibleSplit()) { + // Split Layout doesn't actually keep track of the bounds based on the stage, + // it only knows that bounds1 is leftTop position and bounds2 is bottomRight position + // We'll then assume this method is to get bounds of topLeft stage mSplitLayout.getBounds1(rect); + } else { + if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) { + mSplitLayout.getBounds2(rect); + } else { + mSplitLayout.getBounds1(rect); + } } } @@ -2214,14 +2592,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * this task (yet) so this can also be used to identify which stage to put a task into. */ private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) { - // TODO(b/184679596): Find a way to either include task-org information in the transition, - // or synchronize task-org callbacks so we can use stage.containsTask - if (mMainStage.mRootTaskInfo != null - && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) { - return mMainStage; - } else if (mSideStage.mRootTaskInfo != null - && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) { - return mSideStage; + if (enableFlexibleSplit()) { + return mStageOrderOperator.getActiveStages().stream() + .filter((stage) -> stage.mRootTaskInfo != null && + taskInfo.parentTaskId == stage.mRootTaskInfo.taskId + ) + .findFirst() + .orElse(null); + } else { + // TODO(b/184679596): Find a way to either include task-org information in the + // transition, or synchronize task-org callbacks so we can use stage.containsTask + if (mMainStage.mRootTaskInfo != null + && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) { + return mMainStage; + } else if (mSideStage.mRootTaskInfo != null + && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) { + return mSideStage; + } } return null; } @@ -2229,7 +2616,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @StageType private int getStageType(StageTaskListener stage) { if (stage == null) return STAGE_TYPE_UNDEFINED; - return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + if (enableFlexibleSplit()) { + return stage.getId(); + } else { + return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + } } @Override @@ -2276,11 +2667,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isSplitActive()) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d split active", request.getDebugId()); + StageTaskListener primaryStage = enableFlexibleSplit() + ? mStageOrderOperator.getActiveStages().get(0) + : mMainStage; + StageTaskListener secondaryStage = enableFlexibleSplit() + ? mStageOrderOperator.getActiveStages().get(1) + : mSideStage; // Try to handle everything while in split-screen, so return a WCT even if it's empty. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type), - mMainStage.getChildCount(), mSideStage.getChildCount()); + primaryStage.getChildCount(), secondaryStage.getChildCount()); out = new WindowContainerTransaction(); if (stage != null) { if (isClosingType(type) && stage.getChildCount() == 1) { @@ -2320,11 +2717,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // the remote handler. return null; } - - if ((mMainStage.containsTask(triggerTask.taskId) - && mMainStage.getChildCount() == 1) - || (mSideStage.containsTask(triggerTask.taskId) - && mSideStage.getChildCount() == 1)) { + boolean anyStageContainsSingleFullscreenTask; + if (enableFlexibleSplit()) { + anyStageContainsSingleFullscreenTask = + mStageOrderOperator.getActiveStages().stream() + .anyMatch(stageListener -> + stageListener.containsTask(triggerTask.taskId) + && stageListener.getChildCount() == 1); + } else { + anyStageContainsSingleFullscreenTask = + (mMainStage.containsTask(triggerTask.taskId) + && mMainStage.getChildCount() == 1) + || (mSideStage.containsTask(triggerTask.taskId) + && mSideStage.getChildCount() == 1); + } + if (anyStageContainsSingleFullscreenTask) { // A splitting task is opening to fullscreen causes one side of the split empty, // so appends operations to exit split. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); @@ -2344,11 +2751,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // One of the cases above handled it return out; } else if (isSplitScreenVisible()) { + boolean allStagesHaveChildren; + if (enableFlexibleSplit()) { + allStagesHaveChildren = runForActiveStagesAllMatch(stageTaskListener -> + stageTaskListener.getChildCount() != 0); + } else { + allStagesHaveChildren = mMainStage.getChildCount() != 0 + && mSideStage.getChildCount() != 0; + } // If split is visible, only defer handling this transition if it's launching // adjacent while there is already a split pair -- this may trigger PIP and // that should be handled by the mixed handler. final boolean deferTransition = requestHasLaunchAdjacentFlag(request) - && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0; + && allStagesHaveChildren; return !deferTransition ? out : null; } // Don't intercept the transition if we are not handling it as a part of one of the @@ -2588,8 +3003,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage(); - if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0 - || dismissStages.size() == 1) { + boolean anyStageHasNoChildren; + if (enableFlexibleSplit()) { + anyStageHasNoChildren = mStageOrderOperator.getActiveStages().stream() + .anyMatch(stage -> stage.getChildCount() == 0); + } else { + anyStageHasNoChildren = mMainStage.getChildCount() == 0 + || mSideStage.getChildCount() == 0; + } + if (anyStageHasNoChildren || dismissStages.size() == 1) { // If the size of dismissStages == 1, one of the task is closed without prepare // pending transition, which could happen if all activities were finished after // finish top activity in a task, so the trigger task is null when handleRequest. @@ -2735,24 +3157,48 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, shouldAnimate = startPendingDismissAnimation( dismiss, info, startTransaction, finishTransaction); if (shouldAnimate && dismiss.mReason == EXIT_REASON_DRAG_DIVIDER) { - final StageTaskListener toTopStage = - dismiss.mDismissTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage; + StageTaskListener toTopStage; + if (enableFlexibleSplit()) { + toTopStage = mStageOrderOperator.getAllStages().stream() + .filter(stage -> stage.getId() == dismiss.mDismissTop) + .findFirst().orElseThrow(); + } else { + toTopStage = dismiss.mDismissTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage; + } mSplitTransitions.playDragDismissAnimation(transition, info, startTransaction, finishTransaction, finishCallback, toTopStage.mRootTaskInfo.token, toTopStage.getSplitDecorManager(), mRootTaskInfo.token); return true; } } else if (mSplitTransitions.isPendingResize(transition)) { + Map<WindowContainerToken, SplitDecorManager> tokenDecorMap = new HashMap<>(); + if (enableFlexibleSplit()) { + runForActiveStages(stageTaskListener -> + tokenDecorMap.put(stageTaskListener.mRootTaskInfo.getToken(), + stageTaskListener.getSplitDecorManager())); + } else { + tokenDecorMap.put(mMainStage.mRootTaskInfo.getToken(), + mMainStage.getSplitDecorManager()); + tokenDecorMap.put(mSideStage.mRootTaskInfo.getToken(), + mSideStage.getSplitDecorManager()); + } mSplitTransitions.playResizeAnimation(transition, info, startTransaction, - finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token, - mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(), - mSideStage.getSplitDecorManager()); + finishTransaction, finishCallback, tokenDecorMap); return true; } if (!shouldAnimate) return false; + WindowContainerToken mainToken; + WindowContainerToken sideToken; + if (enableFlexibleSplit()) { + mainToken = mStageOrderOperator.getActiveStages().get(0).mRootTaskInfo.token; + sideToken = mStageOrderOperator.getActiveStages().get(1).mRootTaskInfo.token; + } else { + mainToken = mMainStage.mRootTaskInfo.token; + sideToken = mSideStage.mRootTaskInfo.token; + } mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction, - finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token, + finishCallback, mainToken, sideToken, mRootTaskInfo.token); return true; } @@ -2777,6 +3223,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // First, verify that we actually have opened apps in both splits. TransitionInfo.Change mainChild = null; TransitionInfo.Change sideChild = null; + StageTaskListener firstAppStage = null; + StageTaskListener secondAppStage = null; final WindowContainerTransaction evictWct = new WindowContainerTransaction(); for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); @@ -2785,14 +3233,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mPausingTasks.contains(taskInfo.taskId)) { continue; } - final @StageType int stageType = getStageType(getStageOfTask(taskInfo)); - if (mainChild == null && stageType == STAGE_TYPE_MAIN + StageTaskListener stage = getStageOfTask(taskInfo); + final @StageType int stageType = getStageType(stage); + if (mainChild == null + && stageType == (enableFlexibleSplit() ? STAGE_TYPE_A : STAGE_TYPE_MAIN) && (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) { // Includes TRANSIT_CHANGE to cover reparenting top-most task to split. mainChild = change; - } else if (sideChild == null && stageType == STAGE_TYPE_SIDE + firstAppStage = getStageOfTask(taskInfo); + } else if (sideChild == null + && stageType == (enableFlexibleSplit() ? STAGE_TYPE_B : STAGE_TYPE_SIDE) && (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) { sideChild = change; + secondAppStage = stage; } else if (stageType != STAGE_TYPE_UNDEFINED && change.getMode() == TRANSIT_TO_BACK) { // Collect all to back task's and evict them when transition finished. evictWct.reparent(taskInfo.token, null /* parent */, false /* onTop */); @@ -2848,9 +3301,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // TODO(b/184679596): Find a way to either include task-org information in // the transition, or synchronize task-org callbacks. final boolean mainNotContainOpenTask = - mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId); + mainChild != null && !firstAppStage.containsTask(mainChild.getTaskInfo().taskId); final boolean sideNotContainOpenTask = - sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId); + sideChild != null && !secondAppStage.containsTask(sideChild.getTaskInfo().taskId); if (mainNotContainOpenTask) { Log.w(TAG, "Expected onTaskAppeared on " + mMainStage + " to have been called with " + mainChild.getTaskInfo().taskId @@ -2863,6 +3316,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } final TransitionInfo.Change finalMainChild = mainChild; final TransitionInfo.Change finalSideChild = sideChild; + final StageTaskListener finalFirstAppStage = firstAppStage; + final StageTaskListener finalSecondAppStage = secondAppStage; enterTransition.setFinishedCallback((callbackWct, callbackT) -> { if (!enterTransition.mResizeAnim) { // If resizing, we'll call notify at the end of the resizing animation (below) @@ -2870,16 +3325,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } if (finalMainChild != null) { if (!mainNotContainOpenTask) { - mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId); + finalFirstAppStage.evictOtherChildren(callbackWct, + finalMainChild.getTaskInfo().taskId); } else { - mMainStage.evictInvisibleChildren(callbackWct); + finalFirstAppStage.evictInvisibleChildren(callbackWct); } } if (finalSideChild != null) { if (!sideNotContainOpenTask) { - mSideStage.evictOtherChildren(callbackWct, finalSideChild.getTaskInfo().taskId); + finalSecondAppStage.evictOtherChildren(callbackWct, + finalSideChild.getTaskInfo().taskId); } else { - mSideStage.evictInvisibleChildren(callbackWct); + finalSecondAppStage.evictInvisibleChildren(callbackWct); } } if (!evictWct.isEmpty()) { @@ -2961,8 +3418,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, public void onPipExpandToSplit(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo taskInfo) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onPipExpandToSplit: task=%s", taskInfo); + // TODO(b/349828130) currently pass in index_undefined until we can revisit these + // flex split + pip interactions in the future prepareEnterSplitScreen(wct, taskInfo, getActivateSplitPosition(taskInfo), - false /*resizeAnim*/); + false /*resizeAnim*/, SPLIT_INDEX_UNDEFINED); if (!isSplitScreenVisible() || mSplitRequest == null) { return; @@ -3067,13 +3526,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Wait until after animation to update divider // Reset crops so they don't interfere with subsequent launches - t.setCrop(mMainStage.mRootLeash, null); - t.setCrop(mSideStage.mRootLeash, null); + if (enableFlexibleSplit()) { + runForActiveStages(stage -> t.setCrop(stage.mRootLeash, null /*crop*/)); + } else { + t.setCrop(mMainStage.mRootLeash, null); + t.setCrop(mSideStage.mRootLeash, null); + } // Hide the non-top stage and set the top one to the fullscreen position. if (toStage != STAGE_TYPE_UNDEFINED) { - t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash); - t.setPosition(toStage == STAGE_TYPE_MAIN - ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0); + if (enableFlexibleSplit()) { + StageTaskListener stageToKeep = mStageOrderOperator.getAllStages().stream() + .filter(stage -> stage.getId() == toStage) + .findFirst().orElseThrow(); + List<StageTaskListener> stagesToHide = mStageOrderOperator.getAllStages().stream() + .filter(stage -> stage.getId() != toStage) + .toList(); + stagesToHide.forEach(stage -> t.hide(stage.mRootLeash)); + t.setPosition(stageToKeep.mRootLeash, 0, 0); + } else { + t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash); + t.setPosition(toStage == STAGE_TYPE_MAIN + ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0); + } } else { for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { finishT.hide(dismissingTasks.valueAt(i)); @@ -3088,8 +3562,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Hide divider and dim layer on transition finished. setDividerVisibility(false, t); - finishT.hide(mMainStage.mDimLayer); - finishT.hide(mSideStage.mDimLayer); + if (enableFlexibleSplit()) { + runForActiveStages(stage -> finishT.hide(stage.mRootLeash)); + } else { + finishT.hide(mMainStage.mDimLayer); + finishT.hide(mSideStage.mDimLayer); + } } private boolean startPendingDismissAnimation( @@ -3110,8 +3588,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return false; } dismissTransition.setFinishedCallback((callbackWct, callbackT) -> { - mMainStage.getSplitDecorManager().release(callbackT); - mSideStage.getSplitDecorManager().release(callbackT); + if (enableFlexibleSplit()) { + runForActiveStages(stage -> stage.getSplitDecorManager().release(callbackT)); + } else { + mMainStage.getSplitDecorManager().release(callbackT); + mSideStage.getSplitDecorManager().release(callbackT); + } callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); }); return true; @@ -3128,8 +3610,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (TransitionUtil.isClosingType(change.getMode()) && change.getTaskInfo() != null) { final int taskId = change.getTaskInfo().taskId; - if (mMainStage.getTopVisibleChildTaskId() == taskId - || mSideStage.getTopVisibleChildTaskId() == taskId) { + boolean anyStagesHaveTask; + if (enableFlexibleSplit()) { + anyStagesHaveTask = mStageOrderOperator.getActiveStages().stream() + .anyMatch(stage -> stage.getTopVisibleChildTaskId() == taskId); + } else { + anyStagesHaveTask = mMainStage.getTopVisibleChildTaskId() == taskId + || mSideStage.getTopVisibleChildTaskId() == taskId; + } + if (anyStagesHaveTask) { mPausingTasks.add(taskId); } } @@ -3161,9 +3650,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction.HierarchyOp op = finishWct.getHierarchyOps().get(i); final IBinder container = op.getContainer(); + boolean anyStageContainsContainer; + if (enableFlexibleSplit()) { + anyStageContainsContainer = mStageOrderOperator.getActiveStages().stream() + .anyMatch(stage -> stage.containsContainer(container)); + } else { + anyStageContainsContainer = mMainStage.containsContainer(container) + || mSideStage.containsContainer(container); + } if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() - && (mMainStage.containsContainer(container) - || mSideStage.containsContainer(container))) { + && anyStageContainsContainer) { updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */); finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); @@ -3190,10 +3686,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // user entering recents. for (int i = mPausingTasks.size() - 1; i >= 0; --i) { final int taskId = mPausingTasks.get(i); - if (mMainStage.containsTask(taskId)) { - mMainStage.evictChildren(finishWct, taskId); - } else if (mSideStage.containsTask(taskId)) { - mSideStage.evictChildren(finishWct, taskId); + if (enableFlexibleSplit()) { + mStageOrderOperator.getActiveStages().stream() + .filter(stage -> stage.containsTask(taskId)) + .findFirst() + .ifPresent(stageToEvict -> + stageToEvict.evictChild(finishWct, taskId, "recentsPairToPair")); + } else { + if (mMainStage.containsTask(taskId)) { + mMainStage.evictChild(finishWct, taskId, "recentsPairToPair"); + } else if (mSideStage.containsTask(taskId)) { + mSideStage.evictChild(finishWct, taskId, "recentsPairToPair"); + } } } // If pending enter hasn't consumed, the mix handler will invoke start pending @@ -3256,8 +3760,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, */ private void setSplitsVisible(boolean visible) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible); - mMainStage.mVisible = mSideStage.mVisible = visible; - mMainStage.mHasChildren = mSideStage.mHasChildren = visible; + if (enableFlexibleSplit()) { + runForActiveStages(stage -> { + stage.mVisible = visible; + stage.mHasChildren = visible; + }); + } else { + mMainStage.mVisible = mSideStage.mVisible = visible; + mMainStage.mHasChildren = mSideStage.mHasChildren = visible; + } } /** @@ -3300,6 +3811,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * executed. */ private void logExitToStage(@ExitReason int exitReason, boolean toMainStage) { + if (enableFlexibleSplit()) { + // TODO(b/374825718) update logging for 2+ apps + return; + } mLogger.logExit(exitReason, toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED, toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt new file mode 100644 index 000000000000..b7b3c9b62a58 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt @@ -0,0 +1,185 @@ +/* + * 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.wm.shell.splitscreen + +import android.content.Context +import com.android.internal.protolog.ProtoLog +import com.android.launcher3.icons.IconProvider +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.shared.split.SplitScreenConstants +import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0 +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1 +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_2 +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED +import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition +import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex +import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition +import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_A +import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_B +import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_C +import com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString +import com.android.wm.shell.windowdecor.WindowDecorViewModel +import java.util.Optional + +/** + * Responsible for creating [StageTaskListener]s and maintaining their ordering on screen. + * Must be notified whenever stages positions change via swapping or starting/ending tasks + */ +class StageOrderOperator ( + context: Context, + taskOrganizer: ShellTaskOrganizer, + displayId: Int, + stageCallbacks: StageTaskListener.StageListenerCallbacks, + syncQueue: SyncTransactionQueue, + iconProvider: IconProvider, + windowDecorViewModel: Optional<WindowDecorViewModel> + ) { + + private val MAX_STAGES = 3 + /** + * This somewhat acts as a replacement to stageTypes in the intermediary, so we want to start + * it after the @StageType constant values just to be safe and avoid potentially subtle bugs. + */ + private var stageIds = listOf(STAGE_TYPE_A, STAGE_TYPE_B, STAGE_TYPE_C) + + /** + * Active Stages, this list represent the current, ordered list of stages that are + * currently visible to the user. This map should be empty if the user is currently + * not in split screen. Note that this is different than if split screen is visible, which + * is determined by [StageListenerImpl.mVisible]. + * Split stages can be active and in the background + */ + val activeStages = mutableListOf<StageTaskListener>() + val allStages = mutableListOf<StageTaskListener>() + var isActive: Boolean = false + var isVisible: Boolean = false + @SnapPosition private var currentLayout: Int = SNAP_TO_NONE + + init { + for(i in 0 until MAX_STAGES) { + allStages.add(StageTaskListener(context, + taskOrganizer, + displayId, + stageCallbacks, + syncQueue, + iconProvider, + windowDecorViewModel, + stageIds[i]) + ) + } + } + + /** + * Updates internal state to keep record of "active" stages. Note that this does NOT call + * [StageTaskListener.activate] on the stages. + */ + fun onEnteringSplit(@SnapPosition goingToLayout: Int) { + if (goingToLayout == currentLayout) { + // Add protolog here. Return for now, but maybe we want to handle swap case, TBD + return + } + val freeStages: List<StageTaskListener> = + allStages.filterNot { activeStages.contains(it) } + when(goingToLayout) { + SplitScreenConstants.SNAP_TO_2_50_50 -> { + if (activeStages.size < 2) { + // take from allStages and add into activeStages + for (i in 0 until (2 - activeStages.size)) { + val stage = freeStages[i] + activeStages.add(stage) + } + } + } + } + ProtoLog.d( + ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Activated stages: %d ids=%s", + activeStages.size, + activeStages.joinToString(",") { stageTypeToString(it.id) } + ) + isActive = true + } + + fun onExitingSplit() { + activeStages.clear() + isActive = false + } + + /** + * Given a legacy [SplitPosition] returns one of the stages from the actives stages. + * If there are no active stages and [checkAllStagesIfNotActive] is not true, then will return + * null + */ + fun getStageForLegacyPosition(@SplitPosition position: Int, + checkAllStagesIfNotActive : Boolean = false) : + StageTaskListener? { + if (activeStages.size != 2 && !checkAllStagesIfNotActive) { + return null + } + val listToCheck = if (activeStages.isEmpty() and checkAllStagesIfNotActive) + allStages else + activeStages + if (position == SPLIT_POSITION_TOP_OR_LEFT) { + return listToCheck[0] + } else if (position == SPLIT_POSITION_BOTTOM_OR_RIGHT) { + return listToCheck[1] + } else { + throw IllegalArgumentException("No stage for invalid position") + } + } + + /** + * Returns a legacy split position for the given stage. If no stages are active then this will + * return [SPLIT_POSITION_UNDEFINED] + */ + @SplitPosition + fun getLegacyPositionForStage(stage: StageTaskListener) : Int { + if (allStages[0] == stage) { + return SPLIT_POSITION_TOP_OR_LEFT + } else if (allStages[1] == stage) { + return SPLIT_POSITION_BOTTOM_OR_RIGHT + } else { + return SPLIT_POSITION_UNDEFINED + } + } + + /** + * Returns the stageId from a given splitIndex. This will default to checking from all stages if + * [isActive] is false, otherwise will only check active stages. + */ + fun getStageForIndex(@SplitIndex splitIndex: Int) : StageTaskListener { + // Probably should do a check for index to be w/in the bounds of the current split layout + // that we're currently in + val listToCheck = if (isActive) activeStages else allStages + if (splitIndex == SPLIT_INDEX_0) { + return listToCheck[0] + } else if (splitIndex == SPLIT_INDEX_1) { + return listToCheck[1] + } else if (splitIndex == SPLIT_INDEX_2) { + return listToCheck[2] + } else { + // Though I guess what if we're adding to the end? Maybe that indexing needs to be + // resolved elsewhere + throw IllegalStateException("No stage for the given splitIndex") + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 4407e5b3106f..4a37169add36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -22,14 +22,17 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.RemoteAnimationTarget.MODE_OPENING; +import static com.android.wm.shell.Flags.enableFlexibleSplit; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE; +import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; import android.annotation.CallSuper; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.TaskInfo; import android.content.Context; import android.graphics.Rect; import android.os.IBinder; @@ -71,6 +74,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { // No current way to enforce this but if enableFlexibleSplit() is enabled, then only 1 of the // stages should have this be set/being used private boolean mIsActive; + /** Unique identifier for this state, > 0 */ + @StageType private final int mId; /** Callback interface for listening to changes in a split-screen stage. */ public interface StageListenerCallbacks { void onRootTaskAppeared(); @@ -109,13 +114,14 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, IconProvider iconProvider, - Optional<WindowDecorViewModel> windowDecorViewModel) { + Optional<WindowDecorViewModel> windowDecorViewModel, int id) { mContext = context; mCallbacks = callbacks; mSyncQueue = syncQueue; mIconProvider = iconProvider; mWindowDecorViewModel = windowDecorViewModel; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); + mId = id; } int getChildCount() { @@ -138,6 +144,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { * Returns the top visible child task's id. */ int getTopVisibleChildTaskId() { + // TODO(b/378601156): This doesn't get the top task (translucent tasks are also + // visible-requested) final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible && t.isVisibleRequested); return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID; @@ -147,6 +155,7 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { * Returns the top activity uid for the top child task. */ int getTopChildTaskUid() { + // TODO(b/378601156): This doesn't get the top task final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.topActivityInfo != null); return taskInfo != null ? taskInfo.topActivityInfo.applicationInfo.uid : 0; @@ -157,6 +166,11 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { return contains(t -> t.isFocused); } + @StageType + int getId() { + return mId; + } + private boolean contains(Predicate<ActivityManager.RunningTaskInfo> predicate) { if (mRootTaskInfo != null && predicate.test(mRootTaskInfo)) { return true; @@ -193,10 +207,10 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { @CallSuper public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d taskParent=%d rootTask=%d " - + "taskActivity=%s", + + "stageId=%s taskActivity=%s", taskInfo.taskId, taskInfo.parentTaskId, mRootTaskInfo != null ? mRootTaskInfo.taskId : -1, - taskInfo.baseActivity); + stageTypeToString(mId), taskInfo.baseActivity); if (mRootTaskInfo == null) { mRootLeash = leash; mRootTaskInfo = taskInfo; @@ -226,8 +240,9 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s", - taskInfo.taskId, taskInfo.baseActivity); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s " + + "stageId=%s", + taskInfo.taskId, taskInfo.baseActivity, stageTypeToString(mId)); mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); if (mRootTaskInfo.taskId == taskInfo.taskId) { mRootTaskInfo = taskInfo; @@ -257,7 +272,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d", taskInfo.taskId); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d stageId=%s", + taskInfo.taskId, stageTypeToString(mId)); final int taskId = taskInfo.taskId; mWindowDecorViewModel.ifPresent(vm -> vm.onTaskVanished(taskInfo)); if (mRootTaskInfo.taskId == taskId) { @@ -379,10 +395,9 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { /** Collects all the current child tasks and prepares transaction to evict them to display. */ void evictAllChildren(WindowContainerTransaction wct) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evicting all children"); for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); - wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); + evictChild(wct, taskInfo, "all"); } } @@ -390,13 +405,11 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); if (taskId == taskInfo.taskId) continue; - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict other child: task=%d", taskId); - wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); + evictChild(wct, taskInfo, "other"); } } void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "evictNonOpeningChildren"); final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone(); for (int i = 0; i < apps.length; i++) { if (apps[i].mode == MODE_OPENING) { @@ -405,8 +418,7 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { } for (int i = toBeEvict.size() - 1; i >= 0; i--) { final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i); - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict non-opening child: task=%d", taskInfo.taskId); - wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); + evictChild(wct, taskInfo, "non-opening"); } } @@ -414,21 +426,30 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); if (!taskInfo.isVisible) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict invisible child: task=%d", - taskInfo.taskId); - wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); + evictChild(wct, taskInfo, "invisible"); } } } - void evictChildren(WindowContainerTransaction wct, int taskId) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict child: task=%d", taskId); + void evictChild(WindowContainerTransaction wct, int taskId, String reason) { final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.get(taskId); if (taskInfo != null) { - wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); + evictChild(wct, taskInfo, reason); } } + private void evictChild(@NonNull WindowContainerTransaction wct, @NonNull TaskInfo taskInfo, + @NonNull String reason) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict child: task=%d reason=%s", taskInfo.taskId, + reason); + // We are reparenting the task, but not removing the task from mChildrenTaskInfo, so to + // prevent this task from being considered as a top task for the roots, we need to override + // the visibility of the soon-to-be-hidden task + taskInfo.isVisible = false; + taskInfo.isVisibleRequested = false; + wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); + } + void reparentTopTask(WindowContainerTransaction wct) { wct.reparentTasks(null /* currentParent */, mRootTaskInfo.token, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES, @@ -457,14 +478,17 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { } void activate(WindowContainerTransaction wct, boolean includingTopTask) { - if (mIsActive) return; - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: includingTopTask=%b", - includingTopTask); + if (mIsActive && !enableFlexibleSplit()) return; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: includingTopTask=%b stage=%s", + includingTopTask, stageTypeToString(mId)); if (includingTopTask) { reparentTopTask(wct); } + if (enableFlexibleSplit()) { + return; + } mIsActive = true; } @@ -472,11 +496,14 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { deactivate(wct, false /* toTop */); } - void deactivate(WindowContainerTransaction wct, boolean toTop) { - if (!mIsActive) return; - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: toTop=%b rootTaskInfo=%s", - toTop, mRootTaskInfo); - mIsActive = false; + void deactivate(WindowContainerTransaction wct, boolean reparentTasksToTop) { + if (!mIsActive && !enableFlexibleSplit()) return; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: reparentTasksToTop=%b " + + "rootTaskInfo=%s stage=%s", + reparentTasksToTop, mRootTaskInfo, stageTypeToString(mId)); + if (!enableFlexibleSplit()) { + mIsActive = false; + } if (mRootTaskInfo == null) return; final WindowContainerToken rootToken = mRootTaskInfo.token; @@ -485,14 +512,15 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { null /* newParent */, null /* windowingModes */, null /* activityTypes */, - toTop); + reparentTasksToTop); } // -------- - // Previously only used in SideStage + // Previously only used in SideStage. With flexible split this is called for all stages boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b", - mChildrenTaskInfo.size(), toTop); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b " + + " stageI=%s", + mChildrenTaskInfo.size(), toTop, stageTypeToString(mId)); if (mChildrenTaskInfo.size() == 0) return false; wct.reparentTasks( mRootTaskInfo.token, @@ -513,6 +541,15 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { } @Override + public String toString() { + return "mId: " + stageTypeToString(mId) + + " mVisible: " + mVisible + + " mActive: " + mIsActive + + " mHasRootTask: " + mHasRootTask + + " childSize: " + mChildrenTaskInfo.size(); + } + + @Override @CallSuper public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index d28287da83b6..32f3cd820421 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -334,8 +334,8 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, // Sides adjacent to split bar or task bar are not be animated. Insets margins; - final boolean isLandscape = mRootStageBounds.width() > mRootStageBounds.height(); - if (isLandscape) { // Left and right splits. + final boolean isLeftRightSplit = mSplitScreenController.get().get().isLeftRightSplit(); + if (isLeftRightSplit) { margins = getLandscapeMargins(margin, taskbarExpanded); } else { // Top and bottom splits. margins = getPortraitMargins(margin, taskbarExpanded); diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAutoPipAppWindowTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAutoPipAppWindowTest.kt new file mode 100644 index 000000000000..48befc06b9c5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAutoPipAppWindowTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.MinimizeAutoPipAppWindow +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [MinimizeAutoPipAppWindow]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class MinimizeAutoPipAppWindowTest : MinimizeAutoPipAppWindow() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenUnlimitedAppsTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenUnlimitedAppsTest.kt new file mode 100644 index 000000000000..9462f15335de --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenUnlimitedAppsTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.OpenUnlimitedApps +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [OpenUnlimitedApps]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class OpenUnlimitedAppsTest : OpenUnlimitedApps() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt index 0f546cdf97c5..8d04749d76a5 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt @@ -49,7 +49,8 @@ constructor( @Test open fun enterDesktopWithDrag() { - testApp.enterDesktopModeWithDrag(wmHelper, device) + // By default this method uses drag to desktop + testApp.enterDesktopMode(wmHelper, device) } @After diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAutoPipAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAutoPipAppWindow.kt new file mode 100644 index 000000000000..d6c3266e915c --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAutoPipAppWindow.kt @@ -0,0 +1,72 @@ +/* + * 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.wm.shell.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.PipAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +/** Base scenario test for minimizing the app entering pip on leave automatically */ +@Ignore("Test Base Class") +abstract class MinimizeAutoPipAppWindow { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val pipApp = PipAppHelper(instrumentation) + private val pipAppDesktopMode = DesktopModeAppHelper(pipApp) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + Assume.assumeTrue(Flags.enableMinimizeButton()) + testApp.enterDesktopMode(wmHelper, device) + pipApp.launchViaIntent(wmHelper) + pipApp.enableAutoEnterForPipActivity() + } + + @Test + open fun minimizePipAppWindow() { + pipAppDesktopMode.minimizeDesktopApp(wmHelper, device, isPip = true) + } + + @After + fun teardown() { + pipApp.exit(wmHelper) + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt index 7987f7ec59fa..a246326f1137 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt @@ -23,12 +23,10 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.helpers.DesktopModeAppHelper -import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.helpers.LetterboxAppHelper import com.android.server.wm.flicker.helpers.MailAppHelper -import com.android.server.wm.flicker.helpers.NewTasksAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.window.flags.Flags +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import org.junit.After import org.junit.Assume import org.junit.Before @@ -51,32 +49,30 @@ open class MinimizeWindowOnAppOpen() private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation)) - private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation)) - private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation)) - private val letterboxAppHelper = DesktopModeAppHelper(LetterboxAppHelper(instrumentation)) + + private val maxNum = DesktopModeStatus.getMaxTaskLimit(instrumentation.context) @Before fun setup() { Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + Assume.assumeTrue(maxNum > 0) testApp.enterDesktopMode(wmHelper, device) - mailApp.launchViaIntent(wmHelper) - newTasksApp.launchViaIntent(wmHelper) - imeApp.launchViaIntent(wmHelper) + // Launch new [maxNum-1] tasks, which ends up opening [maxNum] tasks in total. + for (i in 1..maxNum - 1) { + mailApp.launchViaIntent(wmHelper) + } } @Test open fun openAppToMinimizeWindow() { - // Launch a new app while 4 apps are already open on desktop. This should result in the - // first app we opened to be minimized. - letterboxAppHelper.launchViaIntent(wmHelper) + // Launch a new tasks, which ends up opening [maxNum]+1 tasks in total. This should + // result in the first app we opened to be minimized. + mailApp.launchViaIntent(wmHelper) } @After fun teardown() { testApp.exit(wmHelper) mailApp.exit(wmHelper) - newTasksApp.exit(wmHelper) - imeApp.exit(wmHelper) - letterboxAppHelper.exit(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt new file mode 100644 index 000000000000..367c4a437018 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt @@ -0,0 +1,73 @@ +/* + * 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.wm.shell.scenarios + +import android.app.Instrumentation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.MailAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +/** + * Base scenario test for opening many apps on the device without the window limit. + */ +@Ignore("Test Base Class") +abstract class OpenUnlimitedApps() +{ + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation)) + + private val maxNum = DesktopModeStatus.getMaxTaskLimit(instrumentation.context) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + Assume.assumeTrue(maxNum == 0) + testApp.enterDesktopMode(wmHelper, device) + } + + @Test + open fun openUnlimitedApps() { + // The maximum number of active tasks is infinite. We here use 12 as a large enough number. + val openTaskNum = 12 + + // Launch new [openTaskNum] tasks. + for (i in 1..openTaskNum) { + mailApp.launchViaIntent(wmHelper) + } + } + + @After + fun teardown() { + testApp.exit(wmHelper) + mailApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index a248303b1c33..fd4328dee0a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -16,16 +16,18 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder import android.tools.flicker.subject.exceptions.IncorrectRegionException import android.tools.flicker.subject.layers.LayerSubject -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.Assume import org.junit.FixMethodOrder @@ -35,6 +37,7 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized import kotlin.math.abs + /** * Test entering pip from an app via auto-enter property when navigating to home. * @@ -60,6 +63,7 @@ import kotlin.math.abs @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt index df952c925720..d4ad4ef8a401 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt @@ -17,10 +17,12 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.traces.component.ComponentNameMatcher +import com.android.wm.shell.Flags import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -52,6 +54,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) : AutoEnterPipOnGoToHomeTest(flicker) { override val defaultEnterPip: FlickerBuilder.() -> Unit = { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt index 302b8c414979..cc6e4b5a90d5 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest @@ -53,6 +54,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt index 77a1edb7039a..53725fa046c6 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt @@ -17,9 +17,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.ClosePipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -52,6 +54,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.closePipWindow(wmHelper) } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index 6e32d6412b50..a1551b7924fe 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -17,9 +17,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.Assume import org.junit.FixMethodOrder @@ -43,6 +45,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index 9a6cb61cfc66..ea5b3e5b08df 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -19,6 +19,7 @@ package com.android.wm.shell.flicker.pip import android.app.Activity import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest import android.tools.flicker.junit.FlickerParametersRunnerFactory @@ -32,6 +33,7 @@ import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT @@ -68,6 +70,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val testApp = FixedOrientationAppHelper(instrumentation) private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt index 6b4751cee3bb..a109c4bba2b3 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt @@ -16,9 +16,11 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.FixMethodOrder import org.junit.runner.RunWith @@ -49,6 +51,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.clickEnterPipButton(wmHelper) } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt index 8d0bc0f5a155..14ec303206ee 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt @@ -16,9 +16,11 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition import org.junit.FixMethodOrder import org.junit.runner.RunWith @@ -51,6 +53,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt index 939f3280d2e6..8a34b5e27fdb 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt @@ -16,9 +16,11 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition import org.junit.FixMethodOrder import org.junit.runner.RunWith @@ -50,6 +52,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { setup { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 258663b5556d..4f189fc6190d 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -17,12 +17,14 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.traces.component.ComponentNameMatcher +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -54,6 +56,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.doubleClickPipWindow(wmHelper) } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt index 1964e3cebc89..4d72b03d0345 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt @@ -17,11 +17,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -33,6 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt index 5f8ac2af241b..1c40d89aec80 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt @@ -16,7 +16,10 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -24,10 +27,9 @@ import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils import android.tools.traces.parsers.toFlickerComponent -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.Assume import org.junit.FixMethodOrder @@ -62,6 +64,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : AutoEnterPipOnGoToHomeTest(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt index 48c85a84e556..12e23285ea68 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt @@ -16,7 +16,10 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -24,10 +27,9 @@ import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils import android.tools.traces.parsers.toFlickerComponent -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.EnterPipTransition import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.Assume @@ -63,6 +65,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt index ee62cf59b2f9..d979b428d8b2 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt @@ -17,10 +17,12 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest -import androidx.test.filters.RequiresDevice +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition import com.android.wm.shell.flicker.utils.Direction import org.junit.FixMethodOrder @@ -56,6 +58,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class MovePipDownOnShelfHeightChange(flicker: LegacyFlickerTest) : MovePipShelfHeightTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt index 04fedf4f2550..88d78edae94a 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest import android.tools.flicker.junit.FlickerParametersRunnerFactory @@ -27,6 +28,7 @@ import android.tools.helpers.WindowUtils import android.tools.traces.component.ComponentNameMatcher import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.setRotation +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -41,6 +43,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val imeApp = ImeAppHelper(instrumentation) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt index 4d643f7b4408..c533800b37c5 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt @@ -17,10 +17,12 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest -import androidx.test.filters.RequiresDevice +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition import com.android.wm.shell.flicker.utils.Direction import org.junit.FixMethodOrder @@ -56,6 +58,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) open class MovePipUpOnShelfHeightChangeTest(flicker: LegacyFlickerTest) : MovePipShelfHeightTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { @@ -65,7 +68,8 @@ open class MovePipUpOnShelfHeightChangeTest(flicker: LegacyFlickerTest) : } /** Checks that the visible region of [pipApp] window always moves up during the animation. */ - @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP) + @Presubmit + @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP) /** Checks that the visible region of [pipApp] layer always moves up during the animation. */ @Presubmit @Test fun pipLayerMovesUp() = pipLayerMoves(Direction.UP) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt index 429774f890a5..04016a93e53d 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt @@ -17,11 +17,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -33,6 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.changeAspectRatio(wmHelper) } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt index a4df69fc6539..6bcaabc3b680 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt @@ -18,11 +18,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -35,6 +37,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { private var isDraggedLeft: Boolean = true diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt index cbd4a528474a..d82bfdd6dc2f 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt @@ -17,16 +17,18 @@ package com.android.wm.shell.flicker.pip import android.graphics.Rect +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.flicker.rules.RemoveAllTasksButHomeRule -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -40,6 +42,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { // represents the direction in which the pip window should be snapping private var willSnapRight: Boolean = true diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index 16d08e5e9055..dbc97d072f9b 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -17,13 +17,15 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.flicker.subject.exceptions.IncorrectRegionException -import androidx.test.filters.RequiresDevice +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -36,6 +38,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt index 578a9b536289..9d46ac1d6e00 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt @@ -17,8 +17,11 @@ package com.android.wm.shell.flicker.pip import android.app.Activity +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest import android.tools.flicker.junit.FlickerParametersRunnerFactory @@ -26,10 +29,9 @@ import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import org.junit.Assume @@ -48,6 +50,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt index c6cf3411835c..e72251fb7a31 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.flicker.assertions.FlickerTest import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -25,6 +26,7 @@ import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.setRotation +import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -58,6 +60,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val testApp = SimpleAppHelper(instrumentation) private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt index bc2bfdbe1df1..c37bf3579e93 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt @@ -20,6 +20,7 @@ import android.app.Instrumentation import android.content.Intent import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.tools.Rotation import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest @@ -31,9 +32,14 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.wm.shell.flicker.BaseTest import com.google.common.truth.Truth +import org.junit.Rule import org.junit.Test abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) { + @JvmField + @Rule + val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + protected val pipApp = PipAppHelper(instrumentation) protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 93999476316a..5df395754c7a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -95,12 +95,15 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.desktopmode.DesktopTasksController.DesktopModeEntryExitTransitionListener import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask +import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION +import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler import com.android.wm.shell.desktopmode.persistence.Desktop import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository @@ -113,6 +116,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN import com.android.wm.shell.shared.split.SplitScreenConstants +import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -223,6 +227,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock private lateinit var desktopWindowDecoration: DesktopModeWindowDecoration @Mock private lateinit var resources: Resources + @Mock + lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit private lateinit var taskRepository: DesktopRepository @@ -294,6 +300,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller = createController() controller.setSplitScreenController(splitScreenController) controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter + controller.desktopModeEnterExitTransitionListener = desktopModeEnterExitTransitionListener shellInit.init() @@ -1054,6 +1061,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) } @Test @@ -1065,12 +1073,14 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) } @Test fun moveTaskToDesktop_nonExistentTask_doesNothing() { controller.moveTaskToDesktop(999, transitionSource = UNKNOWN) verifyEnterDesktopWCTNotExecuted() + verify(desktopModeEnterExitTransitionListener, times(0)).onEnterDesktopModeTransitionStarted(anyInt()) } @Test @@ -1115,9 +1125,9 @@ class DesktopTasksControllerTest : ShellTestCase() { } controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) } @Test @@ -1132,6 +1142,9 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) verifyEnterDesktopWCTNotExecuted() + verify(desktopModeEnterExitTransitionListener, times(0)).onEnterDesktopModeTransitionStarted( + FREEFORM_ANIMATION_DURATION + ) } @Test @@ -1178,6 +1191,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) } @Test @@ -1197,6 +1211,7 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) } + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) } @Test @@ -1217,6 +1232,7 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) } + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) } @Test @@ -1238,6 +1254,7 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(hierarchyOps.map { it.container }) .doesNotContain(freeformTaskSecond.token.asBinder()) } + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) } @Test @@ -1246,6 +1263,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) verify(splitScreenController) .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)) } @@ -1256,6 +1274,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) val wct = getLatestEnterDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) verify(splitScreenController, never()) .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)) } @@ -1270,6 +1289,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN) val wct = getLatestEnterDesktopWct() + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 1) // visible tasks + home wct.assertReorderAt(0, homeTask) wct.assertReorderSequenceInRange( @@ -1288,6 +1308,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN) val wct = getLatestEnterDesktopWct() + verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 2) // tasks + home + wallpaper // Move home to front wct.assertReorderAt(0, homeTask) @@ -1307,6 +1328,7 @@ class DesktopTasksControllerTest : ShellTestCase() { tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN) val wct = getLatestExitDesktopWct() + verify(desktopModeEnterExitTransitionListener, times(1)).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) } @@ -1324,6 +1346,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestExitDesktopWct() val taskChange = assertNotNull(wct.changes[task.token.asBinder()]) + verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // Removes wallpaper activity when leaving desktop wct.assertRemoveAt(index = 0, wallpaperToken) @@ -1338,6 +1361,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestExitDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) + verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) } @Test @@ -1354,6 +1378,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestExitDesktopWct() val taskChange = assertNotNull(wct.changes[task.token.asBinder()]) assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN) + verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) // Removes wallpaper activity when leaving desktop wct.assertRemoveAt(index = 0, wallpaperToken) } @@ -1374,6 +1399,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestExitDesktopWct() val task1Change = assertNotNull(wct.changes[task1.token.asBinder()]) assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) + verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) // Does not remove wallpaper activity, as desktop still has a visible desktop task assertThat(wct.hierarchyOps).isEmpty() } @@ -1388,13 +1414,13 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() { val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY) - controller.moveToFullscreen(taskDefaultDisplay.taskId, transitionSource = UNKNOWN) with(getLatestExitDesktopWct()) { assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder()) assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder()) } + verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) } @Test @@ -3133,7 +3159,7 @@ class DesktopTasksControllerTest : ShellTestCase() { runOpenNewWindow(task) verify(splitScreenController) .startIntent(any(), anyInt(), any(), any(), - optionsCaptor.capture(), anyOrNull(), eq(true) + optionsCaptor.capture(), anyOrNull(), eq(true), eq(SPLIT_INDEX_UNDEFINED) ) assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) @@ -3149,7 +3175,7 @@ class DesktopTasksControllerTest : ShellTestCase() { verify(splitScreenController) .startIntent( any(), anyInt(), any(), any(), - optionsCaptor.capture(), anyOrNull(), eq(true) + optionsCaptor.capture(), anyOrNull(), eq(true), eq(SPLIT_INDEX_UNDEFINED) ) assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java index 2cfce6933e1b..0cf15baf30b0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java @@ -24,6 +24,7 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED; import static com.android.wm.shell.draganddrop.DragTestUtils.createAppClipData; import static com.android.wm.shell.draganddrop.DragTestUtils.createIntentClipData; import static com.android.wm.shell.draganddrop.DragTestUtils.createTaskInfo; @@ -226,7 +227,7 @@ public class SplitDragPolicyTest extends ShellTestCase { mPolicy.onDropped(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */); verify(mFullscreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_UNDEFINED), any(), any()); + eq(SPLIT_POSITION_UNDEFINED), any(), any(), eq(SPLIT_INDEX_UNDEFINED)); } private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) { @@ -241,12 +242,12 @@ public class SplitDragPolicyTest extends ShellTestCase { mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any()); + eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any(), eq(SPLIT_INDEX_UNDEFINED)); reset(mSplitScreenStarter); mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any()); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any(), eq(SPLIT_INDEX_UNDEFINED)); } private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) { @@ -261,13 +262,13 @@ public class SplitDragPolicyTest extends ShellTestCase { mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any()); + eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any(), eq(SPLIT_INDEX_UNDEFINED)); reset(mSplitScreenStarter); mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any()); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any(), eq(SPLIT_INDEX_UNDEFINED)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 966651f19711..72a7a3f5ec99 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -200,10 +201,11 @@ public class SplitScreenControllerTests extends ShellTestCase { PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, + SPLIT_INDEX_0); verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(), - eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull()); + eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull(), eq(SPLIT_INDEX_0)); assertEquals(FLAG_ACTIVITY_NO_USER_ACTION, mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION); } @@ -220,10 +222,11 @@ public class SplitScreenControllerTests extends ShellTestCase { doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, + SPLIT_INDEX_0); verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(), - eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull()); + eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull(), eq(SPLIT_INDEX_0)); assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK, mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK); } @@ -243,10 +246,11 @@ public class SplitScreenControllerTests extends ShellTestCase { doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt(), any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, + SPLIT_INDEX_0); verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), - isNull(), isNull()); + isNull(), isNull(), eq(SPLIT_INDEX_0)); verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any()); verify(mStageCoordinator, never()).switchSplitPosition(any()); } @@ -269,10 +273,11 @@ public class SplitScreenControllerTests extends ShellTestCase { .findTaskInBackground(any(), anyInt(), any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, + SPLIT_INDEX_0); verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any()); verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), - isNull(), isNull()); + isNull(), isNull(), eq(SPLIT_INDEX_0)); } @Test @@ -289,7 +294,8 @@ public class SplitScreenControllerTests extends ShellTestCase { SPLIT_POSITION_BOTTOM_OR_RIGHT); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, + SPLIT_INDEX_0); verify(mStageCoordinator).switchSplitPosition(anyString()); } @@ -301,7 +307,7 @@ public class SplitScreenControllerTests extends ShellTestCase { PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, - true /* forceLaunchNewTask */); + true /* forceLaunchNewTask */, SPLIT_INDEX_0); verify(mRecentTasks, never()).findTaskInBackground(any(), anyInt(), any()); } @@ -312,7 +318,7 @@ public class SplitScreenControllerTests extends ShellTestCase { PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, - false /* forceLaunchNewTask */); + false /* forceLaunchNewTask */, SPLIT_INDEX_0); verify(mRecentTasks).findTaskInBackground(any(), anyInt(), any()); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index ce3944a5855e..e32cf3899a03 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; @@ -133,11 +134,11 @@ public class SplitTransitionTests extends ShellTestCase { mSplitLayout = SplitTestUtils.createMockSplitLayout(); mMainStage = spy(new StageTaskListener(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, - mIconProvider, Optional.of(mWindowDecorViewModel))); + mIconProvider, Optional.of(mWindowDecorViewModel), STAGE_TYPE_MAIN)); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mSideStage = spy(new StageTaskListener(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, - mIconProvider, Optional.of(mWindowDecorViewModel))); + mIconProvider, Optional.of(mWindowDecorViewModel), STAGE_TYPE_SIDE)); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index a252a9db7095..2d102fb27c66 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -19,6 +19,7 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -165,8 +166,9 @@ public class StageCoordinatorTests extends ShellTestCase { final WindowContainerTransaction wct = spy(new WindowContainerTransaction()); mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + // TODO(b/349828130) Address this once we remove index_undefined called verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false)); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false), eq(SPLIT_INDEX_UNDEFINED)); verify(mMainStage).reparentTopTask(eq(wct)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); @@ -183,8 +185,9 @@ public class StageCoordinatorTests extends ShellTestCase { final WindowContainerTransaction wct = new WindowContainerTransaction(); mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + // TODO(b/349828130) Address this once we remove index_undefined called verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false)); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false), eq(SPLIT_INDEX_UNDEFINED)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition()); assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition()); } @@ -195,8 +198,9 @@ public class StageCoordinatorTests extends ShellTestCase { final WindowContainerTransaction wct = new WindowContainerTransaction(); mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + // TODO(b/349828130) Address this once we remove index_undefined called verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false)); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false), eq(SPLIT_INDEX_UNDEFINED)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 7144a1e038f9..fe91440b106f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -19,6 +19,8 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; @@ -93,7 +95,8 @@ public final class StageTaskListenerTests extends ShellTestCase { mCallbacks, mSyncQueue, mIconProvider, - Optional.of(mWindowDecorViewModel)); + Optional.of(mWindowDecorViewModel), + STAGE_TYPE_UNDEFINED); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootTask.parentTaskId = INVALID_TASK_ID; mSurfaceControl = new SurfaceControl.Builder().setName("test").build(); diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java index f5913c763b82..4b3962e6dd74 100644 --- a/media/java/android/media/AudioDevicePort.java +++ b/media/java/android/media/AudioDevicePort.java @@ -22,6 +22,7 @@ import android.os.Build; import com.android.aconfig.annotations.VisibleForTesting; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -68,14 +69,14 @@ public class AudioDevicePort extends AudioPort { return new AudioDevicePort( new AudioHandle(/* id= */ 0), /* name= */ "testAudioDevicePort", - /* profiles= */ null, + /* profiles= */ new ArrayList<>(), /* gains= */ null, /* type= */ AudioManager.DEVICE_OUT_SPEAKER, /* address= */ "testAddress", /* speakerLayoutChannelMask= */ speakerLayoutChannelMask, /* encapsulationModes= */ null, /* encapsulationMetadataTypes= */ null, - /* descriptors= */ null); + /* descriptors= */ new ArrayList<>()); } private final int mType; diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java index da50f2cd86c4..c085b8985783 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -41,6 +41,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -59,6 +60,8 @@ public final class AudioPlaybackConfiguration implements Parcelable { public static final int PLAYER_UPID_INVALID = -1; /** @hide */ public static final int PLAYER_DEVICEID_INVALID = 0; + /** @hide */ + public static final int[] PLAYER_DEVICEIDS_INVALID = new int[0]; // information about the implementation /** @@ -335,7 +338,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { private final Object mUpdateablePropLock = new Object(); @GuardedBy("mUpdateablePropLock") - private int mDeviceId; + private @NonNull int[] mDeviceIds = AudioPlaybackConfiguration.PLAYER_DEVICEIDS_INVALID; @GuardedBy("mUpdateablePropLock") private int mSessionId; @GuardedBy("mUpdateablePropLock") @@ -364,7 +367,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { mClientUid = uid; mClientPid = pid; mMutedState = 0; - mDeviceId = PLAYER_DEVICEID_INVALID; + mDeviceIds = AudioPlaybackConfiguration.PLAYER_DEVICEIDS_INVALID; mPlayerState = PLAYER_STATE_IDLE; mPlayerAttr = pic.mAttributes; if ((sPlayerDeathMonitor != null) && (pic.mIPlayer != null)) { @@ -388,10 +391,11 @@ public final class AudioPlaybackConfiguration implements Parcelable { } // sets the fields that are updateable and require synchronization - private void setUpdateableFields(int deviceId, int sessionId, int mutedState, FormatInfo format) + private void setUpdateableFields(int[] deviceIds, int sessionId, int mutedState, + FormatInfo format) { synchronized (mUpdateablePropLock) { - mDeviceId = deviceId; + mDeviceIds = deviceIds; mSessionId = sessionId; mMutedState = mutedState; mFormatInfo = format; @@ -427,7 +431,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { anonymCopy.mClientPid = PLAYER_UPID_INVALID; anonymCopy.mIPlayerShell = null; anonymCopy.setUpdateableFields( - /*deviceId*/ PLAYER_DEVICEID_INVALID, + /*deviceIds*/ new int[0], /*sessionId*/ AudioSystem.AUDIO_SESSION_ALLOCATE, /*mutedState*/ 0, FormatInfo.DEFAULT); @@ -471,14 +475,14 @@ public final class AudioPlaybackConfiguration implements Parcelable { @Deprecated @FlaggedApi(FLAG_ROUTED_DEVICE_IDS) public @Nullable AudioDeviceInfo getAudioDeviceInfo() { - final int deviceId; + final int[] deviceIds; synchronized (mUpdateablePropLock) { - deviceId = mDeviceId; + deviceIds = mDeviceIds; } - if (deviceId == PLAYER_DEVICEID_INVALID) { + if (deviceIds.length == 0) { return null; } - return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_OUTPUTS); + return AudioManager.getDeviceForPortId(deviceIds[0], AudioManager.GET_DEVICES_OUTPUTS); } /** @@ -491,9 +495,17 @@ public final class AudioPlaybackConfiguration implements Parcelable { @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public @NonNull List<AudioDeviceInfo> getAudioDeviceInfos() { List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>(); - AudioDeviceInfo audioDeviceInfo = getAudioDeviceInfo(); - if (audioDeviceInfo != null) { - audioDeviceInfos.add(audioDeviceInfo); + final int[] deviceIds; + synchronized (mUpdateablePropLock) { + deviceIds = mDeviceIds; + } + + for (int i = 0; i < deviceIds.length; i++) { + AudioDeviceInfo audioDeviceInfo = AudioManager.getDeviceForPortId(deviceIds[i], + AudioManager.GET_DEVICES_OUTPUTS); + if (audioDeviceInfo != null) { + audioDeviceInfos.add(audioDeviceInfo); + } } return audioDeviceInfos; } @@ -701,15 +713,15 @@ public final class AudioPlaybackConfiguration implements Parcelable { * @hide * Handle a player state change * @param event - * @param deviceId active device id or {@Code PLAYER_DEVICEID_INVALID} - * <br>Note device id is valid for {@code PLAYER_UPDATE_DEVICE_ID} or - * <br>{@code PLAYER_STATE_STARTED} events, as the device id will be reset to none when - * <br>pausing or stopping playback. It will be set to active device when playback starts or + * @param deviceIds an array of device ids. This can be empty. + * <br>Note device ids are non-empty for {@code PLAYER_UPDATE_DEVICE_ID} or + * <br>{@code PLAYER_STATE_STARTED} events, as the device ids will be emptied when pausing + * <br>or stopping playback. It will be set to active devices when playback starts or * <br>it will be changed when PLAYER_UPDATE_DEVICE_ID is sent. The latter can happen if the - * <br>device changes in the middle of playback. + * <br>devices change in the middle of playback. * @return true if the state changed, false otherwise */ - public boolean handleStateEvent(int event, int deviceId) { + public boolean handleStateEvent(int event, int[] deviceIds) { boolean changed = false; synchronized (mUpdateablePropLock) { @@ -720,8 +732,8 @@ public final class AudioPlaybackConfiguration implements Parcelable { } if (event == PLAYER_STATE_STARTED || event == PLAYER_UPDATE_DEVICE_ID) { - changed = changed || (mDeviceId != deviceId); - mDeviceId = deviceId; + changed = changed || !Arrays.equals(mDeviceIds, deviceIds); + mDeviceIds = deviceIds; } if (changed && (event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) { @@ -801,8 +813,8 @@ public final class AudioPlaybackConfiguration implements Parcelable { @Override public int hashCode() { synchronized (mUpdateablePropLock) { - return Objects.hash(mPlayerIId, mDeviceId, mMutedState, mPlayerType, mClientUid, - mClientPid, mSessionId); + return Objects.hash(mPlayerIId, Arrays.toString(mDeviceIds), mMutedState, mPlayerType, + mClientUid, mClientPid, mSessionId); } } @@ -815,7 +827,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { public void writeToParcel(Parcel dest, int flags) { synchronized (mUpdateablePropLock) { dest.writeInt(mPlayerIId); - dest.writeInt(mDeviceId); + dest.writeIntArray(mDeviceIds); dest.writeInt(mMutedState); dest.writeInt(mPlayerType); dest.writeInt(mClientUid); @@ -834,7 +846,10 @@ public final class AudioPlaybackConfiguration implements Parcelable { private AudioPlaybackConfiguration(Parcel in) { mPlayerIId = in.readInt(); - mDeviceId = in.readInt(); + mDeviceIds = new int[in.readInt()]; + for (int i = 0; i < mDeviceIds.length; i++) { + mDeviceIds[i] = in.readInt(); + } mMutedState = in.readInt(); mPlayerType = in.readInt(); mClientUid = in.readInt(); @@ -855,7 +870,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { AudioPlaybackConfiguration that = (AudioPlaybackConfiguration) o; return ((mPlayerIId == that.mPlayerIId) - && (mDeviceId == that.mDeviceId) + && Arrays.equals(mDeviceIds, that.mDeviceIds) && (mMutedState == that.mMutedState) && (mPlayerType == that.mPlayerType) && (mClientUid == that.mClientUid) @@ -868,7 +883,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { StringBuilder apcToString = new StringBuilder(); synchronized (mUpdateablePropLock) { apcToString.append("AudioPlaybackConfiguration piid:").append(mPlayerIId).append( - " deviceId:").append(mDeviceId).append(" type:").append( + " deviceIds:").append(Arrays.toString(mDeviceIds)).append(" type:").append( toLogFriendlyPlayerType(mPlayerType)).append(" u/pid:").append( mClientUid).append( "/").append(mClientPid).append(" state:").append( diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 939494152116..cacd59f42ec4 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -1908,17 +1908,37 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, } /** + * Internal API of getRoutedDevices(). We should not call flag APIs internally. + */ + private @NonNull List<AudioDeviceInfo> getRoutedDevicesInternal() { + List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>(); + final int[] deviceIds = native_getRoutedDeviceIds(); + if (deviceIds == null || deviceIds.length == 0) { + return audioDeviceInfos; + } + + for (int i = 0; i < deviceIds.length; i++) { + AudioDeviceInfo audioDeviceInfo = AudioManager.getDeviceForPortId(deviceIds[i], + AudioManager.GET_DEVICES_INPUTS); + if (audioDeviceInfo != null) { + audioDeviceInfos.add(audioDeviceInfo); + } + } + return audioDeviceInfos; + } + + /** * Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioRecord. * Note: The query is only valid if the AudioRecord is currently recording. If it is not, * <code>getRoutedDevice()</code> will return null. */ @Override public AudioDeviceInfo getRoutedDevice() { - int deviceId = native_getRoutedDeviceId(); - if (deviceId == 0) { + final List<AudioDeviceInfo> audioDeviceInfos = getRoutedDevicesInternal(); + if (audioDeviceInfos.isEmpty()) { return null; } - return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_INPUTS); + return audioDeviceInfos.get(0); } /** @@ -1930,12 +1950,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, @Override @FlaggedApi(FLAG_ROUTED_DEVICE_IDS) public @NonNull List<AudioDeviceInfo> getRoutedDevices() { - List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>(); - AudioDeviceInfo audioDeviceInfo = getRoutedDevice(); - if (audioDeviceInfo != null) { - audioDeviceInfos.add(audioDeviceInfo); - } - return audioDeviceInfos; + return getRoutedDevicesInternal(); } /** @@ -2513,7 +2528,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, int sampleRateInHz, int channelCount, int audioFormat); private native final boolean native_setInputDevice(int deviceId); - private native final int native_getRoutedDeviceId(); + private native int[] native_getRoutedDeviceIds(); private native final void native_enableDeviceCallback(); private native final void native_disableDeviceCallback(); diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 93a183188793..a5d9adb5cadc 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -3023,7 +3023,7 @@ public class AudioTrack extends PlayerBase } } synchronized(mPlayStateLock) { - baseStart(0); // unknown device at this point + baseStart(new int[0]); // unknown device at this point native_start(); // FIXME see b/179218630 //baseStart(native_getRoutedDeviceId()); @@ -3784,6 +3784,26 @@ public class AudioTrack extends PlayerBase } /** + * Internal API of getRoutedDevices(). We should not call flag APIs internally. + */ + private @NonNull List<AudioDeviceInfo> getRoutedDevicesInternal() { + List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>(); + final int[] deviceIds = native_getRoutedDeviceIds(); + if (deviceIds == null || deviceIds.length == 0) { + return audioDeviceInfos; + } + + for (int i = 0; i < deviceIds.length; i++) { + AudioDeviceInfo audioDeviceInfo = AudioManager.getDeviceForPortId(deviceIds[i], + AudioManager.GET_DEVICES_OUTPUTS); + if (audioDeviceInfo != null) { + audioDeviceInfos.add(audioDeviceInfo); + } + } + return audioDeviceInfos; + } + + /** * Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioTrack. * Note: The query is only valid if the AudioTrack is currently playing. If it is not, * <code>getRoutedDevice()</code> will return null. @@ -3792,11 +3812,11 @@ public class AudioTrack extends PlayerBase */ @Override public AudioDeviceInfo getRoutedDevice() { - int deviceId = native_getRoutedDeviceId(); - if (deviceId == 0) { + final List<AudioDeviceInfo> audioDeviceInfos = getRoutedDevicesInternal(); + if (audioDeviceInfos.isEmpty()) { return null; } - return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_OUTPUTS); + return audioDeviceInfos.get(0); } /** @@ -3808,12 +3828,7 @@ public class AudioTrack extends PlayerBase @Override @FlaggedApi(FLAG_ROUTED_DEVICE_IDS) public @NonNull List<AudioDeviceInfo> getRoutedDevices() { - List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>(); - AudioDeviceInfo audioDeviceInfo = getRoutedDevice(); - if (audioDeviceInfo != null) { - audioDeviceInfos.add(audioDeviceInfo); - } - return audioDeviceInfos; + return getRoutedDevicesInternal(); } private void tryToDisableNativeRoutingCallback() { @@ -3973,7 +3988,7 @@ public class AudioTrack extends PlayerBase */ private void broadcastRoutingChange() { AudioManager.resetAudioPortGeneration(); - baseUpdateDeviceId(getRoutedDevice()); + baseUpdateDeviceIds(getRoutedDevicesInternal()); synchronized (mRoutingChangeListeners) { for (NativeRoutingEventHandlerDelegate delegate : mRoutingChangeListeners.values()) { delegate.notifyClient(); @@ -4530,7 +4545,7 @@ public class AudioTrack extends PlayerBase private native final int native_setAuxEffectSendLevel(float level); private native final boolean native_setOutputDevice(int deviceId); - private native final int native_getRoutedDeviceId(); + private native int[] native_getRoutedDeviceIds(); private native final void native_enableDeviceCallback(); private native final void native_disableDeviceCallback(); diff --git a/media/java/android/media/HwAudioSource.java b/media/java/android/media/HwAudioSource.java index 167ab6535843..68a3aa7eb834 100644 --- a/media/java/android/media/HwAudioSource.java +++ b/media/java/android/media/HwAudioSource.java @@ -145,7 +145,14 @@ public class HwAudioSource extends PlayerBase { mAudioAttributes); if (isPlaying()) { // FIXME: b/174876389 clean up device id reporting - baseStart(getDeviceId()); + // Set as deviceIds empty and create an array with element if device id is valid. + int[] deviceIds = AudioPlaybackConfiguration.PLAYER_DEVICEIDS_INVALID; + int deviceId = getDeviceId(); + if (deviceId != 0) { + deviceIds = new int[1]; + deviceIds[0] = deviceId; + } + baseStart(deviceIds); } } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 9fd3f5beb25c..08b0dd3fb11c 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -87,7 +87,7 @@ interface IAudioService { oneway void playerAttributes(in int piid, in AudioAttributes attr); - oneway void playerEvent(in int piid, in int event, in int eventId); + oneway void playerEvent(in int piid, in int event, in int[] eventId); oneway void releasePlayer(in int piid); diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 96edd63a9b12..782db358bf9f 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -1876,6 +1876,8 @@ public final class MediaCodecInfo { * Codecs with this security model is not included in * {@link MediaCodecList#REGULAR_CODECS}, but included in * {@link MediaCodecList#ALL_CODECS}. + * + * @hide */ @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) public static final int SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 2; diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index bd65b2ecb76a..bc09aee9ac11 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -1748,6 +1748,7 @@ public final class MediaFormat { (1 << MediaCodecInfo.SECURITY_MODEL_MEMORY_SAFE); /** * Flag for {@link MediaCodecInfo#SECURITY_MODEL_TRUSTED_CONTENT_ONLY}. + * @hide */ @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) public static final int FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY = @@ -1759,8 +1760,7 @@ public final class MediaFormat { * The associated value is a flag of the following values: * {@link FLAG_SECURITY_MODEL_SANDBOXED}, * {@link FLAG_SECURITY_MODEL_MEMORY_SAFE}, - * {@link FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY}. The default value is - * {@link FLAG_SECURITY_MODEL_SANDBOXED}. + * The default value is {@link FLAG_SECURITY_MODEL_SANDBOXED}. * <p> * When passed to {@link MediaCodecList#findDecoderForFormat} or * {@link MediaCodecList#findEncoderForFormat}, MediaCodecList filters diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 158bc7fcd482..f1c2a7aec903 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1415,7 +1415,7 @@ public class MediaPlayer extends PlayerBase } private void startImpl() { - baseStart(0); // unknown device at this point + baseStart(new int[0]); // unknown device at this point stayAwake(true); tryToEnableNativeRoutingCallback(); _start(); @@ -1541,6 +1541,26 @@ public class MediaPlayer extends PlayerBase } /** + * Internal API of getRoutedDevices(). We should not call flag APIs internally. + */ + private @NonNull List<AudioDeviceInfo> getRoutedDevicesInternal() { + List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>(); + final int[] deviceIds = native_getRoutedDeviceIds(); + if (deviceIds == null || deviceIds.length == 0) { + return audioDeviceInfos; + } + + for (int i = 0; i < deviceIds.length; i++) { + AudioDeviceInfo audioDeviceInfo = AudioManager.getDeviceForPortId(deviceIds[i], + AudioManager.GET_DEVICES_OUTPUTS); + if (audioDeviceInfo != null) { + audioDeviceInfos.add(audioDeviceInfo); + } + } + return audioDeviceInfos; + } + + /** * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer * Note: The query is only valid if the MediaPlayer is currently playing. * If the player is not playing, the returned device can be null or correspond to previously @@ -1550,11 +1570,11 @@ public class MediaPlayer extends PlayerBase */ @Override public AudioDeviceInfo getRoutedDevice() { - int deviceId = native_getRoutedDeviceId(); - if (deviceId == 0) { + final List<AudioDeviceInfo> audioDeviceInfos = getRoutedDevicesInternal(); + if (audioDeviceInfos.isEmpty()) { return null; } - return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_OUTPUTS); + return audioDeviceInfos.get(0); } /** @@ -1567,12 +1587,7 @@ public class MediaPlayer extends PlayerBase @Override @FlaggedApi(FLAG_ROUTED_DEVICE_IDS) public @NonNull List<AudioDeviceInfo> getRoutedDevices() { - List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>(); - AudioDeviceInfo audioDeviceInfo = getRoutedDevice(); - if (audioDeviceInfo != null) { - audioDeviceInfos.add(audioDeviceInfo); - } - return audioDeviceInfos; + return getRoutedDevicesInternal(); } /** @@ -1584,7 +1599,7 @@ public class MediaPlayer extends PlayerBase // Prevent the case where an event is triggered by registering a routing change // listener via the media player. if (mEnableSelfRoutingMonitor) { - baseUpdateDeviceId(getRoutedDevice()); + baseUpdateDeviceIds(getRoutedDevicesInternal()); } for (NativeRoutingEventHandlerDelegate delegate : mRoutingChangeListeners.values()) { @@ -1694,7 +1709,7 @@ public class MediaPlayer extends PlayerBase } private native final boolean native_setOutputDevice(int deviceId); - private native final int native_getRoutedDeviceId(); + private native int[] native_getRoutedDeviceIds(); private native final void native_enableDeviceCallback(boolean enabled); /** diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index f75bcf3c437d..7af78b81cda5 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -1684,6 +1684,26 @@ public class MediaRecorder implements AudioRouting, } /** + * Internal API of getRoutedDevices(). We should not call flag APIs internally. + */ + private @NonNull List<AudioDeviceInfo> getRoutedDevicesInternal() { + List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>(); + final int[] deviceIds = native_getRoutedDeviceIds(); + if (deviceIds == null || deviceIds.length == 0) { + return audioDeviceInfos; + } + + for (int i = 0; i < deviceIds.length; i++) { + AudioDeviceInfo audioDeviceInfo = AudioManager.getDeviceForPortId(deviceIds[i], + AudioManager.GET_DEVICES_INPUTS); + if (audioDeviceInfo != null) { + audioDeviceInfos.add(audioDeviceInfo); + } + } + return audioDeviceInfos; + } + + /** * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaRecorder * Note: The query is only valid if the MediaRecorder is currently recording. * If the recorder is not recording, the returned device can be null or correspond to previously @@ -1691,11 +1711,11 @@ public class MediaRecorder implements AudioRouting, */ @Override public AudioDeviceInfo getRoutedDevice() { - int deviceId = native_getRoutedDeviceId(); - if (deviceId == 0) { + final List<AudioDeviceInfo> audioDeviceInfos = getRoutedDevicesInternal(); + if (audioDeviceInfos.isEmpty()) { return null; } - return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_INPUTS); + return audioDeviceInfos.get(0); } /** @@ -1708,12 +1728,7 @@ public class MediaRecorder implements AudioRouting, @Override @FlaggedApi(FLAG_ROUTED_DEVICE_IDS) public @NonNull List<AudioDeviceInfo> getRoutedDevices() { - List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>(); - AudioDeviceInfo audioDeviceInfo = getRoutedDevice(); - if (audioDeviceInfo != null) { - audioDeviceInfos.add(audioDeviceInfo); - } - return audioDeviceInfos; + return getRoutedDevicesInternal(); } /* @@ -1773,7 +1788,7 @@ public class MediaRecorder implements AudioRouting, } private native final boolean native_setInputDevice(int deviceId); - private native final int native_getRoutedDeviceId(); + private native int[] native_getRoutedDeviceIds(); private native final void native_enableDeviceCallback(boolean enabled); //-------------------------------------------------------------------------- diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java index 3f44b09124e4..dbf8338ac99f 100644 --- a/media/java/android/media/PlayerBase.java +++ b/media/java/android/media/PlayerBase.java @@ -39,6 +39,8 @@ import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.List; import java.util.Objects; /** @@ -91,7 +93,7 @@ public abstract class PlayerBase { @GuardedBy("mLock") private float mVolMultiplier = 1.0f; @GuardedBy("mLock") - private int mDeviceId; + private @NonNull int[] mDeviceIds = AudioPlaybackConfiguration.PLAYER_DEVICEIDS_INVALID; /** * Constructor. Must be given audio attributes, as they are required for AppOps. @@ -158,35 +160,36 @@ public abstract class PlayerBase { } } - void baseUpdateDeviceId(@Nullable AudioDeviceInfo deviceInfo) { - int deviceId = 0; - if (deviceInfo != null) { - deviceId = deviceInfo.getId(); + void baseUpdateDeviceIds(@NonNull List<AudioDeviceInfo> deviceInfos) { + int[] deviceIds = new int[deviceInfos.size()]; + for (int i = 0; i < deviceInfos.size(); i++) { + deviceIds[i] = deviceInfos.get(i).getId(); } + int piid; synchronized (mLock) { piid = mPlayerIId; - mDeviceId = deviceId; + mDeviceIds = deviceIds; } try { getService().playerEvent(piid, - AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID, deviceId); + AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID, deviceIds); } catch (RemoteException e) { Log.e(TAG, "Error talking to audio service, " - + deviceId + + Arrays.toString(deviceIds) + " device id will not be tracked for piid=" + piid, e); } } - private void updateState(int state, int deviceId) { + private void updateState(int state, @NonNull int[] deviceIds) { final int piid; synchronized (mLock) { mState = state; piid = mPlayerIId; - mDeviceId = deviceId; + mDeviceIds = deviceIds; } try { - getService().playerEvent(piid, state, deviceId); + getService().playerEvent(piid, state, deviceIds); } catch (RemoteException e) { Log.e(TAG, "Error talking to audio service, " + AudioPlaybackConfiguration.toLogFriendlyPlayerState(state) @@ -194,11 +197,12 @@ public abstract class PlayerBase { } } - void baseStart(int deviceId) { + void baseStart(@NonNull int[] deviceIds) { if (DEBUG) { - Log.v(TAG, "baseStart() piid=" + mPlayerIId + " deviceId=" + deviceId); + Log.v(TAG, "baseStart() piid=" + mPlayerIId + " deviceId=" + + Arrays.toString(deviceIds)); } - updateState(AudioPlaybackConfiguration.PLAYER_STATE_STARTED, deviceId); + updateState(AudioPlaybackConfiguration.PLAYER_STATE_STARTED, deviceIds); } void baseSetStartDelayMs(int delayMs) { @@ -215,12 +219,12 @@ public abstract class PlayerBase { void basePause() { if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); } - updateState(AudioPlaybackConfiguration.PLAYER_STATE_PAUSED, 0); + updateState(AudioPlaybackConfiguration.PLAYER_STATE_PAUSED, new int[0]); } void baseStop() { if (DEBUG) { Log.v(TAG, "baseStop() piid=" + mPlayerIId); } - updateState(AudioPlaybackConfiguration.PLAYER_STATE_STOPPED, 0); + updateState(AudioPlaybackConfiguration.PLAYER_STATE_STOPPED, new int[0]); } void baseSetPan(float pan) { diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java index 7b9ff23596d9..f22cc9c08b4c 100644 --- a/media/java/android/media/SoundPool.java +++ b/media/java/android/media/SoundPool.java @@ -317,7 +317,7 @@ public class SoundPool extends PlayerBase { // FIXME: b/174876164 implement device id for soundpool try { Trace.traceBegin(Trace.TRACE_TAG_AUDIO, "SoundPool.play"); - baseStart(0); + baseStart(new int[0]); return _play(soundID, leftVolume, rightVolume, priority, loop, rate, getPlayerIId()); } finally { Trace.traceEnd(Trace.TRACE_TAG_AUDIO); diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java index f07ef873a0af..5fec86a81a53 100644 --- a/media/java/android/media/quality/MediaQualityContract.java +++ b/media/java/android/media/quality/MediaQualityContract.java @@ -23,7 +23,6 @@ import android.media.tv.flags.Flags; /** * The contract between the media quality service and applications. Contains definitions for the * commonly used parameter names. - * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) public class MediaQualityContract { @@ -42,9 +41,8 @@ public class MediaQualityContract { /** * Parameters picture quality. - * @hide */ - public static final class PictureQuality implements BaseParameters { + public static final class PictureQuality { /** * The brightness. * diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index dcf497122053..28fe9b6c8112 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.media.tv.flags.Flags; @@ -37,7 +38,6 @@ import java.util.concurrent.Executor; /** * Central system API to the overall media quality, which arbitrates interaction between * applications and media quality service. - * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) @SystemService(Context.MEDIA_QUALITY_SERVICE) @@ -180,7 +180,6 @@ public final class MediaQualityManager { /** * Registers a {@link PictureProfileCallback}. - * @hide */ public void registerPictureProfileCallback( @NonNull @CallbackExecutor Executor executor, @@ -194,7 +193,6 @@ public final class MediaQualityManager { /** * Unregisters the existing {@link PictureProfileCallback}. - * @hide */ public void unregisterPictureProfileCallback(@NonNull final PictureProfileCallback callback) { Preconditions.checkNotNull(callback); @@ -216,7 +214,6 @@ public final class MediaQualityManager { * * @return the corresponding picture profile if available; {@code null} if the name doesn't * exist. - * @hide */ @Nullable public PictureProfile getPictureProfile( @@ -232,8 +229,9 @@ public final class MediaQualityManager { /** * Gets profiles that available to the given package. * - * @hide @SystemApi + * @hide */ + @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) { @@ -260,8 +258,9 @@ public final class MediaQualityManager { * Gets all package names whose picture profiles are available. * * @see #getPictureProfilesByPackage(String) - * @hide @SystemApi + * @hide */ + @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<String> getPictureProfilePackageNames() { @@ -276,14 +275,12 @@ public final class MediaQualityManager { /** * Creates a picture profile and store it in the system. * - * @return the stored profile with an assigned profile ID. {@code null} if it's not created - * successfully. - * @hide + * <p>If the profile is created successfully, + * {@link PictureProfileCallback#onPictureProfileAdded(String, PictureProfile)} is invoked. */ - @Nullable - public PictureProfile createPictureProfile(@NonNull PictureProfile pp) { + public void createPictureProfile(@NonNull PictureProfile pp) { try { - return mService.createPictureProfile(pp); + mService.createPictureProfile(pp); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -292,7 +289,6 @@ public final class MediaQualityManager { /** * Updates an existing picture profile and store it in the system. - * @hide */ public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) { try { @@ -305,7 +301,6 @@ public final class MediaQualityManager { /** * Removes a picture profile from the system. - * @hide */ public void removePictureProfile(@NonNull String profileId) { try { @@ -453,7 +448,6 @@ public final class MediaQualityManager { /** * Gets capability information of the given parameters. - * @hide */ @NonNull public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) { @@ -471,6 +465,7 @@ public final class MediaQualityManager { * @see #removePictureProfile(String) * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) @NonNull public List<String> getPictureProfileAllowList() { @@ -485,6 +480,7 @@ public final class MediaQualityManager { * Sets the allowlist of packages that can create and removed picture profiles * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setPictureProfileAllowList(@NonNull List<String> packageNames) { try { @@ -544,6 +540,7 @@ public final class MediaQualityManager { * @param enabled {@code true} to enable, {@code false} to disable. * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setAutoPictureQualityEnabled(boolean enabled) { try { @@ -555,7 +552,6 @@ public final class MediaQualityManager { /** * Returns {@code true} if auto picture quality is enabled; {@code false} otherwise. - * @hide */ public boolean isAutoPictureQualityEnabled() { try { @@ -572,6 +568,7 @@ public final class MediaQualityManager { * @param enabled {@code true} to enable, {@code false} to disable. * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setSuperResolutionEnabled(boolean enabled) { try { @@ -583,7 +580,6 @@ public final class MediaQualityManager { /** * Returns {@code true} if super resolution is enabled; {@code false} otherwise. - * @hide */ public boolean isSuperResolutionEnabled() { try { @@ -830,8 +826,7 @@ public final class MediaQualityManager { } /** - * Callback used to monitor status of picture profiles. - * @hide + * Callback used to monitor status of picture profiles */ public abstract static class PictureProfileCallback { /** @@ -839,7 +834,6 @@ public final class MediaQualityManager { * * @param profileId the ID of the profile. * @param profile the newly added profile. - * @hide */ public void onPictureProfileAdded( @NonNull String profileId, @NonNull PictureProfile profile) { @@ -850,7 +844,6 @@ public final class MediaQualityManager { * * @param profileId the ID of the profile. * @param profile the profile with updated info. - * @hide */ public void onPictureProfileUpdated( @NonNull String profileId, @NonNull PictureProfile profile) { @@ -861,7 +854,6 @@ public final class MediaQualityManager { * * @param profileId the ID of the profile. * @param profile the removed profile. - * @hide */ public void onPictureProfileRemoved( @NonNull String profileId, @NonNull PictureProfile profile) { @@ -871,7 +863,6 @@ public final class MediaQualityManager { * This is invoked when an issue has occurred. * * @param errorCode the error code - * @hide */ public void onError(@PictureProfile.ErrorCode int errorCode) { } @@ -883,7 +874,6 @@ public final class MediaQualityManager { * @param profileId the ID of the profile used by the media content. {@code null} if there * is no associated profile * @param updatedCaps the updated capabilities. - * @hide */ public void onParamCapabilitiesChanged( @Nullable String profileId, @NonNull List<ParamCapability> updatedCaps) { diff --git a/media/java/android/media/quality/ParamCapability.java b/media/java/android/media/quality/ParamCapability.java index 0b698a9c1ad2..ed11abd28379 100644 --- a/media/java/android/media/quality/ParamCapability.java +++ b/media/java/android/media/quality/ParamCapability.java @@ -31,7 +31,6 @@ import java.lang.annotation.RetentionPolicy; /** * Capability info of media quality parameters - * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) public final class ParamCapability implements Parcelable { diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java index 2be47dd87ef2..dcb4222c3eaf 100644 --- a/media/java/android/media/quality/PictureProfile.java +++ b/media/java/android/media/quality/PictureProfile.java @@ -18,11 +18,12 @@ package android.media.quality; import android.annotation.FlaggedApi; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.media.tv.TvInputInfo; import android.media.tv.flags.Flags; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.PersistableBundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -33,7 +34,6 @@ import java.lang.annotation.RetentionPolicy; /** * Profile for picture quality. - * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) public final class PictureProfile implements Parcelable { @@ -47,7 +47,7 @@ public final class PictureProfile implements Parcelable { @NonNull private final String mPackageName; @NonNull - private final Bundle mParams; + private final PersistableBundle mParams; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -59,14 +59,14 @@ public final class PictureProfile implements Parcelable { /** * System profile type. * - * <p>A profile of system type is managed by the system, and readable to the package define in + * <p>A profile of system type is managed by the system, and readable to the package returned by * {@link #getPackageName()}. */ public static final int TYPE_SYSTEM = 1; /** * Application profile type. * - * <p>A profile of application type is managed by the package define in + * <p>A profile of application type is managed by the package returned by * {@link #getPackageName()}. */ public static final int TYPE_APPLICATION = 2; @@ -84,13 +84,11 @@ public final class PictureProfile implements Parcelable { /** * Error code for unknown errors. - * @hide */ public static final int ERROR_UNKNOWN = 0; /** * Error code for missing necessary permission to handle the profiles. - * @hide */ public static final int ERROR_NO_PERMISSION = 1; @@ -99,13 +97,11 @@ public final class PictureProfile implements Parcelable { * * @see #getProfileType() * @see #getName() - * @hide */ public static final int ERROR_DUPLICATE = 2; /** * Error code for invalid argument. - * @hide */ public static final int ERROR_INVALID_ARGUMENT = 3; @@ -114,7 +110,6 @@ public final class PictureProfile implements Parcelable { * list. * * @see MediaQualityManager#getPictureProfileAllowList() - * @hide */ public static final int ERROR_NOT_ALLOWLISTED = 4; @@ -125,7 +120,7 @@ public final class PictureProfile implements Parcelable { mName = in.readString(); mInputId = in.readString(); mPackageName = in.readString(); - mParams = in.readBundle(); + mParams = in.readPersistableBundle(); } @Override @@ -135,7 +130,7 @@ public final class PictureProfile implements Parcelable { dest.writeString(mName); dest.writeString(mInputId); dest.writeString(mPackageName); - dest.writeBundle(mParams); + dest.writePersistableBundle(mParams); } @Override @@ -168,7 +163,7 @@ public final class PictureProfile implements Parcelable { @NonNull String name, @Nullable String inputId, @NonNull String packageName, - @NonNull Bundle params) { + @NonNull PersistableBundle params) { this.mId = id; this.mType = type; this.mName = name; @@ -251,13 +246,12 @@ public final class PictureProfile implements Parcelable { * {@link MediaQualityContract.PictureQuality}. */ @NonNull - public Bundle getParameters() { - return new Bundle(mParams); + public PersistableBundle getParameters() { + return new PersistableBundle(mParams); } /** * A builder for {@link PictureProfile}. - * @hide */ public static final class Builder { @Nullable @@ -270,7 +264,7 @@ public final class PictureProfile implements Parcelable { @NonNull private String mPackageName; @NonNull - private Bundle mParams; + private PersistableBundle mParams; /** * Creates a new Builder. @@ -291,8 +285,6 @@ public final class PictureProfile implements Parcelable { mParams = p.getParameters(); } - /* @hide using by MediaQualityService */ - /** * Only used by system to assign the ID. * @hide @@ -306,8 +298,9 @@ public final class PictureProfile implements Parcelable { /** * Sets profile type. * - * @hide @SystemApi + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) @NonNull public Builder setProfileType(@ProfileType int value) { @@ -320,8 +313,9 @@ public final class PictureProfile implements Parcelable { * * @see PictureProfile#getInputId() * - * @hide @SystemApi + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) @NonNull public Builder setInputId(@NonNull String value) { @@ -334,8 +328,9 @@ public final class PictureProfile implements Parcelable { * * @see PictureProfile#getPackageName() * - * @hide @SystemApi + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) @NonNull public Builder setPackageName(@NonNull String value) { @@ -349,8 +344,8 @@ public final class PictureProfile implements Parcelable { * @see PictureProfile#getParameters() */ @NonNull - public Builder setParameters(@NonNull Bundle params) { - mParams = new Bundle(params); + public Builder setParameters(@NonNull PersistableBundle params) { + mParams = new PersistableBundle(params); return this; } diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java index 9442726508c6..b876bcf8cd7e 100644 --- a/media/java/android/media/tv/TvInputServiceExtensionManager.java +++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java @@ -194,96 +194,78 @@ public final class TvInputServiceExtensionManager { public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface"; /** * Interface that handles scan session and get/store related information. - * @hide */ public static final String ISCAN_SESSION = SCAN_PACKAGE + "IScanSession"; /** - * Interface that notifies changes related to scan session. - * @hide + * Interface that notifies changes related to a scan session. */ public static final String ISCAN_LISTENER = SCAN_PACKAGE + "IScanListener"; /** * Interface for setting HDPlus information. - * @hide */ public static final String IHDPLUS_INFO = SCAN_PACKAGE + "IHDPlusInfo"; /** * Interface for handling operator detection for scanning. - * @hide */ public static final String IOPERATOR_DETECTION = SCAN_PACKAGE + "IOperatorDetection"; /** - * Interface for changes related to operator detection searches. - * @hide + * Interface for notifying changes related to operator detection searches. */ public static final String IOPERATOR_DETECTION_LISTENER = SCAN_PACKAGE + "IOperatorDetectionListener"; /** * Interface for handling region channel list for scanning. - * @hide */ public static final String IREGION_CHANNEL_LIST = SCAN_PACKAGE + "IRegionChannelList"; /** - * Interface for changes related to changes in region channel list search. - * @hide + * Interface for notifying changes related to changes in region channel list search. */ public static final String IREGION_CHANNEL_LIST_LISTENER = SCAN_PACKAGE + "IRegionChannelListListener"; /** * Interface for handling target region information. - * @hide */ public static final String ITARGET_REGION = SCAN_PACKAGE + "ITargetRegion"; /** - * Interface for changes related to target regions during scanning. - * @hide + * Interface for detecting changes related to target regions. */ public static final String ITARGET_REGION_LISTENER = SCAN_PACKAGE + "ITargetRegionListener"; /** - * Interface for handling LCN conflict groups. - * @hide + * Interface for handling logical channel number conflict groups. */ public static final String ILCN_CONFLICT = SCAN_PACKAGE + "ILcnConflict"; /** - * Interface for detecting LCN conflicts during scanning. - * @hide + * Interface for notifying changes in handling logical channel number conflicts. */ public static final String ILCN_CONFLICT_LISTENER = SCAN_PACKAGE + "ILcnConflictListener"; /** - * Interface for handling LCN V2 channel list information. - * @hide + * Interface for handling the updated standard for assigning logical channel numbers. */ public static final String ILCNV2_CHANNEL_LIST = SCAN_PACKAGE + "ILcnV2ChannelList"; /** - * Interface for detecting LCN V2 channel list during scanning. - * @hide + * Interface for notifying changes in assigning logical channel numbers with updated standard. */ public static final String ILCNV2_CHANNEL_LIST_LISTENER = SCAN_PACKAGE + "ILcnV2ChannelListListener"; /** * Interface for handling favorite network related information. - * @hide */ public static final String IFAVORITE_NETWORK = SCAN_PACKAGE + "IFavoriteNetwork"; /** - * Interface for detecting favorite network during scanning. - * @hide + * Interface for notifying changes favorite network during scanning. */ public static final String IFAVORITE_NETWORK_LISTENER = SCAN_PACKAGE + "IFavoriteNetworkListener"; /** - * Interface for handling Turksat channel update system service. - * @hide + * Interface for handling Turksat(TKGS) channel update system service. */ public static final String ITKGS_INFO = SCAN_PACKAGE + "ITkgsInfo"; /** - * Interface for changes related to TKGS information. - * @hide + * Interface for notifying changes related to Turksat(TKGS) information. */ public static final String ITKGS_INFO_LISTENER = SCAN_PACKAGE + "ITkgsInfoListener"; /** * Interface for satellite search related to low noise block downconverter. - * @hide */ public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch"; /** @@ -295,113 +277,94 @@ public final class TvInputServiceExtensionManager { */ public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService"; /** - * Interface for changes on conditional access module app related information. - * @hide + * Interface for notifying changes on conditional access module app related information. */ public static final String ICAM_APP_INFO_LISTENER = CAM_PACKAGE + "ICamAppInfoListener"; /** * Interface for handling conditional access module related information. - * @hide */ public static final String ICAM_MONITORING_SERVICE = CAM_PACKAGE + "ICamMonitoringService"; /** - * Interface for changes on conditional access module related information. - * @hide + * Interface for notifying changes on conditional access module related information. */ public static final String ICAM_INFO_LISTENER = CAM_PACKAGE + "ICamInfoListener"; /** - * Interface for handling control of CI+ operations. - * @hide + * Interface for handling control of common interface plus operations. */ public static final String ICI_OPERATOR_INTERFACE = CAM_PACKAGE + "ICiOperatorInterface"; /** - * Interfaces for changes on CI+ operations. - * @hide + * Interfaces for notifying changes on common interface plus operations. */ public static final String ICI_OPERATOR_LISTENER = CAM_PACKAGE + "ICiOperatorListener"; /** * Interface for handling conditional access module profile related information. - * @hide */ public static final String ICAM_PROFILE_INTERFACE = CAM_PACKAGE + "ICamProfileInterface"; /** - * Interface for handling conditional access module DRM related information. - * @hide + * Interface for handling conditional access module digital rights management (DRM) + * related information. */ public static final String ICONTENT_CONTROL_SERVICE = CAM_PACKAGE + "IContentControlService"; /** - * Interface for changes on DRM. - * @hide + * Interface for notifying changes on digital rights management (DRM). */ public static final String ICAM_DRM_INFO_LISTENER = CAM_PACKAGE + "ICamDrmInfoListener"; /** * Interface for handling conditional access module pin related information. - * @hide */ public static final String ICAM_PIN_SERVICE = CAM_PACKAGE + "ICamPinService"; /** - * Interface for changes on conditional access module pin capability. - * @hide + * Interface for notifying changes on conditional access module pin capability. */ public static final String ICAM_PIN_CAPABILITY_LISTENER = CAM_PACKAGE + "ICamPinCapabilityListener"; /** - * Interface for changes on conditional access module pin status. - * @hide + * Interface for notifying changes on conditional access module pin status. */ public static final String ICAM_PIN_STATUS_LISTENER = CAM_PACKAGE + "ICamPinStatusListener"; /** * Interface for handling conditional access module host control service. - * @hide */ public static final String ICAM_HOST_CONTROL_SERVICE = CAM_PACKAGE + "ICamHostControlService"; /** * Interface for handling conditional access module ask release reply. - * @hide */ public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = CAM_PACKAGE + "ICamHostControlAskReleaseReplyCallback"; /** - * Interface for changes on conditional access module host control service. - * @hide + * Interface for notifying changes on conditional access module host control service. */ public static final String ICAM_HOST_CONTROL_INFO_LISTENER = CAM_PACKAGE + "ICamHostControlInfoListener"; /** * Interface for handling conditional access module host control service tune_quietly_flag. - * @hide */ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG = CAM_PACKAGE + "ICamHostControlTuneQuietlyFlag"; /** - * Interface for changes on conditional access module host control service tune_quietly_flag. - * @hide + * Interface for notifying changes on conditional access module host control service + * tune_quietly_flag. */ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = CAM_PACKAGE + "ICamHostControlTuneQuietlyFlagListener"; /** - * Interface for handling conditional access module multi media interface. - * @hide + * Interface for handling conditional access module multi-media interface. */ public static final String IMMI_INTERFACE = CAM_PACKAGE + "IMmiInterface"; /** - * Interface for controlling conditional access module multi media session. - * @hide + * Interface for controlling conditional access module multi-media session. */ public static final String IMMI_SESSION = CAM_PACKAGE + "IMmiSession"; /** - * Interface for changes on conditional access module multi media session status. - * @hide + * Interface for notifying changes on conditional access module multi-media session status. */ public static final String IMMI_STATUS_CALLBACK = CAM_PACKAGE + "IMmiStatusCallback"; /** - * Interface for changes on conditional access app info related to entering menu. - * @hide + * Interface for notifying changes on conditional access app info related to entering menu. */ public static final String IENTER_MENU_ERROR_CALLBACK = CAM_PACKAGE + "IEnterMenuErrorCallback"; /** - * Interface for handling RRT downloadable rating data. - * @hide + * Interface for handling Region Rating Table downloadable rating data. */ public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE + "IDownloadableRatingTableMonitor"; @@ -410,64 +373,54 @@ public final class TvInputServiceExtensionManager { */ public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface"; /** - * Interface for handling PMT rating related information. - * @hide + * Interface for handling Program Map Table rating related information. */ public static final String IPMT_RATING_INTERFACE = RATING_PACKAGE + "IPmtRatingInterface"; /** - * Interface for changes on PMT rating related information. - * @hide + * Interface for notifying changes on Program Map Table rating related information. */ public static final String IPMT_RATING_LISTENER = RATING_PACKAGE + "IPmtRatingListener"; /** - * Interface for handling IVBI rating related information. - * @hide + * Interface for handling Vertical Blanking Interval rating related information. */ public static final String IVBI_RATING_INTERFACE = RATING_PACKAGE + "IVbiRatingInterface"; /** - * Interface for changes on IVBI rating related information. - * @hide + * Interface for notifying changes on Vertical Blanking Interval rating related information. */ public static final String IVBI_RATING_LISTENER = RATING_PACKAGE + "IVbiRatingListener"; /** * Interface for handling program rating related information. - * @hide */ public static final String IPROGRAM_INFO = RATING_PACKAGE + "IProgramInfo"; /** - * Interface for changes on program rating related information. - * @hide + * Interface for notifying changes on program rating related information. */ public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener"; /** * Interface for getting broadcast time related information. */ - public static final String IBROADCAST_TIME = TIME_PACKAGE + "BroadcastTime"; + public static final String IBROADCAST_TIME = TIME_PACKAGE + "IBroadcastTime"; /** * Interface for handling data service signal information on teletext. */ public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE + "IDataServiceSignalInfo"; /** - * Interface for changes on data service signal information on teletext. - * @hide + * Interface for notifying changes on data service signal information on teletext. */ public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = TELETEXT_PACKAGE + "IDataServiceSignalInfoListener"; /** * Interface for handling teletext page information. - * @hide */ public static final String ITELETEXT_PAGE_SUB_CODE = TELETEXT_PACKAGE + "ITeletextPageSubCode"; /** * Interface for handling scan background service update. - * @hide */ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = SCAN_BSU_PACKAGE + "IScanBackgroundServiceUpdate"; /** - * Interface for changes on background service update - * @hide + * Interface for notifying changes on background service update */ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = SCAN_BSU_PACKAGE + "IScanBackgroundServiceUpdateListener"; @@ -484,98 +437,82 @@ public final class TvInputServiceExtensionManager { */ public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface"; /** - * Interfaces for changes on HDMI signal information update. - * @hide + * Interfaces for notifying changes on HDMI signal information update. */ public static final String IHDMI_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "IHdmiSignalInfoListener"; /** * Interfaces for handling audio signal information update. - * @hide */ public static final String IAUDIO_SIGNAL_INFO = SIGNAL_PACKAGE + "IAudioSignalInfo"; /** * Interfaces for handling analog audio signal information update. - * @hide */ public static final String IANALOG_AUDIO_INFO = SIGNAL_PACKAGE + "IAnalogAudioInfo"; /** - * Interfaces for change on audio signal information update. - * @hide + * Interfaces for notifying changes on audio signal information update. */ public static final String IAUDIO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "IAudioSignalInfoListener"; /** * Interfaces for handling video signal information update. - * @hide */ public static final String IVIDEO_SIGNAL_INFO = SIGNAL_PACKAGE + "IVideoSignalInfo"; /** - * Interfaces for changes on video signal information update. - * @hide + * Interfaces for notifying changes on video signal information update. */ public static final String IVIDEO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "IVideoSignalInfoListener"; /** * Interfaces for handling service database updates. - * @hide */ public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit"; /** - * Interfaces for changes on service database updates. + * Interfaces for notifying changes on service database updates. */ public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListEditListener"; /** * Interfaces for getting service database related information. - * @hide */ public static final String ISERVICE_LIST = SERVICE_DATABASE_PACKAGE + "IServiceList"; /** * Interfaces for transferring service database related information. - * @hide */ public static final String ISERVICE_LIST_TRANSFER_INTERFACE = SERVICE_DATABASE_PACKAGE + "IServiceListTransferInterface"; /** * Interfaces for exporting service database session. - * @hide */ public static final String ISERVICE_LIST_EXPORT_SESSION = SERVICE_DATABASE_PACKAGE + "IServiceListExportSession"; /** - * Interfaces for changes on exporting service database session. - * @hide + * Interfaces for notifying changes on exporting service database session. */ public static final String ISERVICE_LIST_EXPORT_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListExportListener"; /** * Interfaces for importing service database session. - * @hide */ public static final String ISERVICE_LIST_IMPORT_SESSION = SERVICE_DATABASE_PACKAGE + "IServiceListImportSession"; /** - * Interfaces for changes on importing service database session. - * @hide + * Interfaces for notifying changes on importing service database session. */ public static final String ISERVICE_LIST_IMPORT_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListImportListener"; /** * Interfaces for setting channel list resources. - * @hide */ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = SERVICE_DATABASE_PACKAGE + "IServiceListSetChannelListSession"; /** - * Interfaces for changes on setting channel list resources. - * @hide + * Interfaces for notifying changes on setting channel list resources. */ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListSetChannelListListener"; /** * Interfaces for transferring channel list resources. - * @hide */ public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE + "IChannelListTransfer"; @@ -584,14 +521,12 @@ public final class TvInputServiceExtensionManager { */ public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents"; /** - * Interfaces for changes on deleting record contents. - * @hide + * Interfaces for notifying changes on deleting record contents. */ public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE + "IDeleteRecordedContentsCallback"; /** - * Interfaces for changes on getting record contents. - * @hide + * Interfaces for notifying changes on getting record contents. */ public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE + "IGetInfoRecordedContentsCallback"; @@ -600,61 +535,51 @@ public final class TvInputServiceExtensionManager { */ public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor"; /** - * Interfaces for changes on present event information. - * @hide + * Interfaces for notifying changes on present event information. */ public static final String IEVENT_MONITOR_LISTENER = EVENT_PACKAGE + "IEventMonitorListener"; /** * Interfaces for handling download event information. - * @hide */ public static final String IEVENT_DOWNLOAD = EVENT_PACKAGE + "IEventDownload"; /** - * Interfaces for changes on downloading event information. - * @hide + * Interfaces for notifying changes on downloading event information. */ public static final String IEVENT_DOWNLOAD_LISTENER = EVENT_PACKAGE + "IEventDownloadListener"; /** - * Interfaces for handling download event information for DVB and DTMB. - * @hide + * Interfaces for handling download event information for Digital Video Broadcast + * and Digital Terrestrial Multimedia Broadcast. */ public static final String IEVENT_DOWNLOAD_SESSION = EVENT_PACKAGE + "IEventDownloadSession"; /** * Interfaces for handling analog color system. - * @hide */ public static final String IANALOG_ATTRIBUTE_INTERFACE = ANALOG_PACKAGE + "IAnalogAttributeInterface"; /** * Interfaces for monitoring channel tuned information. - * @hide */ public static final String ICHANNEL_TUNED_INTERFACE = TUNE_PACKAGE + "IChannelTunedInterface"; /** - * Interfaces for changes on channel tuned information. - * @hide + * Interfaces for notifying changes on channel tuned information. */ public static final String ICHANNEL_TUNED_LISTENER = TUNE_PACKAGE + "IChannelTunedListener"; /** * Interfaces for handling tuner frontend signal info. - * @hide */ public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = SIGNAL_PACKAGE + "ITunerFrontendSignalInfoInterface"; /** - * Interfaces for changes on tuner frontend signal info. - * @hide + * Interfaces for notifying changes on tuner frontend signal info. */ public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "ITunerFrontendSignalInfoListener"; /** * Interfaces for handling mux tune operations. - * @hide */ public static final String IMUX_TUNE_SESSION = TUNE_PACKAGE + "IMuxTuneSession"; /** * Interfaces for initing mux tune session. - * @hide */ public static final String IMUX_TUNE = TUNE_PACKAGE + "IMuxTune"; diff --git a/media/java/android/media/tv/extension/pvr/IDeleteRecordedContentsCallback.aidl b/media/java/android/media/tv/extension/pvr/IDeleteRecordedContentsCallback.aidl new file mode 100644 index 000000000000..62f151145472 --- /dev/null +++ b/media/java/android/media/tv/extension/pvr/IDeleteRecordedContentsCallback.aidl @@ -0,0 +1,24 @@ +/* + * 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 android.media.tv.extension.pvr; + +/** + * @hide + */ +oneway interface IDeleteRecordedContentsCallback { + void onRecordedContentsDeleted(in String[] contentUri, in int[] result); +} diff --git a/media/java/android/media/tv/extension/pvr/IGetInfoRecordedContentsCallback.aidl b/media/java/android/media/tv/extension/pvr/IGetInfoRecordedContentsCallback.aidl new file mode 100644 index 000000000000..64f8fc2188b7 --- /dev/null +++ b/media/java/android/media/tv/extension/pvr/IGetInfoRecordedContentsCallback.aidl @@ -0,0 +1,24 @@ +/* + * 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 android.media.tv.extension.pvr; + +/** + * @hide + */ +interface IGetInfoRecordedContentsCallback { + void onRecordedContentsGetInfo(int result); +} diff --git a/media/java/android/media/tv/extension/pvr/IRecordedContents.aidl b/media/java/android/media/tv/extension/pvr/IRecordedContents.aidl new file mode 100644 index 000000000000..74a15b8e0b94 --- /dev/null +++ b/media/java/android/media/tv/extension/pvr/IRecordedContents.aidl @@ -0,0 +1,36 @@ +/* + * 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 android.media.tv.extension.pvr; + +import android.media.tv.extension.pvr.IDeleteRecordedContentsCallback; +import android.media.tv.extension.pvr.IGetInfoRecordedContentsCallback; + + +/** + * @hide + */ +interface IRecordedContents { + // Delete recorded contents by URIs + // using callback to notify the result or any errors during the deletion process. + void deleteRecordedContents(in String[] contentUri, + in IDeleteRecordedContentsCallback callback); + // Get the channel lock status for recorded content identified by the URI provided in sync way. + int getRecordedContentsLockInfoSync(String contentUri); + // Get the channel lock status for recorded content identified by the URI provided in async way. + void getRecordedContentsLockInfoAsync(String contentUri, + in IGetInfoRecordedContentsCallback callback); +} diff --git a/media/java/android/media/tv/extension/scan/IFavoriteNetwork.aidl b/media/java/android/media/tv/extension/scan/IFavoriteNetwork.aidl new file mode 100644 index 000000000000..ff78aa4be39c --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IFavoriteNetwork.aidl @@ -0,0 +1,36 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.media.tv.extension.scan.IFavoriteNetworkListener; +import android.os.Bundle; + +/** + * Country: Norway + * Broadcast Type: BROADCAST_TYPE_DVB_T + * (Operator: RiksTV) + * + * @hide + */ +interface IFavoriteNetwork { + // Get the favorite network information,If there are no conflicts, the array of Bundle is empty. + Bundle[] getFavoriteNetworks(); + // Select and set one of two or more favorite networks detected by the service scan. + int setFavoriteNetwork(in Bundle favoriteNetworkSettings); + // Set the listener to be invoked when two or more favorite networks are detected. + int setListener(in IFavoriteNetworkListener listener); +} diff --git a/media/java/android/media/tv/extension/scan/IFavoriteNetworkListener.aidl b/media/java/android/media/tv/extension/scan/IFavoriteNetworkListener.aidl new file mode 100644 index 000000000000..699422493dd6 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IFavoriteNetworkListener.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.os.Bundle; + +/** + * @hide + */ +oneway interface IFavoriteNetworkListener { + void onDetectFavoriteNetwork(in Bundle detectFavoriteNetworks); +} diff --git a/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl b/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl new file mode 100644 index 000000000000..cdf6e23f4b47 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl @@ -0,0 +1,25 @@ +/* + * 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 android.media.tv.extension.scan; + +/** + * @hide + */ +interface IHDPlusInfo { + // Specifying a HDPlusInfo and start a network scan. + int setHDPlusInfo(String isBlindScanContinue, String isHDMode); +} diff --git a/media/java/android/media/tv/extension/scan/ILcnConflict.aidl b/media/java/android/media/tv/extension/scan/ILcnConflict.aidl new file mode 100644 index 000000000000..5dff39eb5920 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/ILcnConflict.aidl @@ -0,0 +1,35 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.media.tv.extension.scan.ILcnConflictListener; +import android.os.Bundle; + +/** + * Country: Italy, France + * Broadcast Type: BROADCAST_TYPE_DVB_T + * + * @hide + */ +interface ILcnConflict { + // Get the LCN conflict groups information, If there are no conflicts, the array of Bundle is empty. + Bundle[] getLcnConflictGroups(); + // Resolve LCN conflicts caused by service scans. + int resolveLcnConflict(in Bundle[] lcnConflictSettings); + // Set the listener to be invoked the LCN conflict event. + int setListener(in ILcnConflictListener listener); +} diff --git a/media/java/android/media/tv/extension/scan/ILcnConflictListener.aidl b/media/java/android/media/tv/extension/scan/ILcnConflictListener.aidl new file mode 100644 index 000000000000..6bbbeb8e1e06 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/ILcnConflictListener.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.os.Bundle; + +/** + * @hide + */ +oneway interface ILcnConflictListener { + void onDetectLcnConflict(in Bundle detectLcnConflicts); +} diff --git a/media/java/android/media/tv/extension/scan/ILcnV2ChannelList.aidl b/media/java/android/media/tv/extension/scan/ILcnV2ChannelList.aidl new file mode 100644 index 000000000000..f9a9d345a575 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/ILcnV2ChannelList.aidl @@ -0,0 +1,35 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.media.tv.extension.scan.ILcnV2ChannelListListener; +import android.os.Bundle; + +/** + * Country: (NorDig etc.) + * Broadcast Type: BROADCAST_TYPE_DVB_T, BROADCAST_TYPE_DVB_C + * + * @hide + */ +interface ILcnV2ChannelList { + // Get the LCN V2 channel list information. If there are no conflicts, the array of Bundle is empty. + Bundle[] getLcnV2ChannelLists(); + // Select and set one of two or more LCN V2 channel list detected by the service scan. + int setLcnV2ChannelList(in Bundle lcnV2ChannelListSettings); + // Set the listener to be invoked when two or more LCN V2 channel list are detected. + int setListener(in ILcnV2ChannelListListener listener); +} diff --git a/media/java/android/media/tv/extension/scan/ILcnV2ChannelListListener.aidl b/media/java/android/media/tv/extension/scan/ILcnV2ChannelListListener.aidl new file mode 100644 index 000000000000..cbdb83c656f4 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/ILcnV2ChannelListListener.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.os.Bundle; + +/** + * @hide + */ +oneway interface ILcnV2ChannelListListener { + void onDetectLcnV2ChannelList(in Bundle detectLcnV2ChannelList); +} diff --git a/media/java/android/media/tv/extension/scan/IOperatorDetection.aidl b/media/java/android/media/tv/extension/scan/IOperatorDetection.aidl new file mode 100644 index 000000000000..770f8668983e --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IOperatorDetection.aidl @@ -0,0 +1,35 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.media.tv.extension.scan.IOperatorDetectionListener; +import android.os.Bundle; + +/** + * Country: Any + * Broadcast Type: BROADCAST_TYPE_DVB_S + * (Operator: M7) + * + * @hide + */ +interface IOperatorDetection { + // Set the operator selected info for scanning. + int setOperatorDetection(in Bundle operatorSelected); + // Set the listener to be invoked when one or more operator detection has been detected by + // operator detection searches. + int setListener(in IOperatorDetectionListener listener); +} diff --git a/media/java/android/media/tv/extension/scan/IOperatorDetectionListener.aidl b/media/java/android/media/tv/extension/scan/IOperatorDetectionListener.aidl new file mode 100644 index 000000000000..7dcd46177c43 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IOperatorDetectionListener.aidl @@ -0,0 +1,27 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.os.Bundle; + + +/** + * @hide + */ +oneway interface IOperatorDetectionListener { + void onDetectOperatorDetectionList(in Bundle[] detectOperatorDetectionList); +} diff --git a/media/java/android/media/tv/extension/scan/IRegionChannelList.aidl b/media/java/android/media/tv/extension/scan/IRegionChannelList.aidl new file mode 100644 index 000000000000..fe755f873110 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IRegionChannelList.aidl @@ -0,0 +1,30 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.media.tv.extension.scan.IRegionChannelListListener; + +/** + * @hide + */ +interface IRegionChannelList { + // Set the region channel list for scanning. + int setRegionChannelList(String regionChannelList); + // Set the listener to be invoked when one or more region channel list has been detected by + // region channel list searches. + int setListener(in IRegionChannelListListener listener); +} diff --git a/media/java/android/media/tv/extension/scan/IRegionChannelListListener.aidl b/media/java/android/media/tv/extension/scan/IRegionChannelListListener.aidl new file mode 100644 index 000000000000..06b0eb5537a2 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IRegionChannelListListener.aidl @@ -0,0 +1,24 @@ +/* + * 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 android.media.tv.extension.scan; + +/** + * @hide + */ +oneway interface IRegionChannelListListener { + void onDetectRegionChannelList(in String[] detectRegionChannelList); +} diff --git a/media/java/android/media/tv/extension/scan/IScanInterface.aidl b/media/java/android/media/tv/extension/scan/IScanInterface.aidl new file mode 100644 index 000000000000..b44d1d243150 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IScanInterface.aidl @@ -0,0 +1,30 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.media.tv.extension.scan.IScanListener; +import android.os.Bundle; + +/** + * @hide + */ +interface IScanInterface { + IBinder createSession(int broadcastType, String countryCode, String operator, + in IScanListener listener); + Bundle getParameters(int broadcastType, String countryCode, String operator, + in Bundle params); +} diff --git a/media/java/android/media/tv/extension/scan/IScanListener.aidl b/media/java/android/media/tv/extension/scan/IScanListener.aidl new file mode 100644 index 000000000000..2c4807f97c58 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IScanListener.aidl @@ -0,0 +1,33 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.os.Bundle; + +/** + * @hide + */ +oneway interface IScanListener { + // notify events during scan. + void onEvent(in Bundle eventArgs); + // notify the scan progress. + void onScanProgress(String scanProgress, in Bundle scanProgressInfo); + // notify the scan completion. + void onScanCompleted(int scanResult); + // notify that the temporaily held channel list is stored. + void onStoreCompleted(int storeResult); +} diff --git a/media/java/android/media/tv/extension/scan/IScanSatSearch.aidl b/media/java/android/media/tv/extension/scan/IScanSatSearch.aidl new file mode 100644 index 000000000000..b8074fc4a9bd --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IScanSatSearch.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.tv.extension.scan; + +/** + * For satellite search function. + * @hide + */ +interface IScanSatSearch { + // Set currecnt LNB as customized LNB, default LNB is universal LNB + int setCustomizedLnb(String customizedLnb); +} diff --git a/media/java/android/media/tv/extension/scan/IScanSession.aidl b/media/java/android/media/tv/extension/scan/IScanSession.aidl new file mode 100644 index 000000000000..d42eca1342b5 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/IScanSession.aidl @@ -0,0 +1,75 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.os.Bundle; + +/** + * @hide + */ +interface IScanSession { + // Start a service scan. + int startScan(int broadcastType, String countryCode, String operator, in int[] frequency, + String scanType, String languageCode); + // Reset the scan information held in TIS. + int resetScan(); + // Cancel scan. + int cancelScan(); + + // Get available interface for created ScanExtension interface. + String[] getAvailableExtensionInterfaceNames(); + // Get extension interface for Scan. + IBinder getExtensionInterface(String name); + + // Clear the results of the service scan from the service database. + int clearServiceList(in Bundle optionalClearParams); + // Store the results of the service scan from the service database. + int storeServiceList(); + // Get a service information specified by the service information ID. + Bundle getServiceInfo(String serviceInfoId, in String[] keys); + // Get a service information ID list. + String[] getServiceInfoIdList(); + // Get a list of service info by the filter. + Bundle getServiceInfoList(in Bundle filterInfo, in String[] keys); + // Update the service information. + int updateServiceInfo(in Bundle serviceInfo); + // Updates the service information for the specified service information ID in array list. + int updateServiceInfoByList(in Bundle[] serviceInfo); + + /* DVBI specific functions */ + // Get all of the serviceLists, parsed from Local TV storage, Broadcast, USB file discovery. + Bundle getServiceLists(); + // Users choose one serviceList from the serviceLists, and install the services. + int setServiceList(int serviceListRecId); + // Get all of the packageData, parsed from the selected serviceList XML. + Bundle getPackageData(); + // Choose the package using package id and install the corresponding services. + int setPackage(String packageId); + // Get all of the countryRegionData, parsed from the selected serviceList XML. + Bundle getCountryRegionData(); + // Choose the countryRegion using countryRegion id, and install the corresponding services. + int setCountryRegion(String regionId); + // Get all of the regionData, parsed from the selected serviceList XML. + Bundle getRegionData(); + // Choose the region using the regionData id, and install the corresponding services. + int setRegion(String regionId); + + // Get unique session token for the scan. + String getSessionToken(); + // Release scan resource, the register listener will be released. + int release(); +} diff --git a/media/java/android/media/tv/extension/scan/ITargetRegion.aidl b/media/java/android/media/tv/extension/scan/ITargetRegion.aidl new file mode 100644 index 000000000000..417e12243b82 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/ITargetRegion.aidl @@ -0,0 +1,36 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.media.tv.extension.scan.ITargetRegionListener; + +import android.os.Bundle; + +/** + * Country: U.K. + * Broadcast Type: BROADCAST_TYPE_DVB_T + * + * @hide + */ +interface ITargetRegion { + // Get the target regions information. If there are no conflicts, the array of Bundle is empty. + Bundle[] getTargetRegions(); + // Select and set one of two or more target region detected by the service scan. + int setTargetRegion(in Bundle targetRegionSettings); + // Set the listener to be invoked when two or more regions are detected. + int setListener(in ITargetRegionListener listener); +} diff --git a/media/java/android/media/tv/extension/scan/ITargetRegionListener.aidl b/media/java/android/media/tv/extension/scan/ITargetRegionListener.aidl new file mode 100644 index 000000000000..9d6aa8e8ea31 --- /dev/null +++ b/media/java/android/media/tv/extension/scan/ITargetRegionListener.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.os.Bundle; + +/** + * @hide + */ +oneway interface ITargetRegionListener { + void onDetectTargetRegion(in Bundle detectTargetRegions); +} diff --git a/media/java/android/media/tv/extension/scan/ITkgsInfo.aidl b/media/java/android/media/tv/extension/scan/ITkgsInfo.aidl new file mode 100644 index 000000000000..f25952c1cbdc --- /dev/null +++ b/media/java/android/media/tv/extension/scan/ITkgsInfo.aidl @@ -0,0 +1,28 @@ +/* + * 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 android.media.tv.extension.scan; + +import android.media.tv.extension.scan.ITkgsInfoListener; +import android.os.Bundle; + +/** + * @hide + */ +interface ITkgsInfo { + int setPrefServiceList(String prefServiceList); + int setTkgsInfoListener(in ITkgsInfoListener listener); +} diff --git a/media/java/android/media/tv/extension/scan/ITkgsInfoListener.aidl b/media/java/android/media/tv/extension/scan/ITkgsInfoListener.aidl new file mode 100644 index 000000000000..e3dcf2d4c5ad --- /dev/null +++ b/media/java/android/media/tv/extension/scan/ITkgsInfoListener.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.tv.extension.scan; + +/** + * @hide + */ +oneway interface ITkgsInfoListener { + void onServiceList(in String[] serviceList); + void onTableVersionUpdate(int tableVersion); + void onUserMessage(String strMessage); +} diff --git a/media/java/android/media/tv/extension/signal/IAnalogAudioInfo.aidl b/media/java/android/media/tv/extension/signal/IAnalogAudioInfo.aidl new file mode 100644 index 000000000000..742191ff3c05 --- /dev/null +++ b/media/java/android/media/tv/extension/signal/IAnalogAudioInfo.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.tv.extension.signal; + +import android.os.Bundle; + +/** + * @hide + */ +interface IAnalogAudioInfo { + Bundle getAnalogAudioInfo(String sessionToken); +} diff --git a/media/java/android/media/tv/extension/signal/IAudioSignalInfo.aidl b/media/java/android/media/tv/extension/signal/IAudioSignalInfo.aidl new file mode 100644 index 000000000000..4c953a08364a --- /dev/null +++ b/media/java/android/media/tv/extension/signal/IAudioSignalInfo.aidl @@ -0,0 +1,36 @@ +/* + * 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 android.media.tv.extension.signal; + +import android.media.tv.extension.signal.IAudioSignalInfoListener; +import android.os.Bundle; + +/** + * @hide + */ +interface IAudioSignalInfo { + // Get audio signal information. + Bundle getAudioSignalInfo(String sessionToken); + // Notify TIS whether user selects audio track via mts button on the remote control. + void notifyMtsSelectTrackFlag(boolean mtsFlag); + // Get the audio track id selected via mts. + String getMtsSelectedTrackId(); + // Register a listener to receive the updated audio signal information. + void addAudioSignalInfoListener(String clientToken, in IAudioSignalInfoListener listener); + // Remove a listener for audio signal information update notifications. + void removeAudioSignalInfoListener(in IAudioSignalInfoListener listener); +} diff --git a/media/java/android/media/tv/extension/signal/IAudioSignalInfoListener.aidl b/media/java/android/media/tv/extension/signal/IAudioSignalInfoListener.aidl new file mode 100644 index 000000000000..adf239ab1b32 --- /dev/null +++ b/media/java/android/media/tv/extension/signal/IAudioSignalInfoListener.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.tv.extension.signal; + +import android.os.Bundle; + +/** + * @hide + */ +oneway interface IAudioSignalInfoListener { + void onAudioSignalInfoChanged(String sessionToken, in Bundle changedSignalInfo); +} diff --git a/media/java/android/media/tv/extension/signal/IHdmiSignalInfoListener.aidl b/media/java/android/media/tv/extension/signal/IHdmiSignalInfoListener.aidl new file mode 100644 index 000000000000..bd468b2e7f34 --- /dev/null +++ b/media/java/android/media/tv/extension/signal/IHdmiSignalInfoListener.aidl @@ -0,0 +1,25 @@ +/* + * 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 android.media.tv.extension.signal; + +/** + * @hide + */ +oneway interface IHdmiSignalInfoListener { + void onSignalInfoChanged(String sessionToken); + void onLowLatencyModeChanged(int enable); +} diff --git a/media/java/android/media/tv/extension/signal/IHdmiSignalInterface.aidl b/media/java/android/media/tv/extension/signal/IHdmiSignalInterface.aidl new file mode 100644 index 000000000000..39625e3c958d --- /dev/null +++ b/media/java/android/media/tv/extension/signal/IHdmiSignalInterface.aidl @@ -0,0 +1,36 @@ +/* + * 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 android.media.tv.extension.signal; + +import android.media.tv.extension.signal.IHdmiSignalInfoListener; +import android.os.Bundle; + +/** + * @hide + */ +interface IHdmiSignalInterface { + // Register a listener for Hdmi Signal Info updates. + void addHdmiSignalInfoListener(String inputId, in IHdmiSignalInfoListener listener); + // Remove a listener for Hdmi Signal Info update notifications. + void removeHdmiSignalInfoListener(String inputId, in IHdmiSignalInfoListener listener); + // Obtain HdmiSignalInfo based on the inputId and sessionToken. + Bundle getHdmiSignalInfo(String sessionToken); + // Enable/disable low-latency decoding mode. + void setLowLatency(String sessionToken, int mode); + // Enable/disable force-VRR mode. + void setForceVrr(String sessionToken, int mode); +} diff --git a/media/java/android/media/tv/extension/signal/ITunerFrontendSignalInfoInterface.aidl b/media/java/android/media/tv/extension/signal/ITunerFrontendSignalInfoInterface.aidl new file mode 100644 index 000000000000..7f05e7033aeb --- /dev/null +++ b/media/java/android/media/tv/extension/signal/ITunerFrontendSignalInfoInterface.aidl @@ -0,0 +1,28 @@ +/* + * 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 android.media.tv.extension.signal; + +import android.media.tv.extension.signal.ITunerFrontendSignalInfoListener; +import android.os.Bundle; + +/** +* @hide +*/ +interface ITunerFrontendSignalInfoInterface { + Bundle getFrontendSignalInfo(String sessionToken); + void setFrontendSignalInfoListener(in ITunerFrontendSignalInfoListener listener); +} diff --git a/media/java/android/media/tv/extension/signal/ITunerFrontendSignalInfoListener.aidl b/media/java/android/media/tv/extension/signal/ITunerFrontendSignalInfoListener.aidl new file mode 100644 index 000000000000..9c22a3570cce --- /dev/null +++ b/media/java/android/media/tv/extension/signal/ITunerFrontendSignalInfoListener.aidl @@ -0,0 +1,24 @@ +/* + * 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 android.media.tv.extension.signal; + +/** +* @hide +*/ +oneway interface ITunerFrontendSignalInfoListener { + void onFrontendStatusChanged(int frontendStatus); +} diff --git a/media/java/android/media/tv/extension/signal/IVideoSignalInfo.aidl b/media/java/android/media/tv/extension/signal/IVideoSignalInfo.aidl new file mode 100644 index 000000000000..b17142acf2ab --- /dev/null +++ b/media/java/android/media/tv/extension/signal/IVideoSignalInfo.aidl @@ -0,0 +1,30 @@ +/* + * 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 android.media.tv.extension.signal; + +import android.media.tv.extension.signal.IVideoSignalInfoListener; +import android.os.Bundle; + + +/** + * @hide + */ +interface IVideoSignalInfo { + void addVideoSignalInfoListener(String clientToken, in IVideoSignalInfoListener listener); + void removeVideoSignalInfoListener(in IVideoSignalInfoListener listener); + Bundle getVideoSignalInfo(String sessionToken); +} diff --git a/media/java/android/media/tv/extension/signal/IVideoSignalInfoListener.aidl b/media/java/android/media/tv/extension/signal/IVideoSignalInfoListener.aidl new file mode 100644 index 000000000000..aafc192f32fa --- /dev/null +++ b/media/java/android/media/tv/extension/signal/IVideoSignalInfoListener.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.media.tv.extension.signal; + +import android.os.Bundle; + +/** + * @hide + */ +oneway interface IVideoSignalInfoListener { + void onVideoSignalInfoChanged(String sessionToken, in Bundle changedSignalInfo); +} diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index d05ee551c172..a94230014437 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -1362,13 +1362,26 @@ static jboolean android_media_MediaPlayer_setOutputDevice(JNIEnv *env, jobject t return mp->setOutputDevice(device_id) == NO_ERROR; } -static jint android_media_MediaPlayer_getRoutedDeviceId(JNIEnv *env, jobject thiz) +static jintArray android_media_MediaPlayer_getRoutedDeviceIds(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL) { - return AUDIO_PORT_HANDLE_NONE; + return NULL; + } + DeviceIdVector deviceIds; + // TODO: b/379161379 - Should we throw an exception if the result is not ok? + mp->getRoutedDeviceIds(deviceIds); + jintArray result; + result = env->NewIntArray(deviceIds.size()); + if (result == NULL) { + return NULL; } - return mp->getRoutedDeviceId(); + jint* values = env->GetIntArrayElements(result, 0); + for (unsigned int i = 0; i < deviceIds.size(); i++) { + values[i++] = static_cast<jint>(deviceIds[i]); + } + env->ReleaseIntArrayElements(result, values, 0); + return result; } static void android_media_MediaPlayer_enableDeviceCallback( @@ -1452,7 +1465,8 @@ static const JNINativeMethod gMethods[] = { // AudioRouting {"native_setOutputDevice", "(I)Z", (void *)android_media_MediaPlayer_setOutputDevice}, - {"native_getRoutedDeviceId", "()I", (void *)android_media_MediaPlayer_getRoutedDeviceId}, + {"native_getRoutedDeviceIds", "()[I", + (void *)android_media_MediaPlayer_getRoutedDeviceIds}, {"native_enableDeviceCallback", "(Z)V", (void *)android_media_MediaPlayer_enableDeviceCallback}, }; diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 9a6d5d7d730a..643fc8a2d925 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -722,21 +722,31 @@ android_media_MediaRecorder_setInputDevice(JNIEnv *env, jobject thiz, jint devic return true; } -static jint -android_media_MediaRecorder_getRoutedDeviceId(JNIEnv *env, jobject thiz) +static jintArray +android_media_MediaRecorder_getRoutedDeviceIds(JNIEnv *env, jobject thiz) { - ALOGV("android_media_MediaRecorder_getRoutedDeviceId"); + ALOGV("android_media_MediaRecorder_getRoutedDeviceIds"); sp<MediaRecorder> mr = getMediaRecorder(env, thiz); if (mr == NULL) { jniThrowException(env, "java/lang/IllegalStateException", NULL); - return AUDIO_PORT_HANDLE_NONE; + return NULL; } - audio_port_handle_t deviceId; - process_media_recorder_call(env, mr->getRoutedDeviceId(&deviceId), - "java/lang/RuntimeException", "getRoutedDeviceId failed."); - return (jint) deviceId; + DeviceIdVector deviceIds; + process_media_recorder_call(env, mr->getRoutedDeviceIds(deviceIds), + "java/lang/RuntimeException", "getRoutedDeviceIds failed."); + jintArray result; + result = env->NewIntArray(deviceIds.size()); + if (result == NULL) { + return NULL; + } + jint* values = env->GetIntArrayElements(result, 0); + for (unsigned int i = 0; i < deviceIds.size(); i++) { + values[i++] = static_cast<jint>(deviceIds[i]); + } + env->ReleaseIntArrayElements(result, values, 0); + return result; } static void @@ -880,7 +890,8 @@ static const JNINativeMethod gMethods[] = { {"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaRecorder_native_getMetrics}, {"native_setInputDevice", "(I)Z", (void *)android_media_MediaRecorder_setInputDevice}, - {"native_getRoutedDeviceId", "()I", (void *)android_media_MediaRecorder_getRoutedDeviceId}, + {"native_getRoutedDeviceIds", "()[I", + (void *)android_media_MediaRecorder_getRoutedDeviceIds}, {"native_enableDeviceCallback", "(Z)V", (void *)android_media_MediaRecorder_enableDeviceCallback}, {"native_getActiveMicrophones", "(Ljava/util/ArrayList;)I", (void *)android_media_MediaRecord_getActiveMicrophones}, diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 008120429c40..9a7a39f213ee 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -81,11 +81,14 @@ package android.nfc { method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported(); method public boolean isSecureNfcEnabled(); method public boolean isSecureNfcSupported(); + method @FlaggedApi("android.nfc.nfc_check_tag_intent_preference") public boolean isTagIntentAllowed(); + method @FlaggedApi("android.nfc.nfc_check_tag_intent_preference") public boolean isTagIntentAppPreferenceSupported(); method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled(); method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity); method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int); method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setObserveModeEnabled(boolean); field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED"; + field @FlaggedApi("android.nfc.nfc_check_tag_intent_preference") public static final String ACTION_CHANGE_TAG_INTENT_PREFERENCE = "android.nfc.action.CHANGE_TAG_INTENT_PREFERENCE"; field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED"; field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED"; field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED"; diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index f587660cae5b..15814edcd86a 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -11,7 +11,6 @@ package android.nfc { method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn(); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported(); - method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported(); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener); method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerNfcVendorNciCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.NfcVendorNciCallback); method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener); @@ -97,6 +96,7 @@ package android.nfc { method public void onDisableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public void onDisableStarted(); method public void onEeListenActivated(boolean); + method public void onEeUpdated(); method public void onEnableFinished(int); method public void onEnableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public void onEnableStarted(); diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index 31514a09adad..a08b55fe86b8 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -121,4 +121,5 @@ interface INfcAdapter List<Entry> getRoutingTableEntryList(); void indicateDataMigration(boolean inProgress, String pkg); int commitRouting(); + boolean isTagIntentAllowed(in String pkg, in int Userid); } diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl index 1a21c0bae413..e5eac0b4d6fd 100644 --- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl +++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl @@ -48,6 +48,7 @@ interface INfcOemExtensionCallback { void onRfFieldActivated(boolean isActivated); void onRfDiscoveryStarted(boolean isDiscoveryStarted); void onEeListenActivated(boolean isActivated); + void onEeUpdated(); void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer); void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent); void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category); diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index c5d8191b22e6..056844f38f3c 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -49,6 +49,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.io.IOException; @@ -2505,22 +2506,22 @@ public final class NfcAdapter { } /** - * Checks if the device supports Tag application preference. + * Checks if the device supports Tag Intent App Preference functionality. + * + * When supported, {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED} or + * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if + * {@link isTagIntentAllowed} returns {@code false}. * * @return {@code true} if the device supports Tag application preference, {@code false} * otherwise * @throws UnsupportedOperationException if FEATURE_NFC is unavailable - * - * @hide */ - @SystemApi - @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + @FlaggedApi(Flags.FLAG_NFC_CHECK_TAG_INTENT_PREFERENCE) public boolean isTagIntentAppPreferenceSupported() { if (!sHasNfcFeature) { throw new UnsupportedOperationException(); } return callServiceReturn(() -> sService.isTagIntentAppPreferenceSupported(), false); - } /** @@ -2895,4 +2896,42 @@ public final class NfcAdapter { } return mNfcOemExtension; } + + /** + * Activity action: Bring up the settings page that allows the user to enable or disable tag + * intent reception for apps. + * + * <p>This will direct user to the settings page shows a list that asks users whether + * they want to allow or disallow the package to start an activity when a tag is discovered. + * + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @FlaggedApi(Flags.FLAG_NFC_CHECK_TAG_INTENT_PREFERENCE) + public static final String ACTION_CHANGE_TAG_INTENT_PREFERENCE = + "android.nfc.action.CHANGE_TAG_INTENT_PREFERENCE"; + + /** + * Checks whether the user has disabled the calling app from receiving NFC tag intents. + * + * <p>This method checks whether the caller package name is either not present in the user + * disabled list or is explicitly allowed by the user. + * + * @return {@code true} if an app is either not present in the list or is added to the list + * with the flag set to {@code true}. Otherwise, it returns {@code false}. + * It also returns {@code true} if {@link isTagIntentAppPreferenceSupported} returns + * {@code false}. + * + * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. + */ + @FlaggedApi(Flags.FLAG_NFC_CHECK_TAG_INTENT_PREFERENCE) + public boolean isTagIntentAllowed() { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + if (!isTagIntentAppPreferenceSupported()) { + return true; + } + return callServiceReturn(() -> sService.isTagIntentAllowed(mContext.getPackageName(), + UserHandle.myUserId()), false); + } } diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index 326ca6449c53..9ed678fe6014 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -367,6 +367,15 @@ public final class NfcOemExtension { void onEeListenActivated(boolean isActivated); /** + * Notifies that some NFCEE (NFC Execution Environment) has been updated. + * + * <p> This indicates that some applet has been installed/updated/removed in + * one of the NFCEE's. + * </p> + */ + void onEeUpdated(); + + /** * Gets the intent to find the OEM package in the OEM App market. If the consumer returns * {@code null} or a timeout occurs, the intent from the first available package will be * used instead. @@ -830,6 +839,12 @@ public final class NfcOemExtension { } @Override + public void onEeUpdated() throws RemoteException { + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(null, (Object input) -> cb.onEeUpdated(), ex)); + } + + @Override public void onStateUpdated(int state) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(state, cb::onStateUpdated, ex)); diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index 8a37aa28cf9d..ee287aba709f 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -181,3 +181,11 @@ flag { description: "Enable set service enabled for category other" bug: "338157113" } + +flag { + name: "nfc_check_tag_intent_preference" + is_exported: true + namespace: "nfc" + description: "App can check its tag intent preference status" + bug: "335916336" +} diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java index fbf51fd7cca7..8c15b093e48e 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java @@ -218,7 +218,7 @@ public class PackageWatchdog { @GuardedBy("sPackageWatchdogLock") private static PackageWatchdog sPackageWatchdog; - private final Object mLock = new Object(); + private static final Object sLock = new Object(); // System server context private final Context mContext; // Handler to run short running tasks @@ -228,7 +228,7 @@ public class PackageWatchdog { // Contains (observer-name -> observer-handle) that have ever been registered from // previous boots. Observers with all packages expired are periodically pruned. // It is saved to disk on system shutdown and repouplated on startup so it survives reboots. - @GuardedBy("mLock") + @GuardedBy("sLock") private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>(); // File containing the XML data of monitored packages /data/system/package-watchdog.xml private final AtomicFile mPolicyFile; @@ -244,26 +244,26 @@ public class PackageWatchdog { private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>(); // The set of packages that have been synced with the ExplicitHealthCheckController - @GuardedBy("mLock") + @GuardedBy("sLock") private Set<String> mRequestedHealthCheckPackages = new ArraySet<>(); - @GuardedBy("mLock") + @GuardedBy("sLock") private boolean mIsPackagesReady; // Flag to control whether explicit health checks are supported or not - @GuardedBy("mLock") + @GuardedBy("sLock") private boolean mIsHealthCheckEnabled = DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED; - @GuardedBy("mLock") + @GuardedBy("sLock") private int mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS; - @GuardedBy("mLock") + @GuardedBy("sLock") private int mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT; // SystemClock#uptimeMillis when we last executed #syncState // 0 if no prune is scheduled. - @GuardedBy("mLock") + @GuardedBy("sLock") private long mUptimeAtLastStateSync; // If true, sync explicit health check packages with the ExplicitHealthCheckController. - @GuardedBy("mLock") + @GuardedBy("sLock") private boolean mSyncRequired = false; - @GuardedBy("mLock") + @GuardedBy("sLock") private long mLastMitigation = -1000000; @FunctionalInterface @@ -303,7 +303,11 @@ public class PackageWatchdog { sPackageWatchdog = this; } - /** Creates or gets singleton instance of PackageWatchdog. */ + /** + * Creates or gets singleton instance of PackageWatchdog. + * + * @param context The system server context. + */ public static @NonNull PackageWatchdog getInstance(@NonNull Context context) { synchronized (sPackageWatchdogLock) { if (sPackageWatchdog == null) { @@ -319,7 +323,7 @@ public class PackageWatchdog { * @hide */ public void onPackagesReady() { - synchronized (mLock) { + synchronized (sLock) { mIsPackagesReady = true; mHealthCheckController.setCallbacks(packageName -> onHealthCheckPassed(packageName), packages -> onSupportedPackages(packages), @@ -338,7 +342,7 @@ public class PackageWatchdog { * @hide */ public void registerHealthObserver(PackageHealthObserver observer) { - synchronized (mLock) { + synchronized (sLock) { ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier()); if (internalObserver != null) { internalObserver.registeredObserver = observer; @@ -405,7 +409,7 @@ public class PackageWatchdog { mLongTaskHandler.post(() -> { syncState("observing new packages"); - synchronized (mLock) { + synchronized (sLock) { ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier()); if (oldObserver == null) { Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health " @@ -437,7 +441,7 @@ public class PackageWatchdog { */ public void unregisterHealthObserver(PackageHealthObserver observer) { mLongTaskHandler.post(() -> { - synchronized (mLock) { + synchronized (sLock) { mAllObservers.remove(observer.getUniqueIdentifier()); } syncState("unregistering observer: " + observer.getUniqueIdentifier()); @@ -458,7 +462,7 @@ public class PackageWatchdog { Slog.w(TAG, "Could not resolve a list of failing packages"); return; } - synchronized (mLock) { + synchronized (sLock) { final long now = mSystemClock.uptimeMillis(); if (Flags.recoverabilityDetection()) { if (now >= mLastMitigation @@ -469,7 +473,7 @@ public class PackageWatchdog { } } mLongTaskHandler.post(() -> { - synchronized (mLock) { + synchronized (sLock) { if (mAllObservers.isEmpty()) { return; } @@ -569,7 +573,7 @@ public class PackageWatchdog { int currentObserverImpact, int mitigationCount) { if (allowMitigations(currentObserverImpact, versionedPackage)) { - synchronized (mLock) { + synchronized (sLock) { mLastMitigation = mSystemClock.uptimeMillis(); } currentObserverToNotify.onExecuteHealthCheckMitigation(versionedPackage, failureReason, @@ -599,7 +603,7 @@ public class PackageWatchdog { */ @SuppressWarnings("GuardedBy") public void noteBoot() { - synchronized (mLock) { + synchronized (sLock) { // if boot count has reached threshold, start mitigation. // We wait until threshold number of restarts only for the first time. Perform // mitigations for every restart after that. @@ -652,7 +656,7 @@ public class PackageWatchdog { // This currently adds about 7ms extra to shutdown thread /** @hide Writes the package information to file during shutdown. */ public void writeNow() { - synchronized (mLock) { + synchronized (sLock) { // Must only run synchronous tasks as this runs on the ShutdownThread and no other // thread is guaranteed to run during shutdown. if (!mAllObservers.isEmpty()) { @@ -671,7 +675,7 @@ public class PackageWatchdog { * passed and the health check service is stopped. */ private void setExplicitHealthCheckEnabled(boolean enabled) { - synchronized (mLock) { + synchronized (sLock) { mIsHealthCheckEnabled = enabled; mHealthCheckController.setEnabled(enabled); mSyncRequired = true; @@ -841,7 +845,10 @@ public class PackageWatchdog { /** * Returns {@code true} if this observer wishes to observe the given package, {@code false} - * otherwise + * otherwise. + * Any failing package can be passed on to the observer. Currently the packages that have + * ANRs and perform {@link android.service.watchdog.ExplicitHealthCheckService} are being + * passed to observers in these API. * * <p> A persistent observer may choose to start observing certain failing packages, even if * it has not explicitly asked to watch the package with {@link #startObservingHealth}. @@ -853,14 +860,14 @@ public class PackageWatchdog { @VisibleForTesting long getTriggerFailureCount() { - synchronized (mLock) { + synchronized (sLock) { return mTriggerFailureCount; } } @VisibleForTesting long getTriggerFailureDurationMs() { - synchronized (mLock) { + synchronized (sLock) { return mTriggerFailureDurationMs; } } @@ -881,7 +888,7 @@ public class PackageWatchdog { */ private void syncRequests() { boolean syncRequired = false; - synchronized (mLock) { + synchronized (sLock) { if (mIsPackagesReady) { Set<String> packages = getPackagesPendingHealthChecksLocked(); if (mSyncRequired || !packages.equals(mRequestedHealthCheckPackages) @@ -918,7 +925,7 @@ public class PackageWatchdog { Slog.i(TAG, "Health check passed for package: " + packageName); boolean isStateChanged = false; - synchronized (mLock) { + synchronized (sLock) { for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) { ObserverInternal observer = mAllObservers.valueAt(observerIdx); MonitoredPackage monitoredPackage = observer.getMonitoredPackage(packageName); @@ -946,7 +953,7 @@ public class PackageWatchdog { supportedPackageTimeouts.put(info.getPackageName(), info.getHealthCheckTimeoutMillis()); } - synchronized (mLock) { + synchronized (sLock) { Slog.d(TAG, "Received supported packages " + supportedPackages); Iterator<ObserverInternal> oit = mAllObservers.values().iterator(); while (oit.hasNext()) { @@ -977,13 +984,13 @@ public class PackageWatchdog { } private void onSyncRequestNotified() { - synchronized (mLock) { + synchronized (sLock) { mSyncRequired = true; syncRequestsAsync(); } } - @GuardedBy("mLock") + @GuardedBy("sLock") private Set<String> getPackagesPendingHealthChecksLocked() { Set<String> packages = new ArraySet<>(); Iterator<ObserverInternal> oit = mAllObservers.values().iterator(); @@ -1009,7 +1016,7 @@ public class PackageWatchdog { * health check service and schedules the next state sync. */ private void syncState(String reason) { - synchronized (mLock) { + synchronized (sLock) { Slog.i(TAG, "Syncing state, reason: " + reason); pruneObserversLocked(); @@ -1025,7 +1032,7 @@ public class PackageWatchdog { syncState("scheduled"); } - @GuardedBy("mLock") + @GuardedBy("sLock") private void scheduleNextSyncStateLocked() { long durationMs = getNextStateSyncMillisLocked(); mShortTaskHandler.removeCallbacks(mSyncStateWithScheduledReason); @@ -1043,7 +1050,7 @@ public class PackageWatchdog { * * @returns Long#MAX_VALUE if there are no observed packages. */ - @GuardedBy("mLock") + @GuardedBy("sLock") private long getNextStateSyncMillisLocked() { long shortestDurationMs = Long.MAX_VALUE; for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { @@ -1064,7 +1071,7 @@ public class PackageWatchdog { * Removes {@code elapsedMs} milliseconds from all durations on monitored packages * and updates other internal state. */ - @GuardedBy("mLock") + @GuardedBy("sLock") private void pruneObserversLocked() { long elapsedMs = mUptimeAtLastStateSync == 0 ? 0 : mSystemClock.uptimeMillis() - mUptimeAtLastStateSync; @@ -1092,7 +1099,7 @@ public class PackageWatchdog { private void onHealthCheckFailed(ObserverInternal observer, Set<MonitoredPackage> failedPackages) { mLongTaskHandler.post(() -> { - synchronized (mLock) { + synchronized (sLock) { PackageHealthObserver registeredObserver = observer.registeredObserver; if (registeredObserver != null) { Iterator<MonitoredPackage> it = failedPackages.iterator(); @@ -1201,7 +1208,7 @@ public class PackageWatchdog { */ @VisibleForTesting void updateConfigs() { - synchronized (mLock) { + synchronized (sLock) { mTriggerFailureCount = DeviceConfig.getInt( DeviceConfig.NAMESPACE_ROLLBACK, PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, @@ -1230,7 +1237,7 @@ public class PackageWatchdog { */ private boolean saveToFile() { Slog.i(TAG, "Saving observer state to file"); - synchronized (mLock) { + synchronized (sLock) { FileOutputStream stream; try { stream = mPolicyFile.startWrite(); @@ -1297,20 +1304,38 @@ public class PackageWatchdog { /** Dump status of every observer in mAllObservers. */ public void dump(@NonNull PrintWriter pw) { - if (Flags.synchronousRebootInRescueParty() && RescueParty.isRecoveryTriggeredReboot()) { + if (Flags.synchronousRebootInRescueParty() && isRecoveryTriggeredReboot()) { dumpInternal(pw); } else { - synchronized (mLock) { + synchronized (sLock) { dumpInternal(pw); } } } + /** + * Check if we're currently attempting to reboot during mitigation. This method must return + * true if triggered reboot early during a boot loop, since the device will not be fully booted + * at this time. + * @hide + */ + public static boolean isRecoveryTriggeredReboot() { + return isFactoryResetPropertySet() || isRebootPropertySet(); + } + + private static boolean isFactoryResetPropertySet() { + return CrashRecoveryProperties.attemptingFactoryReset().orElse(false); + } + + private static boolean isRebootPropertySet() { + return CrashRecoveryProperties.attemptingReboot().orElse(false); + } + private void dumpInternal(@NonNull PrintWriter pw) { IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); ipw.println("Package Watchdog status"); ipw.increaseIndent(); - synchronized (mLock) { + synchronized (sLock) { for (String observerName : mAllObservers.keySet()) { ipw.println("Observer name: " + observerName); ipw.increaseIndent(); @@ -1324,7 +1349,7 @@ public class PackageWatchdog { } @VisibleForTesting - @GuardedBy("mLock") + @GuardedBy("sLock") void registerObserverInternal(ObserverInternal observerInternal) { mAllObservers.put(observerInternal.name, observerInternal); } @@ -1333,15 +1358,15 @@ public class PackageWatchdog { * Represents an observer monitoring a set of packages along with the failure thresholds for * each package. * - * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing + * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing * instances of this class. */ static class ObserverInternal { public final String name; - @GuardedBy("mLock") + @GuardedBy("sLock") private final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>(); @Nullable - @GuardedBy("mLock") + @GuardedBy("sLock") public PackageHealthObserver registeredObserver; private int mMitigationCount; @@ -1359,7 +1384,7 @@ public class PackageWatchdog { * Writes important {@link MonitoredPackage} details for this observer to file. * Does not persist any package failure thresholds. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public boolean writeLocked(XmlSerializer out) { try { out.startTag(null, TAG_OBSERVER); @@ -1387,7 +1412,7 @@ public class PackageWatchdog { mMitigationCount = mitigationCount; } - @GuardedBy("mLock") + @GuardedBy("sLock") public void updatePackagesLocked(List<MonitoredPackage> packages) { for (int pIndex = 0; pIndex < packages.size(); pIndex++) { MonitoredPackage p = packages.get(pIndex); @@ -1410,7 +1435,7 @@ public class PackageWatchdog { * health check passing, or an empty list if no package expired for which an explicit health * check was still pending */ - @GuardedBy("mLock") + @GuardedBy("sLock") private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) { Set<MonitoredPackage> failedPackages = new ArraySet<>(); Iterator<MonitoredPackage> it = mPackages.values().iterator(); @@ -1435,7 +1460,7 @@ public class PackageWatchdog { * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise * @hide */ - @GuardedBy("mLock") + @GuardedBy("sLock") public boolean onPackageFailureLocked(String packageName) { if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent() && registeredObserver.mayObservePackage(packageName)) { @@ -1454,7 +1479,7 @@ public class PackageWatchdog { * * @return a mapping of package names to {@link MonitoredPackage} objects. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public ArrayMap<String, MonitoredPackage> getMonitoredPackages() { return mPackages; } @@ -1467,7 +1492,7 @@ public class PackageWatchdog { * @return the {@link MonitoredPackage} object associated with the package name if one * exists, {@code null} otherwise. */ - @GuardedBy("mLock") + @GuardedBy("sLock") @Nullable public MonitoredPackage getMonitoredPackage(String packageName) { return mPackages.get(packageName); @@ -1478,7 +1503,7 @@ public class PackageWatchdog { * * @param p: the {@link MonitoredPackage} to store. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public void putMonitoredPackage(MonitoredPackage p) { mPackages.put(p.getName(), p); } @@ -1601,17 +1626,17 @@ public class PackageWatchdog { * Represents a package and its health check state along with the time * it should be monitored for. * - * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing + * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing * instances of this class. */ class MonitoredPackage { private final String mPackageName; // Times when package failures happen sorted in ascending order - @GuardedBy("mLock") + @GuardedBy("sLock") private final LongArrayQueue mFailureHistory = new LongArrayQueue(); // Times when an observer was called to mitigate this package's failure. Sorted in // ascending order. - @GuardedBy("mLock") + @GuardedBy("sLock") private final LongArrayQueue mMitigationCalls; // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after // methods that could change the health check state: handleElapsedTimeLocked and @@ -1620,17 +1645,17 @@ public class PackageWatchdog { // Whether an explicit health check has passed. // This value in addition with mHealthCheckDurationMs determines the health check state // of the package, see #getHealthCheckStateLocked - @GuardedBy("mLock") + @GuardedBy("sLock") private boolean mHasPassedHealthCheck; // System uptime duration to monitor package. - @GuardedBy("mLock") + @GuardedBy("sLock") private long mDurationMs; // System uptime duration to check the result of an explicit health check // Initially, MAX_VALUE until we get a value from the health check service // and request health checks. // This value in addition with mHasPassedHealthCheck determines the health check state // of the package, see #getHealthCheckStateLocked - @GuardedBy("mLock") + @GuardedBy("sLock") private long mHealthCheckDurationMs = Long.MAX_VALUE; MonitoredPackage(String packageName, long durationMs, @@ -1647,7 +1672,7 @@ public class PackageWatchdog { /** Writes the salient fields to disk using {@code out}. * @hide */ - @GuardedBy("mLock") + @GuardedBy("sLock") public void writeLocked(XmlSerializer out) throws IOException { out.startTag(null, TAG_PACKAGE); out.attribute(null, ATTR_NAME, getName()); @@ -1665,7 +1690,7 @@ public class PackageWatchdog { * * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise */ - @GuardedBy("mLock") + @GuardedBy("sLock") public boolean onFailureLocked() { // Sliding window algorithm: find out if there exists a window containing failures >= // mTriggerFailureCount. @@ -1685,7 +1710,7 @@ public class PackageWatchdog { /** * Notes the timestamp of a mitigation call into the observer. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public void noteMitigationCallLocked() { mMitigationCalls.addLast(mSystemClock.uptimeMillis()); } @@ -1696,7 +1721,7 @@ public class PackageWatchdog { * * @return the number of mitigation calls made in the de-escalation window. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public int getMitigationCountLocked() { try { final long now = mSystemClock.uptimeMillis(); @@ -1716,7 +1741,7 @@ public class PackageWatchdog { * * @return a LongArrayQueue of the mitigation calls relative to the current system uptime. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public LongArrayQueue normalizeMitigationCalls() { LongArrayQueue normalized = new LongArrayQueue(); final long now = mSystemClock.uptimeMillis(); @@ -1731,7 +1756,7 @@ public class PackageWatchdog { * * @return the new health check state */ - @GuardedBy("mLock") + @GuardedBy("sLock") public int setHealthCheckActiveLocked(long initialHealthCheckDurationMs) { if (initialHealthCheckDurationMs <= 0) { Slog.wtf(TAG, "Cannot set non-positive health check duration " @@ -1751,7 +1776,7 @@ public class PackageWatchdog { * * @return the new health check state */ - @GuardedBy("mLock") + @GuardedBy("sLock") public int handleElapsedTimeLocked(long elapsedMs) { if (elapsedMs <= 0) { Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + getName()); @@ -1769,7 +1794,7 @@ public class PackageWatchdog { } /** Explicitly update the monitoring duration of the package. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public void updateHealthCheckDuration(long newDurationMs) { mDurationMs = newDurationMs; } @@ -1780,7 +1805,7 @@ public class PackageWatchdog { * * @return the new {@link HealthCheckState health check state} */ - @GuardedBy("mLock") + @GuardedBy("sLock") @HealthCheckState public int tryPassHealthCheckLocked() { if (mHealthCheckState != HealthCheckState.FAILED) { @@ -1799,7 +1824,7 @@ public class PackageWatchdog { /** * Returns the current {@link HealthCheckState health check state}. */ - @GuardedBy("mLock") + @GuardedBy("sLock") @HealthCheckState public int getHealthCheckStateLocked() { return mHealthCheckState; @@ -1810,7 +1835,7 @@ public class PackageWatchdog { * * @return the duration or {@link Long#MAX_VALUE} if the package should not be scheduled */ - @GuardedBy("mLock") + @GuardedBy("sLock") public long getShortestScheduleDurationMsLocked() { // Consider health check duration only if #isPendingHealthChecksLocked is true return Math.min(toPositive(mDurationMs), @@ -1822,7 +1847,7 @@ public class PackageWatchdog { * Returns {@code true} if the total duration left to monitor the package is less than or * equal to 0 {@code false} otherwise. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public boolean isExpiredLocked() { return mDurationMs <= 0; } @@ -1831,7 +1856,7 @@ public class PackageWatchdog { * Returns {@code true} if the package, {@link #getName} is expecting health check results * {@code false} otherwise. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public boolean isPendingHealthChecksLocked() { return mHealthCheckState == HealthCheckState.ACTIVE || mHealthCheckState == HealthCheckState.INACTIVE; @@ -1843,7 +1868,7 @@ public class PackageWatchdog { * * @return the new {@link HealthCheckState health check state} */ - @GuardedBy("mLock") + @GuardedBy("sLock") @HealthCheckState private int updateHealthCheckStateLocked() { int oldState = mHealthCheckState; @@ -1898,7 +1923,7 @@ public class PackageWatchdog { } } - @GuardedBy("mLock") + @GuardedBy("sLock") @SuppressWarnings("GuardedBy") void saveAllObserversBootMitigationCountToMetadata(String filePath) { HashMap<String, Integer> bootMitigationCounts = new HashMap<>(); @@ -2001,7 +2026,7 @@ public class PackageWatchdog { /** Increments the boot counter, and returns whether the device is bootlooping. */ - @GuardedBy("mLock") + @GuardedBy("sLock") public boolean incrementAndTest() { if (Flags.recoverabilityDetection()) { readAllObserversBootMitigationCountIfNecessary(METADATA_FILE); @@ -2042,7 +2067,7 @@ public class PackageWatchdog { } } - @GuardedBy("mLock") + @GuardedBy("sLock") private boolean performedMitigationsDuringWindow() { for (ObserverInternal observerInternal: mAllObservers.values()) { if (observerInternal.getBootMitigationCount() > 0) { @@ -2052,7 +2077,7 @@ public class PackageWatchdog { return false; } - @GuardedBy("mLock") + @GuardedBy("sLock") private void resetAllObserversBootMitigationCount() { for (int i = 0; i < mAllObservers.size(); i++) { final ObserverInternal observer = mAllObservers.valueAt(i); @@ -2061,7 +2086,7 @@ public class PackageWatchdog { saveAllObserversBootMitigationCountToMetadata(METADATA_FILE); } - @GuardedBy("mLock") + @GuardedBy("sLock") @SuppressWarnings("GuardedBy") void readAllObserversBootMitigationCountIfNecessary(String filePath) { File metadataFile = new File(filePath); diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesObservable.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesObservable.kt deleted file mode 100644 index e70ec5b2e38e..000000000000 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesObservable.kt +++ /dev/null @@ -1,45 +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. - */ - -package com.android.settingslib.datastore - -import android.content.SharedPreferences - -/** [SharedPreferences] based [KeyedDataObservable]. */ -class SharedPreferencesObservable(private val sharedPreferences: SharedPreferences) : - KeyedDataObservable<String>(), AutoCloseable { - - private val listener = createSharedPreferenceListener() - - init { - sharedPreferences.registerOnSharedPreferenceChangeListener(listener) - } - - override fun close() { - sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) - } -} - -/** Creates [SharedPreferences.OnSharedPreferenceChangeListener] for [KeyedObservable]. */ -internal fun KeyedObservable<String>.createSharedPreferenceListener() = - SharedPreferences.OnSharedPreferenceChangeListener { _, key -> - if (key != null) { - notifyChange(key, DataChangeReason.UPDATE) - } else { - // On Android >= R, SharedPreferences.Editor.clear() will trigger this case - notifyChange(DataChangeReason.DELETE) - } - } diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt index 088bef230374..7aece5185800 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt @@ -42,7 +42,7 @@ abstract class GetPreferenceGraphApiHandler( callingUid: Int, request: GetPreferenceGraphRequest, ): PreferenceGraphProto { - val builder = PreferenceGraphBuilder.of(application, request) + val builder = PreferenceGraphBuilder.of(application, myUid, callingUid, request) if (request.screenKeys.isEmpty()) { for (key in PreferenceScreenRegistry.preferenceScreens.keys) { builder.addPreferenceScreenFromRegistry(key) @@ -68,16 +68,18 @@ constructor( val screenKeys: Set<String> = setOf(), val visitedScreens: Set<String> = setOf(), val locale: Locale? = null, - val includeValue: Boolean = true, + val flags: Int = PreferenceGetterFlags.ALL, + val includeValue: Boolean = true, // TODO: clean up val includeValueDescriptor: Boolean = true, ) object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> { override fun encode(data: GetPreferenceGraphRequest): Bundle = - Bundle(3).apply { + Bundle(4).apply { putStringArray(KEY_SCREEN_KEYS, data.screenKeys.toTypedArray()) putStringArray(KEY_VISITED_KEYS, data.visitedScreens.toTypedArray()) putString(KEY_LOCALE, data.locale?.toLanguageTag()) + putInt(KEY_FLAGS, data.flags) } override fun decode(data: Bundle): GetPreferenceGraphRequest { @@ -88,12 +90,14 @@ object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> screenKeys.toSet(), visitedScreens.toSet(), data.getString(KEY_LOCALE).toLocale(), + data.getInt(KEY_FLAGS), ) } private const val KEY_SCREEN_KEYS = "k" private const val KEY_VISITED_KEYS = "v" private const val KEY_LOCALE = "l" + private const val KEY_FLAGS = "f" } object PreferenceGraphProtoCodec : MessageCodec<PreferenceGraphProto> { diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt new file mode 100644 index 000000000000..68aa2d258295 --- /dev/null +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceCoordinate.kt @@ -0,0 +1,45 @@ +/* + * 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.settingslib.graph + +import android.os.Parcel +import android.os.Parcelable + +/** + * Coordinate to locate a preference. + * + * Within an app, the preference screen key (unique among screens) plus preference key (unique on + * the screen) is used to locate a preference. + */ +data class PreferenceCoordinate(val screenKey: String, val key: String) : Parcelable { + + constructor(parcel: Parcel) : this(parcel.readString()!!, parcel.readString()!!) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(screenKey) + parcel.writeString(key) + } + + override fun describeContents() = 0 + + companion object CREATOR : Parcelable.Creator<PreferenceCoordinate> { + + override fun createFromParcel(parcel: Parcel) = PreferenceCoordinate(parcel) + + override fun newArray(size: Int) = arrayOfNulls<PreferenceCoordinate>(size) + } +} diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt new file mode 100644 index 000000000000..c8453efb9161 --- /dev/null +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt @@ -0,0 +1,148 @@ +/* + * 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.settingslib.graph + +import android.app.Application +import androidx.annotation.IntDef +import com.android.settingslib.graph.proto.PreferenceProto +import com.android.settingslib.ipc.ApiDescriptor +import com.android.settingslib.ipc.ApiHandler +import com.android.settingslib.ipc.ApiPermissionChecker +import com.android.settingslib.metadata.PreferenceHierarchyNode +import com.android.settingslib.metadata.PreferenceScreenRegistry + +/** + * Request to get preference information. + * + * @param preferences coordinate of preferences + * @param flags a combination of constants in [PreferenceGetterFlags] + */ +class PreferenceGetterRequest(val preferences: Array<PreferenceCoordinate>, val flags: Int) + +/** Error code of preference getter request. */ +@Target(AnnotationTarget.TYPE) +@IntDef( + PreferenceGetterErrorCode.NOT_FOUND, + PreferenceGetterErrorCode.DISALLOW, + PreferenceGetterErrorCode.INTERNAL_ERROR, +) +@Retention(AnnotationRetention.SOURCE) +annotation class PreferenceGetterErrorCode { + companion object { + /** Preference is not found. */ + const val NOT_FOUND = 1 + /** Disallow to get preference value (e.g. uid not allowed). */ + const val DISALLOW = 2 + /** Internal error happened when get preference information. */ + const val INTERNAL_ERROR = 3 + + fun getMessage(code: Int) = + when (code) { + NOT_FOUND -> "Preference not found" + DISALLOW -> "Disallow to get preference value" + INTERNAL_ERROR -> "Internal error" + else -> "Unknown error" + } + } +} + +/** Response of the getter API. */ +class PreferenceGetterResponse( + val errors: Map<PreferenceCoordinate, @PreferenceGetterErrorCode Int>, + val preferences: Map<PreferenceCoordinate, PreferenceProto>, +) + +/** Preference getter API descriptor. */ +class PreferenceGetterApiDescriptor(override val id: Int) : + ApiDescriptor<PreferenceGetterRequest, PreferenceGetterResponse> { + + override val requestCodec = PreferenceGetterRequestCodec() + + override val responseCodec = PreferenceGetterResponseCodec() +} + +/** Preference getter API implementation. */ +class PreferenceGetterApiHandler( + override val id: Int, + private val permissionChecker: ApiPermissionChecker<PreferenceGetterRequest>, +) : ApiHandler<PreferenceGetterRequest, PreferenceGetterResponse> { + + override fun hasPermission( + application: Application, + myUid: Int, + callingUid: Int, + request: PreferenceGetterRequest, + ) = permissionChecker.hasPermission(application, myUid, callingUid, request) + + override suspend fun invoke( + application: Application, + myUid: Int, + callingUid: Int, + request: PreferenceGetterRequest, + ): PreferenceGetterResponse { + val errors = mutableMapOf<PreferenceCoordinate, Int>() + val preferences = mutableMapOf<PreferenceCoordinate, PreferenceProto>() + val flags = request.flags + for ((screenKey, coordinates) in request.preferences.groupBy { it.screenKey }) { + val screenMetadata = PreferenceScreenRegistry[screenKey] + if (screenMetadata == null) { + for (coordinate in coordinates) { + errors[coordinate] = PreferenceGetterErrorCode.NOT_FOUND + } + continue + } + val nodes = mutableMapOf<String, PreferenceHierarchyNode?>() + for (coordinate in coordinates) nodes[coordinate.key] = null + screenMetadata.getPreferenceHierarchy(application).forEachRecursively { + val metadata = it.metadata + val key = metadata.key + if (nodes.containsKey(key)) nodes[key] = it + } + for (coordinate in coordinates) { + val node = nodes[coordinate.key] + if (node == null) { + errors[coordinate] = PreferenceGetterErrorCode.NOT_FOUND + continue + } + val metadata = node.metadata + try { + val preferenceProto = + metadata.toProto( + application, + myUid, + callingUid, + screenMetadata, + metadata.key == screenMetadata.key, + flags, + ) + if (flags == PreferenceGetterFlags.VALUE && !preferenceProto.hasValue()) { + errors[coordinate] = PreferenceGetterErrorCode.DISALLOW + } else { + preferences[coordinate] = preferenceProto + } + } catch (e: Exception) { + errors[coordinate] = PreferenceGetterErrorCode.INTERNAL_ERROR + } + } + } + return PreferenceGetterResponse(errors, preferences) + } + + override val requestCodec = PreferenceGetterRequestCodec() + + override val responseCodec = PreferenceGetterResponseCodec() +} diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt new file mode 100644 index 000000000000..ff14eb5aae55 --- /dev/null +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterCodecs.kt @@ -0,0 +1,126 @@ +/* + * 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.settingslib.graph + +import android.os.Bundle +import android.os.Parcel +import com.android.settingslib.graph.proto.PreferenceProto +import com.android.settingslib.ipc.MessageCodec +import java.util.Arrays + +/** Message codec for [PreferenceGetterRequest]. */ +class PreferenceGetterRequestCodec : MessageCodec<PreferenceGetterRequest> { + override fun encode(data: PreferenceGetterRequest) = + Bundle(2).apply { + putParcelableArray(null, data.preferences) + putInt(FLAGS, data.flags) + } + + @Suppress("DEPRECATION") + override fun decode(data: Bundle): PreferenceGetterRequest { + data.classLoader = PreferenceCoordinate::class.java.classLoader + val array = data.getParcelableArray(null)!! + + return PreferenceGetterRequest( + Arrays.copyOf(array, array.size, Array<PreferenceCoordinate>::class.java), + data.getInt(FLAGS), + ) + } + + companion object { + private const val FLAGS = "f" + } +} + +/** Message codec for [PreferenceGetterResponse]. */ +class PreferenceGetterResponseCodec : MessageCodec<PreferenceGetterResponse> { + override fun encode(data: PreferenceGetterResponse) = + Bundle(2).apply { + data.errors.toErrorsByteArray()?.let { putByteArray(ERRORS, it) } + data.preferences.toPreferencesByteArray()?.let { putByteArray(null, it) } + } + + private fun Map<PreferenceCoordinate, Int>.toErrorsByteArray(): ByteArray? { + if (isEmpty()) return null + val parcel = Parcel.obtain() + parcel.writeInt(size) + for ((coordinate, code) in this) { + coordinate.writeToParcel(parcel, 0) + parcel.writeInt(code) + } + val bytes = parcel.marshall() + parcel.recycle() + return bytes + } + + private fun Map<PreferenceCoordinate, PreferenceProto>.toPreferencesByteArray(): ByteArray? { + if (isEmpty()) return null + val parcel = Parcel.obtain() + parcel.writeInt(size) + for ((coordinate, preferenceProto) in this) { + coordinate.writeToParcel(parcel, 0) + val data = preferenceProto.toByteArray() + parcel.writeInt(data.size) + parcel.writeByteArray(data) + } + val bytes = parcel.marshall() + parcel.recycle() + return bytes + } + + override fun decode(data: Bundle) = + PreferenceGetterResponse( + data.getByteArray(ERRORS).toErrors(), + data.getByteArray(null).toPreferences(), + ) + + private fun ByteArray?.toErrors(): Map<PreferenceCoordinate, Int> { + if (this == null) return emptyMap() + val parcel = Parcel.obtain() + parcel.unmarshall(this, 0, size) + parcel.setDataPosition(0) + val count = parcel.readInt() + val errors = mutableMapOf<PreferenceCoordinate, Int>() + repeat(count) { + val coordinate = PreferenceCoordinate(parcel) + errors[coordinate] = parcel.readInt() + } + parcel.recycle() + return errors + } + + private fun ByteArray?.toPreferences(): Map<PreferenceCoordinate, PreferenceProto> { + if (this == null) return emptyMap() + val parcel = Parcel.obtain() + parcel.unmarshall(this, 0, size) + parcel.setDataPosition(0) + val count = parcel.readInt() + val preferences = mutableMapOf<PreferenceCoordinate, PreferenceProto>() + repeat(count) { + val coordinate = PreferenceCoordinate(parcel) + val bytes = parcel.readInt() + val array = ByteArray(bytes).also { parcel.readByteArray(it) } + preferences[coordinate] = PreferenceProto.parseFrom(array) + } + parcel.recycle() + return preferences + } + + companion object { + private const val ERRORS = "e" + } +} diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterFlags.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterFlags.kt new file mode 100644 index 000000000000..632f154b72ab --- /dev/null +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterFlags.kt @@ -0,0 +1,31 @@ +/* + * 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.settingslib.graph + +/** Flags for preference getter operation. */ +object PreferenceGetterFlags { + const val VALUE = 1 shl 0 + const val VALUE_DESCRIPTOR = 1 shl 1 + const val METADATA = 1 shl 2 + const val ALL = (1 shl 3) - 1 + + fun Int.includeValue() = (this and VALUE) != 0 + + fun Int.includeValueDescriptor() = (this and VALUE_DESCRIPTOR) != 0 + + fun Int.includeMetadata() = (this and METADATA) != 0 +} diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt index 6760e72be4a6..a65d24cf3d6f 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt @@ -31,6 +31,9 @@ import androidx.preference.Preference import androidx.preference.PreferenceGroup import androidx.preference.PreferenceScreen import androidx.preference.TwoStatePreference +import com.android.settingslib.graph.PreferenceGetterFlags.includeMetadata +import com.android.settingslib.graph.PreferenceGetterFlags.includeValue +import com.android.settingslib.graph.PreferenceGetterFlags.includeValueDescriptor import com.android.settingslib.graph.proto.PreferenceGraphProto import com.android.settingslib.graph.proto.PreferenceGroupProto import com.android.settingslib.graph.proto.PreferenceProto @@ -41,7 +44,6 @@ import com.android.settingslib.metadata.BooleanValue import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceHierarchy -import com.android.settingslib.metadata.PreferenceHierarchyNode import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider @@ -50,6 +52,7 @@ import com.android.settingslib.metadata.PreferenceScreenRegistry import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.PreferenceTitleProvider import com.android.settingslib.metadata.RangeValue +import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.preference.PreferenceScreenFactory import com.android.settingslib.preference.PreferenceScreenProvider import java.util.Locale @@ -60,14 +63,17 @@ private const val TAG = "PreferenceGraphBuilder" /** Builder of preference graph. */ class PreferenceGraphBuilder -private constructor(private val context: Context, private val request: GetPreferenceGraphRequest) { +private constructor( + private val context: Context, + private val myUid: Int, + private val callingUid: Int, + private val request: GetPreferenceGraphRequest, +) { private val preferenceScreenFactory by lazy { PreferenceScreenFactory(context.ofLocale(request.locale)) } private val builder by lazy { PreferenceGraphProto.newBuilder() } private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) } - private val includeValue = request.includeValue - private val includeValueDescriptor = request.includeValueDescriptor private suspend fun init() { for (key in request.screenKeys) { @@ -229,7 +235,7 @@ private constructor(private val context: Context, private val request: GetPrefer enabled = isEnabled available = isVisible persistent = isPersistent - if (includeValue && isPersistent && this@toProto is TwoStatePreference) { + if (request.flags.includeValue() && isPersistent && this@toProto is TwoStatePreference) { value = preferenceValueProto { booleanValue = this@toProto.isChecked } } this@toProto.fragment.toActionTarget(preferenceExtras)?.let { @@ -243,14 +249,14 @@ private constructor(private val context: Context, private val request: GetPrefer screenMetadata: PreferenceScreenMetadata, isRoot: Boolean, ): PreferenceGroupProto = preferenceGroupProto { - preference = toProto(screenMetadata, this@toProto, isRoot) + preference = toProto(screenMetadata, this@toProto.metadata, isRoot) forEachAsync { addPreferences( preferenceOrGroupProto { if (it is PreferenceHierarchy) { group = it.toProto(screenMetadata, false) } else { - preference = toProto(screenMetadata, it, false) + preference = toProto(screenMetadata, it.metadata, false) } } ) @@ -259,93 +265,19 @@ private constructor(private val context: Context, private val request: GetPrefer private suspend fun toProto( screenMetadata: PreferenceScreenMetadata, - node: PreferenceHierarchyNode, + metadata: PreferenceMetadata, isRoot: Boolean, - ) = preferenceProto { - val metadata = node.metadata - key = metadata.key - metadata.getTitleTextProto(isRoot)?.let { title = it } - if (metadata.summary != 0) { - summary = textProto { resourceId = metadata.summary } - } else { - (metadata as? PreferenceSummaryProvider)?.getSummary(context)?.let { - summary = textProto { string = it.toString() } - } - } - val metadataIcon = metadata.getPreferenceIcon(context) - if (metadataIcon != 0) icon = metadataIcon - if (metadata.keywords != 0) keywords = metadata.keywords - val preferenceExtras = metadata.extras(context) - preferenceExtras?.let { extras = it.toProto() } - indexable = metadata.isIndexable(context) - enabled = metadata.isEnabled(context) - if (metadata is PreferenceAvailabilityProvider) { - available = metadata.isAvailable(context) - } - if (metadata is PreferenceRestrictionProvider) { - restricted = metadata.isRestricted(context) - } - persistent = metadata.isPersistent(context) - if (persistent) { - if (includeValue && metadata is PersistentPreference<*>) { - value = preferenceValueProto { - when (metadata) { - is BooleanValue -> - metadata - .storage(context) - .getValue(metadata.key, Boolean::class.javaObjectType) - ?.let { booleanValue = it } - is RangeValue -> { - metadata - .storage(context) - .getValue(metadata.key, Int::class.javaObjectType) - ?.let { intValue = it } - } - else -> {} - } - } + ) = + metadata.toProto(context, myUid, callingUid, screenMetadata, isRoot, request.flags).also { + if (metadata is PreferenceScreenMetadata) { + @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata) } - if (includeValueDescriptor) { - valueDescriptor = preferenceValueDescriptorProto { - when (metadata) { - is BooleanValue -> booleanType = true - is RangeValue -> rangeValue = rangeValueProto { - min = metadata.getMinValue(context) - max = metadata.getMaxValue(context) - step = metadata.getIncrementStep(context) - } - else -> {} - } + metadata.intent(context)?.resolveActivity(context.packageManager)?.let { + if (it.packageName == context.packageName) { + add(it.className) } } } - if (metadata is PreferenceScreenMetadata) { - @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata) - } - metadata.intent(context)?.let { actionTarget = it.toActionTarget() } - screenMetadata.getLaunchIntent(context, metadata)?.let { launchIntent = it.toProto() } - } - - private fun PreferenceMetadata.getTitleTextProto(isRoot: Boolean): TextProto? { - if (isRoot && this is PreferenceScreenMetadata) { - val titleRes = screenTitle - if (titleRes != 0) { - return textProto { resourceId = titleRes } - } else { - getScreenTitle(context)?.let { - return textProto { string = it.toString() } - } - } - } else { - val titleRes = title - if (titleRes != 0) { - return textProto { resourceId = titleRes } - } - } - return (this as? PreferenceTitleProvider)?.getTitle(context)?.let { - textProto { string = it.toString() } - } - } private suspend fun String?.toActionTarget(extras: Bundle?): ActionTarget? { if (this.isNullOrEmpty()) return null @@ -399,22 +331,129 @@ private constructor(private val context: Context, private val request: GetPrefer return null } - private suspend fun Intent.toActionTarget(): ActionTarget { - if (component?.packageName == "") { - setClassName(context, component!!.className) + private suspend fun Intent.toActionTarget() = + toActionTarget(context).also { + resolveActivity(context.packageManager)?.let { + if (it.packageName == context.packageName) { + add(it.className) + } + } } - resolveActivity(context.packageManager)?.let { - if (it.packageName == context.packageName) { - add(it.className) + + companion object { + suspend fun of( + context: Context, + myUid: Int, + callingUid: Int, + request: GetPreferenceGraphRequest, + ) = PreferenceGraphBuilder(context, myUid, callingUid, request).also { it.init() } + } +} + +fun PreferenceMetadata.toProto( + context: Context, + myUid: Int, + callingUid: Int, + screenMetadata: PreferenceScreenMetadata, + isRoot: Boolean, + flags: Int, +) = preferenceProto { + val metadata = this@toProto + key = metadata.key + if (flags.includeMetadata()) { + metadata.getTitleTextProto(context, isRoot)?.let { title = it } + if (metadata.summary != 0) { + summary = textProto { resourceId = metadata.summary } + } else { + (metadata as? PreferenceSummaryProvider)?.getSummary(context)?.let { + summary = textProto { string = it.toString() } } } - return actionTargetProto { intent = toProto() } + val metadataIcon = metadata.getPreferenceIcon(context) + if (metadataIcon != 0) icon = metadataIcon + if (metadata.keywords != 0) keywords = metadata.keywords + val preferenceExtras = metadata.extras(context) + preferenceExtras?.let { extras = it.toProto() } + indexable = metadata.isIndexable(context) + enabled = metadata.isEnabled(context) + if (metadata is PreferenceAvailabilityProvider) { + available = metadata.isAvailable(context) + } + if (metadata is PreferenceRestrictionProvider) { + restricted = metadata.isRestricted(context) + } + metadata.intent(context)?.let { actionTarget = it.toActionTarget(context) } + screenMetadata.getLaunchIntent(context, metadata)?.let { launchIntent = it.toProto() } } + persistent = metadata.isPersistent(context) + if (persistent) { + if ( + flags.includeValue() && + enabled && + (!hasAvailable() || available) && + (!hasRestricted() || !restricted) && + metadata is PersistentPreference<*> && + metadata.getReadPermit(context, myUid, callingUid) == ReadWritePermit.ALLOW + ) { + value = preferenceValueProto { + when (metadata) { + is BooleanValue -> + metadata + .storage(context) + .getValue(metadata.key, Boolean::class.javaObjectType) + ?.let { booleanValue = it } + is RangeValue -> { + metadata + .storage(context) + .getValue(metadata.key, Int::class.javaObjectType) + ?.let { intValue = it } + } + else -> {} + } + } + } + if (flags.includeValueDescriptor()) { + valueDescriptor = preferenceValueDescriptorProto { + when (metadata) { + is BooleanValue -> booleanType = true + is RangeValue -> rangeValue = rangeValueProto { + min = metadata.getMinValue(context) + max = metadata.getMaxValue(context) + step = metadata.getIncrementStep(context) + } + else -> {} + } + } + } + } +} - companion object { - suspend fun of(context: Context, request: GetPreferenceGraphRequest) = - PreferenceGraphBuilder(context, request).also { it.init() } +private fun PreferenceMetadata.getTitleTextProto(context: Context, isRoot: Boolean): TextProto? { + if (isRoot && this is PreferenceScreenMetadata) { + val titleRes = screenTitle + if (titleRes != 0) { + return textProto { resourceId = titleRes } + } else { + getScreenTitle(context)?.let { + return textProto { string = it.toString() } + } + } + } else { + val titleRes = title + if (titleRes != 0) { + return textProto { resourceId = titleRes } + } + } + return (this as? PreferenceTitleProvider)?.getTitle(context)?.let { + textProto { string = it.toString() } + } +} + +private fun Intent.toActionTarget(context: Context): ActionTarget { + if (component?.packageName == "") { + setClassName(context, component!!.className) } + return actionTargetProto { intent = toProto() } } @SuppressLint("AppBundleLocaleChanges") diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt index a2b826a50e58..9179f8ffc082 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt @@ -58,6 +58,9 @@ class PreferenceHierarchy internal constructor(metadata: PreferenceMetadata) : /** Specifies preference order in the hierarchy. */ infix fun PreferenceHierarchyNode.order(order: Int) = apply { this.order = order } + /** Specifies preference order in the hierarchy for group. */ + infix fun PreferenceHierarchy.order(order: Int) = apply { this.order = order } + /** Adds a preference to the hierarchy. */ @JvmOverloads fun add(metadata: PreferenceMetadata, order: Int? = null) { diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt index ed748bb58a9a..7cb36db856eb 100644 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt +++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt @@ -17,6 +17,8 @@ package com.android.settingslib.service import com.android.settingslib.graph.GetPreferenceGraphRequest +import com.android.settingslib.graph.PreferenceGetterApiHandler +import com.android.settingslib.graph.PreferenceGetterRequest import com.android.settingslib.graph.PreferenceSetterApiHandler import com.android.settingslib.graph.PreferenceSetterRequest import com.android.settingslib.ipc.ApiHandler @@ -37,6 +39,7 @@ open class PreferenceService( preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>> = setOf(), graphPermissionChecker: ApiPermissionChecker<GetPreferenceGraphRequest>? = null, setterPermissionChecker: ApiPermissionChecker<PreferenceSetterRequest>? = null, + getterPermissionChecker: ApiPermissionChecker<PreferenceGetterRequest>? = null, vararg apiHandlers: ApiHandler<*, *>, ) : MessengerService( @@ -45,6 +48,9 @@ open class PreferenceService( setterPermissionChecker?.let { add(PreferenceSetterApiHandler(API_PREFERENCE_SETTER, it)) } + getterPermissionChecker?.let { + add(PreferenceGetterApiHandler(API_PREFERENCE_GETTER, it)) + } addAll(apiHandlers) }, permissionChecker, diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt index 7655daa6cf21..d71405e126ce 100644 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt +++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt @@ -24,6 +24,9 @@ internal const val API_GET_PREFERENCE_GRAPH = 1 /** API id for preference value setter. */ internal const val API_PREFERENCE_SETTER = 2 +/** API id for preference getter. */ +internal const val API_PREFERENCE_GETTER = 3 + /** * The max API id reserved for internal preference service usages. Custom API id should start with * **1000** to avoid conflict. diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt index e883a4a55af9..25bb46c837f6 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt @@ -25,3 +25,36 @@ fun Modifier.contentDescription(contentDescription: String?) = if (contentDescription != null) this.semantics { this.contentDescription = contentDescription } else this + +/** + * Concatenates this modifier with another if `condition` is true. + * + * This method allows inline conditional addition of modifiers to a modifier chain. Instead of + * writing + * + * ``` + * val aModifier = Modifier.a() + * val bModifier = if(condition) aModifier.b() else aModifier + * Composable(modifier = bModifier) + * ``` + * + * You can instead write + * + * ``` + * Composable(modifier = Modifier.a().thenIf(condition){ + * Modifier.b() + * } + * ``` + * + * This makes the modifier chain easier to read. + * + * Note that unlike the non-factory version, the conditional modifier is recreated each time, and + * may never be created at all. + * + * @param condition Whether or not to apply the modifiers. + * @param factory Creates the modifier to concatenate with the current one. + * @return a Modifier representing this modifier followed by other in sequence. + * @see Modifier.then + */ +inline fun Modifier.thenIf(condition: Boolean, crossinline factory: () -> Modifier): Modifier = + if (condition) this.then(factory()) else this diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt index acb96be64a34..b1bb79d61b03 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt @@ -36,12 +36,13 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.min +import com.android.settingslib.spa.framework.compose.thenIf import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled import com.android.settingslib.spa.framework.theme.SettingsShape import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled +import com.android.settingslib.spa.widget.ui.LocalIsInCategory import com.android.settingslib.spa.widget.ui.SettingsTitle @Composable @@ -57,18 +58,18 @@ internal fun BaseLayout( paddingVertical: Dp = SettingsDimension.itemPaddingVertical, widget: @Composable () -> Unit = {}, ) { + val surfaceBright = MaterialTheme.colorScheme.surfaceBright Row( modifier = modifier .fillMaxWidth() .semantics(mergeDescendants = true) {} - .then( - if (isSpaExpressiveEnabled) - Modifier.heightIn(min = SettingsDimension.preferenceMinHeight) - .clip(SettingsShape.CornerExtraSmall) - .background(MaterialTheme.colorScheme.surfaceBright) - else Modifier - ) + .thenIf(isSpaExpressiveEnabled) { + Modifier.heightIn(min = SettingsDimension.preferenceMinHeight) + } + .thenIf(isSpaExpressiveEnabled && LocalIsInCategory.current) { + Modifier.clip(SettingsShape.CornerExtraSmall).background(surfaceBright) + } .padding(end = paddingEnd), verticalAlignment = Alignment.CenterVertically, ) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt index 28b2b4ab1662..96d2abb70391 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt @@ -31,6 +31,8 @@ import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -99,7 +101,7 @@ fun Category(title: String? = null, content: @Composable ColumnScope.() -> Unit) verticalArrangement = if (isSpaExpressiveEnabled) Arrangement.spacedBy(SettingsDimension.paddingTiny) else Arrangement.Top, - content = content, + content = { CompositionLocalProvider(LocalIsInCategory provides true) { content() } }, ) } } @@ -109,15 +111,14 @@ fun Category(title: String? = null, content: @Composable ColumnScope.() -> Unit) * * @param list The list of items to display. * @param entry The entry for each list item according to its index in list. - * @param key Optional. The key for each item in list to provide unique item identifiers, making - * the list more efficient. - * @param title Optional. Category title for each item or each group of items in the list. It - * should be decided by the index. + * @param key Optional. The key for each item in list to provide unique item identifiers, making the + * list more efficient. + * @param title Optional. Category title for each item or each group of items in the list. It should + * be decided by the index. * @param bottomPadding Optional. Bottom outside padding of the category. * @param state Optional. State of LazyList. * @param content Optional. Content to be shown at the top of the category. */ - @Composable fun LazyCategory( list: List<Any>, @@ -144,17 +145,19 @@ fun LazyCategory( verticalArrangement = Arrangement.spacedBy(SettingsDimension.paddingTiny), state = state, ) { - item { content() } + item { CompositionLocalProvider(LocalIsInCategory provides true) { content() } } items(count = list.size, key = key) { title?.invoke(it)?.let { title -> CategoryTitle(title) } - val entryPreference = entry(it) - entryPreference() + CompositionLocalProvider(LocalIsInCategory provides true) { entry(it)() } } } } } +/** LocalIsInCategory containing the if the current composable is in a category. */ +internal val LocalIsInCategory = compositionLocalOf { false } + @Preview @Composable private fun CategoryPreview() { diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index bf419cc46aeb..076f82acce09 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -171,3 +171,10 @@ flag { description: "Enable the user consent prompt before writing sensitive preferences via service" bug: "378552675" } + +flag { + name: "hearing_devices_input_routing_control" + namespace: "accessibility" + description: "Enable the input routing control in device details and hearing devices dialog." + bug: "349255906" +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index d0827b30efc9..4eb0567c67d9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -133,8 +133,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> * If an ACTION_UUID intent comes in within * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect * again with the new UUIDs + * The value is reset if a manual disconnection happens. */ - private long mConnectAttempted; + private long mConnectAttempted = -1; // Active device state private boolean mIsActiveDeviceA2dp = false; @@ -369,6 +370,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } public void disconnect() { + mConnectAttempted = -1; synchronized (mProfileLock) { if (getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { for (CachedBluetoothDevice member : getMemberDevice()) { @@ -983,15 +985,19 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } if (BluetoothUtils.D) { - Log.d(TAG, "onUuidChanged: Time since last connect=" - + (SystemClock.elapsedRealtime() - mConnectAttempted)); + long lastConnectAttempted = mConnectAttempted == -1 ? 0 : mConnectAttempted; + Log.d( + TAG, + "onUuidChanged: Time since last connect/manual disconnect=" + + (SystemClock.elapsedRealtime() - lastConnectAttempted)); } /* * If a connect was attempted earlier without any UUID, we will do the connect now. * Otherwise, allow the connect on UUID change. */ - if ((mConnectAttempted + timeout) > SystemClock.elapsedRealtime()) { + if (mConnectAttempted != -1 + && (mConnectAttempted + timeout) > SystemClock.elapsedRealtime()) { Log.d(TAG, "onUuidChanged: triggering connectDevice"); connectDevice(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt index b41e9703d427..4c4ce2a61851 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt @@ -221,6 +221,7 @@ class AudioSharingRepositoryImpl( override suspend fun audioSharingAvailable(): Boolean { return withContext(backgroundCoroutineContext) { BluetoothUtils.isAudioSharingEnabled() + || BluetoothUtils.isAudioSharingPreviewEnabled(contentResolver) } } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt index 18a4c6d1748d..791d86611b06 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt @@ -26,4 +26,6 @@ interface AudioSharingLogger { fun onVolumeMapChanged(map: Map<Int, Int>) fun onSetDeviceVolumeRequested(volume: Int) + + fun onAudioSharingAvailabilityRequestedError(requestFrom: String, e: String) }
\ No newline at end of file diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt index cc4cc8d4ab96..731b3a136cd7 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt @@ -43,4 +43,8 @@ class FakeAudioSharingRepositoryLogger : AudioSharingLogger { override fun onSetDeviceVolumeRequested(volume: Int) { mutableLogs.add("onSetVolumeRequested volume=$volume") } + + override fun onAudioSharingAvailabilityRequestedError(requestFrom: String, e: String) { + mutableLogs.add("onAudioSharingAvailabilityRequestedError, requestFrom=$requestFrom") + } }
\ No newline at end of file diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 8f58e8cd1973..c90ba8249d54 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -462,5 +462,7 @@ public class GlobalSettingsValidators { Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE_NONE, Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE_COMPANION )); + VALIDATORS.put(Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, ANY_STRING_VALIDATOR); + VALIDATORS.put(Global.HEARING_DEVICE_LOCAL_NOTIFICATION, ANY_STRING_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 558ccaf7a707..509b88b257fe 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -263,6 +263,5 @@ public class SystemSettingsValidators { VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR); - VALIDATORS.put(System.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, ANY_STRING_VALIDATOR); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c2beaa8048f6..9de7faf04df6 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -564,6 +564,8 @@ public class SettingsBackupTest { Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER, Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, Settings.Global.ENABLE_BACK_ANIMATION, // Temporary for T, dev option only + Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, // cache per hearing device + Settings.Global.HEARING_DEVICE_LOCAL_NOTIFICATION, // cache per hearing device Settings.Global.Wearable.COMBINED_LOCATION_ENABLE, Settings.Global.Wearable.HAS_PAY_TOKENS, Settings.Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, @@ -944,8 +946,7 @@ public class SettingsBackupTest { Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, Settings.System.WEAR_TTS_PREWARM_ENABLED, Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, - Settings.System.MULTI_AUDIO_FOCUS_ENABLED, // form-factor/OEM specific - Settings.System.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME // internal cache + Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific ); if (!Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) { settings.add(Settings.System.MIN_REFRESH_RATE); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 526320debb1a..1070ebdbb946 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -746,6 +746,9 @@ <!-- Permission required for ATS test - CarDevicePolicyManagerTest --> <uses-permission android:name="android.permission.LOCK_DEVICE" /> + <!-- Permission required for AuthenticationPolicyManagerTest --> + <uses-permission android:name="android.permission.MANAGE_SECURE_LOCK_DEVICE" /> + <!-- Permissions required for CTS test - CtsSafetyCenterTestCases --> <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" /> <uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" /> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index dafe38dc5c00..d1a22e8612d3 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -208,7 +208,9 @@ filegroup { "tests/src/**/systemui/qs/tiles/DreamTileTest.java", "tests/src/**/systemui/qs/FgsManagerControllerTest.java", "tests/src/**/systemui/qs/QSPanelTest.kt", + "tests/src/**/systemui/reardisplay/RearDisplayCoreStartableTest.kt", "tests/src/**/systemui/reardisplay/RearDisplayDialogControllerTest.java", + "tests/src/**/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt", "tests/src/**/systemui/statusbar/KeyboardShortcutListSearchTest.java", "tests/src/**/systemui/statusbar/KeyboardShortcutsTest.java", "tests/src/**/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt", diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 123f82393679..67666c3db81f 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -632,9 +632,9 @@ flag { flag { name: "status_bar_connected_displays" - namespace: "systemui" + namespace: "lse_desktop_experience" description: "Shows the status bar on connected displays" - bug: "362720336" + bug: "379264862" } flag { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt index 1551ca97a2e3..ef5e90bd7aad 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -179,11 +179,18 @@ internal constructor( val targetItem = if (communalWidgetResizing()) { state.layoutInfo.visibleItemsInfo.findLast { item -> + val lastVisibleItemIndex = state.layoutInfo.visibleItemsInfo.last().index val itemBoundingBox = IntRect(item.offset, item.size) draggingItemKey != item.key && contentListState.isItemEditable(item.index) && (draggingBoundingBox.contains(itemBoundingBox.center) || - itemBoundingBox.contains(draggingBoundingBox.center)) + itemBoundingBox.contains(draggingBoundingBox.center)) && + // If we swap with the last visible item, and that item doesn't fit + // in the gap created by moving the current item, then the current item + // will get placed after the last visible item. In this case, it gets + // placed outside of the viewport. We avoid this here, so the user + // has to scroll first before the swap can happen. + (item.index != lastVisibleItemIndex || item.span <= draggingItem.span) } } else { state.layoutInfo.visibleItemsInfo diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index 9390664d1283..597cbf24729b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.systemui.biometrics.AuthController +import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags @@ -147,7 +148,7 @@ constructor( } else { val scaleFactor = authController.scaleFactor val bottomPaddingPx = - context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) + context.resources.getDimensionPixelSize(customR.dimen.lock_icon_margin_bottom) val heightPx = windowViewBounds.bottom.toFloat() Pair( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt index 352d29e21987..74896482be88 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.offset -import androidx.compose.ui.util.fastFirst import androidx.compose.ui.util.fastFirstOrNull import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy.LayoutId import kotlin.math.max @@ -60,18 +59,20 @@ class SingleShadeMeasurePolicy( val shadeHeaderPlaceable = measurables - .fastFirst { it.layoutId == LayoutId.ShadeHeader } - .measure(constraintsWithCutout) + .fastFirstOrNull { it.layoutId == LayoutId.ShadeHeader } + ?.measure(constraintsWithCutout) val mediaPlaceable = measurables .fastFirstOrNull { it.layoutId == LayoutId.Media } ?.measure(applyMediaConstraints(constraintsWithCutout, isMediaInRow)) val quickSettingsPlaceable = measurables - .fastFirst { it.layoutId == LayoutId.QuickSettings } - .measure(constraintsWithCutout) + .fastFirstOrNull { it.layoutId == LayoutId.QuickSettings } + ?.measure(constraintsWithCutout) val notificationsPlaceable = - measurables.fastFirst { it.layoutId == LayoutId.Notifications }.measure(constraints) + measurables + .fastFirstOrNull { it.layoutId == LayoutId.Notifications } + ?.measure(constraints) val notificationsTop = calculateNotificationsTop( @@ -84,23 +85,25 @@ class SingleShadeMeasurePolicy( onNotificationsTopChanged(notificationsTop) return layout(constraints.maxWidth, constraints.maxHeight) { - shadeHeaderPlaceable.placeRelative(x = insetsLeft, y = insetsTop) - quickSettingsPlaceable.placeRelative( + shadeHeaderPlaceable?.placeRelative(x = insetsLeft, y = insetsTop) + val statusBarHeaderHeight = shadeHeaderPlaceable?.height ?: 0 + quickSettingsPlaceable?.placeRelative( x = insetsLeft, - y = insetsTop + shadeHeaderPlaceable.height, + y = insetsTop + statusBarHeaderHeight, ) - if (mediaPlaceable != null) + if (mediaPlaceable != null) { + val quickSettingsHeight = quickSettingsPlaceable?.height ?: 0 + if (isMediaInRow) { // mediaPlaceable height ranges from 0 to qsHeight. We want it to be centered // vertically when it's smaller than the QS - val mediaCenteringOffset = - (quickSettingsPlaceable.height - mediaPlaceable.height) / 2 + val mediaCenteringOffset = (quickSettingsHeight - mediaPlaceable.height) / 2 mediaPlaceable.placeRelative( x = insetsLeft + constraintsWithCutout.maxWidth / 2, y = insetsTop + - shadeHeaderPlaceable.height + + statusBarHeaderHeight + mediaCenteringOffset + mediaOffset(), zIndex = mediaZIndex(), @@ -108,30 +111,34 @@ class SingleShadeMeasurePolicy( } else { mediaPlaceable.placeRelative( x = insetsLeft, - y = insetsTop + shadeHeaderPlaceable.height + quickSettingsPlaceable.height, + y = insetsTop + statusBarHeaderHeight + quickSettingsHeight, zIndex = mediaZIndex(), ) } + } // Notifications don't need to accommodate for horizontal insets - notificationsPlaceable.placeRelative(x = 0, y = notificationsTop) + notificationsPlaceable?.placeRelative(x = 0, y = notificationsTop) } } private fun calculateNotificationsTop( - statusBarHeaderPlaceable: Placeable, - quickSettingsPlaceable: Placeable, + statusBarHeaderPlaceable: Placeable?, + quickSettingsPlaceable: Placeable?, mediaPlaceable: Placeable?, insetsTop: Int, isMediaInRow: Boolean, ): Int { val mediaHeight = mediaPlaceable?.height ?: 0 + val statusBarHeaderHeight = statusBarHeaderPlaceable?.height ?: 0 + val quickSettingsHeight = quickSettingsPlaceable?.height ?: 0 + return insetsTop + - statusBarHeaderPlaceable.height + + statusBarHeaderHeight + if (isMediaInRow) { - max(quickSettingsPlaceable.height, mediaHeight) + max(quickSettingsHeight, mediaHeight) } else { - quickSettingsPlaceable.height + mediaHeight + quickSettingsHeight + mediaHeight } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index 160326792f81..0db545f262a9 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -519,8 +519,19 @@ internal class MultiPointerDraggableNode( // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned // true). if (overSlop == 0f) { - val delta = (drag.position - consumablePointer.position).toFloat() - check(delta != 0f) { "delta is equal to 0" } + // If the user drags in the opposite direction, the delta becomes zero because + // we return to the original point. Therefore, we should use the previous event + // to calculate the direction. + val delta = (drag.position - drag.previousPosition).toFloat() + check(delta != 0f) { + buildString { + append("delta is equal to 0 ") + append("touchSlop ${currentValueOf(LocalViewConfiguration).touchSlop} ") + append("consumablePointer.position ${consumablePointer.position} ") + append("drag.position ${drag.position} ") + append("drag.previousPosition ${drag.previousPosition}") + } + } overSlop = delta.sign } drag diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 3bf2ed50b709..b3fd097946d0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -354,6 +354,7 @@ internal class MutableSceneTransitionLayoutStateImpl( } override suspend fun startTransition(transition: TransitionState.Transition, chain: Boolean) { + Log.i(TAG, "startTransition(transition=$transition, chain=$chain)") checkThread() // Prepare the transition before starting it. This is outside of the try/finally block on @@ -486,6 +487,7 @@ internal class MutableSceneTransitionLayoutStateImpl( return } + Log.i(TAG, "finishTransition(transition=$transition)") check(transitionStates.fastAll { it is TransitionState.Transition }) // Mark this transition as finished. @@ -513,13 +515,10 @@ internal class MutableSceneTransitionLayoutStateImpl( // If all transitions are finished, we are idle. if (i == nStates) { check(finishedTransitions.isEmpty()) - this.transitionStates = - listOf( - TransitionState.Idle( - lastTransition.currentScene, - lastTransition.currentOverlays, - ) - ) + val idle = + TransitionState.Idle(lastTransition.currentScene, lastTransition.currentOverlays) + Log.i(TAG, "all transitions finished. idle=$idle") + this.transitionStates = listOf(idle) } else if (i > 0) { this.transitionStates = transitionStates.subList(fromIndex = i, toIndex = nStates) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt index 33f015fe49d9..d66fe42084de 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt @@ -90,6 +90,10 @@ sealed interface TransitionState { // The set of overlays does not change in a [ChangeCurrentScene] transition. return currentOverlaysWhenTransitionStarted } + + override fun toString(): String { + return "ChangeScene(fromScene=$fromScene, toScene=$toScene)" + } } /** @@ -146,6 +150,12 @@ sealed interface TransitionState { currentOverlaysWhenTransitionStarted - overlay } } + + override fun toString(): String { + val isShowing = overlay == toContent + return "ShowOrHideOverlay(overlay=$overlay, fromOrToScene=$fromOrToScene, " + + "isShowing=$isShowing)" + } } /** We are transitioning from [fromOverlay] to [toOverlay]. */ @@ -194,6 +204,10 @@ sealed interface TransitionState { add(include) } } + + override fun toString(): String { + return "ReplaceOverlay(fromOverlay=$fromOverlay, toOverlay=$toOverlay)" + } } /** diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index 5ec74f8d2260..85d8b60d61fb 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -593,7 +593,7 @@ class MultiPointerDraggableTest { } } - fun continueDraggingDown() { + fun dragDown() { rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) } } @@ -603,11 +603,78 @@ class MultiPointerDraggableTest { assertThat(started).isFalse() swipeConsume = true - continueDraggingDown() + // Drag in same direction + dragDown() assertThat(capturedChange).isNotNull() capturedChange = null - continueDraggingDown() + dragDown() + assertThat(capturedChange).isNull() + + assertThat(started).isTrue() + } + + @Test + fun multiPointerSwipeDetectorInteractionZeroOffsetFromStartPosition() { + val size = 200f + val middle = Offset(size / 2f, size / 2f) + + var started = false + + var capturedChange: PointerInputChange? = null + var swipeConsume = false + + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + Box( + Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() }) + .nestedScrollDispatcher() + .multiPointerDraggable( + orientation = Orientation.Vertical, + startDragImmediately = { false }, + swipeDetector = + object : SwipeDetector { + override fun detectSwipe(change: PointerInputChange): Boolean { + capturedChange = change + return swipeConsume + } + }, + onDragStarted = { _, _ -> + started = true + SimpleDragController( + onDrag = { /* do nothing */ }, + onStop = { /* do nothing */ }, + ) + }, + dispatcher = defaultDispatcher, + ) + ) {} + } + + fun startDraggingDown() { + rule.onRoot().performTouchInput { + down(middle) + moveBy(Offset(0f, touchSlop)) + } + } + + fun dragUp() { + rule.onRoot().performTouchInput { moveBy(Offset(0f, -touchSlop)) } + } + + startDraggingDown() + assertThat(capturedChange).isNotNull() + capturedChange = null + assertThat(started).isFalse() + + swipeConsume = true + // Drag in the opposite direction + dragUp() + assertThat(capturedChange).isNotNull() + capturedChange = null + + dragUp() assertThat(capturedChange).isNull() assertThat(started).isTrue() diff --git a/packages/SystemUI/customization/res/values-land/dimens.xml b/packages/SystemUI/customization/res/values-land/dimens.xml new file mode 100644 index 000000000000..50f220c882bd --- /dev/null +++ b/packages/SystemUI/customization/res/values-land/dimens.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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. + --> + +<resources> + <dimen name="lock_icon_margin_bottom">24dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml index 18073adf9e7a..876028195ff3 100644 --- a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml @@ -17,4 +17,5 @@ <resources> <dimen name="keyguard_smartspace_top_offset">0dp</dimen> <dimen name="status_view_margin_horizontal">8dp</dimen> + <dimen name="lock_icon_margin_bottom">60dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml index 041ae62670e5..21b4c7165226 100644 --- a/packages/SystemUI/customization/res/values/dimens.xml +++ b/packages/SystemUI/customization/res/values/dimens.xml @@ -40,4 +40,5 @@ <dimen name="date_weather_view_height">24dp</dimen> <dimen name="enhanced_smartspace_height">104dp</dimen> <dimen name="status_view_margin_horizontal">0dp</dimen> + <dimen name="lock_icon_margin_bottom">74dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt index 7cdfb0eb2451..0f148f89e3c5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt @@ -30,11 +30,11 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory -import com.android.systemui.kosmos.brightnessWarningToast import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor +import com.android.systemui.settings.brightness.ui.brightnessWarningToast import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt new file mode 100644 index 000000000000..e17b66e90c2d --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt @@ -0,0 +1,167 @@ +/* + * 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.systemui.development.data.repository + +import android.content.pm.UserInfo +import android.os.Build +import android.os.UserHandle +import android.os.UserManager +import android.os.userManager +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.settings.fakeGlobalSettings +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +@SmallTest +class DevelopmentSettingRepositoryTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + private val underTest = kosmos.developmentSettingRepository + + @Test + fun nonAdminUser_unrestricted_neverDevelopmentEnabled() = + with(kosmos) { + testScope.runTest { + val userInfo = nonAdminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = false) + + assertThat(settingEnabled).isFalse() + + setSettingValue(false) + assertThat(settingEnabled).isFalse() + + setSettingValue(true) + assertThat(settingEnabled).isFalse() + } + } + + @Test + fun nonAdminUser_restricted_neverDevelopmentEnabled() = + with(kosmos) { + testScope.runTest { + val userInfo = nonAdminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = true) + + assertThat(settingEnabled).isFalse() + + setSettingValue(false) + assertThat(settingEnabled).isFalse() + + setSettingValue(true) + assertThat(settingEnabled).isFalse() + } + } + + @Test + fun adminUser_unrestricted_defaultValueOfSetting() = + with(kosmos) { + testScope.runTest { + val userInfo = adminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = false) + + val defaultValue = Build.TYPE == "eng" + + assertThat(settingEnabled).isEqualTo(defaultValue) + } + } + + @Test + fun adminUser_unrestricted_enabledTracksSetting() = + with(kosmos) { + testScope.runTest { + val userInfo = adminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = false) + + setSettingValue(false) + assertThat(settingEnabled).isFalse() + + setSettingValue(true) + assertThat(settingEnabled).isTrue() + } + } + + @Test + fun adminUser_restricted_neverDevelopmentEnabled() = + with(kosmos) { + testScope.runTest { + val userInfo = adminUserInfo + val settingEnabled by + collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo)) + + setUserRestriction(userInfo.userHandle, restricted = true) + + assertThat(settingEnabled).isFalse() + + setSettingValue(false) + assertThat(settingEnabled).isFalse() + + setSettingValue(true) + assertThat(settingEnabled).isFalse() + } + } + + private companion object { + const val USER_RESTRICTION = UserManager.DISALLOW_DEBUGGING_FEATURES + const val SETTING_NAME = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED + + val adminUserInfo = + UserInfo( + /* id= */ 10, + /* name= */ "", + /* flags */ UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, + ) + val nonAdminUserInfo = + UserInfo(/* id= */ 11, /* name= */ "", /* flags */ UserInfo.FLAG_FULL) + + fun Kosmos.setUserRestriction(userHandle: UserHandle, restricted: Boolean) { + userManager.stub { + on { hasUserRestrictionForUser(eq(USER_RESTRICTION), eq(userHandle)) } doReturn + restricted + } + } + + fun Kosmos.setSettingValue(enabled: Boolean) { + fakeGlobalSettings.putInt(SETTING_NAME, if (enabled) 1 else 0) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt new file mode 100644 index 000000000000..f29dabe98664 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt @@ -0,0 +1,138 @@ +/* + * 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.systemui.development.domain.interactor + +import android.content.ClipData +import android.content.ClipDescription +import android.content.clipboardManager +import android.content.pm.UserInfo +import android.content.res.mainResources +import android.os.Build +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.development.shared.model.BuildNumber +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.settings.fakeGlobalSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +@SmallTest +class BuildNumberInteractorTest : SysuiTestCase() { + + private val kosmos = + testKosmos().apply { + fakeUserRepository.setUserInfos(listOf(adminUserInfo, nonAdminUserInfo)) + } + + private val expectedBuildNumber = + BuildNumber( + kosmos.mainResources.getString( + R.string.bugreport_status, + Build.VERSION.RELEASE_OR_CODENAME, + Build.ID, + ) + ) + + private val clipLabel = + kosmos.mainResources.getString( + com.android.systemui.res.R.string.build_number_clip_data_label + ) + + private val underTest = kosmos.buildNumberInteractor + + @Test + fun nonAdminUser_settingEnabled_buildNumberNull() = + with(kosmos) { + testScope.runTest { + val buildNumber by collectLastValue(underTest.buildNumber) + + fakeUserRepository.setSelectedUserInfo(nonAdminUserInfo) + setSettingValue(true) + + assertThat(buildNumber).isNull() + } + } + + @Test + fun adminUser_buildNumberCorrect_onlyWhenSettingEnabled() = + with(kosmos) { + testScope.runTest { + val buildNumber by collectLastValue(underTest.buildNumber) + + fakeUserRepository.setSelectedUserInfo(adminUserInfo) + + setSettingValue(false) + assertThat(buildNumber).isNull() + + setSettingValue(true) + assertThat(buildNumber).isEqualTo(expectedBuildNumber) + } + } + + @Test + fun copyToClipboard() = + with(kosmos) { + testScope.runTest { + fakeUserRepository.setSelectedUserInfo(adminUserInfo) + + underTest.copyBuildNumber() + runCurrent() + + val argumentCaptor = argumentCaptor<ClipData>() + + verify(clipboardManager).setPrimaryClip(argumentCaptor.capture()) + + with(argumentCaptor.firstValue) { + assertThat(description.label).isEqualTo(clipLabel) + assertThat(description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) + .isTrue() + assertThat(itemCount).isEqualTo(1) + assertThat(getItemAt(0).text).isEqualTo(expectedBuildNumber.value) + } + } + } + + private companion object { + const val SETTING_NAME = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED + + val adminUserInfo = + UserInfo( + /* id= */ 10, + /* name= */ "", + /* flags */ UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, + ) + val nonAdminUserInfo = + UserInfo(/* id= */ 11, /* name= */ "", /* flags */ UserInfo.FLAG_FULL) + + fun Kosmos.setSettingValue(enabled: Boolean) { + fakeGlobalSettings.putInt(SETTING_NAME, if (enabled) 1 else 0) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt index f0d79bb83652..47cba0723804 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt @@ -568,6 +568,41 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isFalse() } + @Test + fun lockNow() = + testScope.runTest { + setLockAfterScreenTimeout(5000) + val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked }) + unlockDevice() + assertThat(isUnlocked).isTrue() + + underTest.lockNow() + runCurrent() + + assertThat(isUnlocked).isFalse() + } + + @Test + fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_fromSleepButton() = + testScope.runTest { + setLockAfterScreenTimeout(5000) + kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + + kosmos.powerInteractor.setAsleepForTest( + sleepReason = PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON + ) + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + } + private fun TestScope.unlockDevice() { val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/RearDisplayStateInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/RearDisplayStateInteractorTest.kt new file mode 100644 index 000000000000..789178728f18 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/RearDisplayStateInteractorTest.kt @@ -0,0 +1,181 @@ +/* + * 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.systemui.display.domain.interactor + +import android.hardware.display.defaultDisplay +import android.hardware.display.rearDisplay +import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.DeviceStateRepository +import com.android.systemui.display.data.repository.FakeDeviceStateRepository +import com.android.systemui.display.data.repository.FakeDisplayRepository +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.whenever + +/** atest RearDisplayStateInteractorTest */ +@RunWith(AndroidJUnit4::class) +@SmallTest +class RearDisplayStateInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val fakeDisplayRepository = FakeDisplayRepository() + private val fakeDeviceStateRepository = FakeDeviceStateRepository() + private val rearDisplayStateInteractor = + RearDisplayStateInteractorImpl( + fakeDisplayRepository, + fakeDeviceStateRepository, + kosmos.testDispatcher, + ) + private val emissionTracker = EmissionTracker(rearDisplayStateInteractor, kosmos.testScope) + + @Before + fun setup() { + whenever(kosmos.rearDisplay.flags).thenReturn(Display.FLAG_REAR) + } + + @Test + fun enableRearDisplayWhenDisplayImmediatelyAvailable() = + kosmos.runTest { + emissionTracker.use { tracker -> + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(0) + fakeDeviceStateRepository.emit( + DeviceStateRepository.DeviceState.REAR_DISPLAY_OUTER_DEFAULT + ) + + assertThat(tracker.enabledCount).isEqualTo(1) + assertThat(tracker.lastDisplay).isEqualTo(kosmos.rearDisplay) + } + } + + @Test + fun enableAndDisableRearDisplay() = + kosmos.runTest { + emissionTracker.use { tracker -> + // The fake FakeDeviceStateRepository will always start with state UNKNOWN, thus + // triggering one initial emission + assertThat(tracker.disabledCount).isEqualTo(1) + + fakeDeviceStateRepository.emit( + DeviceStateRepository.DeviceState.REAR_DISPLAY_OUTER_DEFAULT + ) + + // Adding a non-rear display does not trigger an emission + fakeDisplayRepository.addDisplay(kosmos.defaultDisplay) + assertThat(tracker.enabledCount).isEqualTo(0) + + // Adding a rear display triggers the emission + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(1) + assertThat(tracker.lastDisplay).isEqualTo(kosmos.rearDisplay) + + fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED) + assertThat(tracker.disabledCount).isEqualTo(2) + } + } + + @Test + fun enableRearDisplayShouldOnlyReactToFirstRearDisplay() = + kosmos.runTest { + emissionTracker.use { tracker -> + fakeDeviceStateRepository.emit( + DeviceStateRepository.DeviceState.REAR_DISPLAY_OUTER_DEFAULT + ) + + // Adding a rear display triggers the emission + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(1) + + // Adding additional rear displays does not trigger additional emissions + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(1) + } + } + + @Test + fun rearDisplayAddedWhenNoLongerInRdm() = + kosmos.runTest { + emissionTracker.use { tracker -> + fakeDeviceStateRepository.emit( + DeviceStateRepository.DeviceState.REAR_DISPLAY_OUTER_DEFAULT + ) + fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED) + + // Adding a rear display when no longer in the correct device state does not trigger + // an emission + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(0) + } + } + + @Test + fun rearDisplayDisabledDoesNotSpam() = + kosmos.runTest { + emissionTracker.use { tracker -> + fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED) + assertThat(tracker.disabledCount).isEqualTo(1) + + // No additional emission + fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.FOLDED) + assertThat(tracker.disabledCount).isEqualTo(1) + } + } + + class EmissionTracker(rearDisplayInteractor: RearDisplayStateInteractor, scope: TestScope) : + AutoCloseable { + var enabledCount = 0 + var disabledCount = 0 + var lastDisplay: Display? = null + + val job: Job + + init { + val channel = Channel<RearDisplayStateInteractor.State>(Channel.UNLIMITED) + job = + scope.launch { + rearDisplayInteractor.state.collect { + channel.send(it) + if (it is RearDisplayStateInteractor.State.Enabled) { + enabledCount++ + lastDisplay = it.innerDisplay + } + if (it is RearDisplayStateInteractor.State.Disabled) { + disabledCount++ + } + } + } + } + + override fun close() { + job.cancel() + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt index cc4c7c419a11..27b8c59a076d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt @@ -18,13 +18,17 @@ package com.android.systemui.keyboard.shortcut.data.repository import android.content.Context import android.content.Context.INPUT_SERVICE -import android.hardware.input.InputGestureData -import android.hardware.input.InputGestureData.createKeyTrigger -import android.hardware.input.KeyGestureEvent import android.hardware.input.fakeInputManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags -import android.view.KeyEvent +import android.view.KeyEvent.KEYCODE_SLASH +import android.view.KeyEvent.META_ALT_ON +import android.view.KeyEvent.META_CAPS_LOCK_ON +import android.view.KeyEvent.META_CTRL_ON +import android.view.KeyEvent.META_FUNCTION_ON +import android.view.KeyEvent.META_META_ON +import android.view.KeyEvent.META_SHIFT_ON +import android.view.KeyEvent.META_SYM_ON import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES @@ -32,17 +36,14 @@ import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository -import com.android.systemui.keyboard.shortcut.shared.model.Shortcut -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination +import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.userTracker import com.android.systemui.testKosmos @@ -83,7 +84,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { whenever( fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) ) - .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations) + .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations) helper.toggle(deviceId = 123) val categories by collectLastValue(repo.categories) @@ -100,7 +101,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { whenever( fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) ) - .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations) + .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations) helper.toggle(deviceId = 123) val categories by collectLastValue(repo.categories) @@ -125,167 +126,73 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { } } - private fun simpleInputGestureData( - keyCode: Int = KeyEvent.KEYCODE_A, - modifiers: Int = KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, - keyGestureType: Int, - ): InputGestureData { - val builder = InputGestureData.Builder() - builder.setKeyGestureType(keyGestureType) - builder.setTrigger(createKeyTrigger(keyCode, modifiers)) - return builder.build() + @Test + fun pressedKeys_isEmptyByDefault() { + testScope.runTest { + val pressedKeys by collectLastValue(repo.pressedKeys) + assertThat(pressedKeys).isEmpty() + + helper.toggle(deviceId = 123) + assertThat(pressedKeys).isEmpty() + } } - private fun simpleShortcutCategory( - category: ShortcutCategoryType, - subcategoryLabel: String, - shortcutLabel: String, - ): ShortcutCategory { - return ShortcutCategory( - type = category, - subCategories = - listOf( - ShortcutSubCategory( - label = subcategoryLabel, - shortcuts = listOf(simpleShortcut(shortcutLabel)), - ) - ), - ) + @Test + fun pressedKeys_recognizesAllSupportedModifiers() { + testScope.runTest { + helper.toggle(deviceId = 123) + val pressedKeys by collectLastValue(repo.pressedKeys) + repo.updateUserKeyCombination( + KeyCombination(modifiers = allSupportedModifiers, keyCode = null) + ) + + assertThat(pressedKeys) + .containsExactly( + ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta), + ShortcutKey.Text("Ctrl"), + ShortcutKey.Text("Fn"), + ShortcutKey.Text("Shift"), + ShortcutKey.Text("Alt"), + ShortcutKey.Text("Sym"), + ) + } } - private fun simpleShortcut(label: String) = - Shortcut( - label = label, - commands = - listOf( - ShortcutCommand( - isCustom = true, - keys = - listOf( - ShortcutKey.Text("Ctrl"), - ShortcutKey.Text("Alt"), - ShortcutKey.Text("A"), - ), - ) - ), - ) + @Test + fun pressedKeys_ignoresUnsupportedModifiers() { + testScope.runTest { + helper.toggle(deviceId = 123) + val pressedKeys by collectLastValue(repo.pressedKeys) + repo.updateUserKeyCombination( + KeyCombination(modifiers = META_CAPS_LOCK_ON, keyCode = null) + ) - private val customizableInputGestureWithUnknownKeyGestureType = - // These key gesture events are currently not supported by shortcut helper customizer - listOf( - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS - ), - simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY), - ) + assertThat(pressedKeys).isEmpty() + } + } - private val expectedShortcutCategoriesWithSimpleShortcutCombination = - listOf( - simpleShortcutCategory(System, "System apps", "Open assistant"), - simpleShortcutCategory(System, "System controls", "Go to home screen"), - simpleShortcutCategory(System, "System apps", "Open settings"), - simpleShortcutCategory(System, "System controls", "Lock screen"), - simpleShortcutCategory(System, "System controls", "View notifications"), - simpleShortcutCategory(System, "System apps", "Take a note"), - simpleShortcutCategory(System, "System controls", "Take screenshot"), - simpleShortcutCategory(System, "System controls", "Go back"), - simpleShortcutCategory( - MultiTasking, - "Split screen", - "Switch from split screen to full screen", - ), - simpleShortcutCategory( - MultiTasking, - "Split screen", - "Use split screen with current app on the left", - ), - simpleShortcutCategory( - MultiTasking, - "Split screen", - "Switch to app on left or above while using split screen", - ), - simpleShortcutCategory( - MultiTasking, - "Split screen", - "Use split screen with current app on the right", - ), - simpleShortcutCategory( - MultiTasking, - "Split screen", - "Switch to app on right or below while using split screen", - ), - simpleShortcutCategory(System, "System controls", "Show shortcuts"), - simpleShortcutCategory(System, "System controls", "View recent apps"), - simpleShortcutCategory(AppCategories, "Applications", "Calculator"), - simpleShortcutCategory(AppCategories, "Applications", "Calendar"), - simpleShortcutCategory(AppCategories, "Applications", "Browser"), - simpleShortcutCategory(AppCategories, "Applications", "Contacts"), - simpleShortcutCategory(AppCategories, "Applications", "Email"), - simpleShortcutCategory(AppCategories, "Applications", "Maps"), - simpleShortcutCategory(AppCategories, "Applications", "SMS"), - simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"), - ) + @Test + fun pressedKeys_assertCorrectConversion() { + testScope.runTest { + helper.toggle(deviceId = 123) + val pressedKeys by collectLastValue(repo.pressedKeys) + repo.updateUserKeyCombination( + KeyCombination(modifiers = META_META_ON, keyCode = KEYCODE_SLASH) + ) + + assertThat(pressedKeys) + .containsExactly( + ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta), + ShortcutKey.Text("/"), + ) + } + } - private val customizableInputGesturesWithSimpleShortcutCombinations = - listOf( - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT - ), - simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS - ), - simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL - ), - simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT - ), - simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_BACK), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER - ), - simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER - ), - ) + private val allSupportedModifiers = + META_META_ON or + META_CTRL_ON or + META_FUNCTION_ON or + META_SHIFT_ON or + META_ALT_ON or + META_SYM_ON } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt index c9c39b3ebf66..a1e7ef4ac5a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt @@ -16,11 +16,20 @@ package com.android.systemui.keyboard.shortcut.data.source +import android.hardware.input.InputGestureData +import android.hardware.input.InputGestureData.createKeyTrigger +import android.hardware.input.KeyGestureEvent import android.view.KeyEvent import android.view.KeyboardShortcutGroup import android.view.KeyboardShortcutInfo +import com.android.systemui.keyboard.shortcut.shared.model.Shortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.shared.model.shortcut import com.android.systemui.res.R @@ -64,6 +73,13 @@ object TestShortcuts { } } + private val goHomeShortcutInfo = + KeyboardShortcutInfo( + /* label = */ "Go to home screen", + /* keycode = */ KeyEvent.KEYCODE_B, + /* modifiers = */ KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + ) + private val standardShortcutInfo1 = KeyboardShortcutInfo( /* label = */ "Standard shortcut 1", @@ -79,6 +95,16 @@ object TestShortcuts { } } + private val customGoHomeShortcut = + shortcut("Go to home screen") { + command { + key("Ctrl") + key("Alt") + key("A") + isCustom(true) + } + } + private val standardShortcutInfo2 = KeyboardShortcutInfo( /* label = */ "Standard shortcut 2", @@ -123,35 +149,38 @@ object TestShortcuts { listOf( shortcutInfoWithRepeatedLabel, shortcutInfoWithRepeatedLabelAlternate, - shortcutInfoWithRepeatedLabelSecondAlternate - ) + shortcutInfoWithRepeatedLabelSecondAlternate, + ), ) private val subCategoryWithGroupedRepeatedShortcutLabels = ShortcutSubCategory( label = groupWithRepeatedShortcutLabels.label!!.toString(), - shortcuts = listOf(shortcutWithGroupedRepeatedLabel) + shortcuts = listOf(shortcutWithGroupedRepeatedLabel), ) private val groupWithStandardShortcutInfo = KeyboardShortcutGroup("Standard group", listOf(standardShortcutInfo1)) + val groupWithGoHomeShortcutInfo = + KeyboardShortcutGroup("System controls", listOf(goHomeShortcutInfo)) + private val subCategoryWithStandardShortcut = ShortcutSubCategory( label = groupWithStandardShortcutInfo.label!!.toString(), - shortcuts = listOf(standardShortcut1) + shortcuts = listOf(standardShortcut1), ) private val groupWithOnlyUnsupportedModifierShortcut = KeyboardShortcutGroup( "Group with unsupported modifiers", - listOf(shortcutInfoWithUnsupportedModifiers) + listOf(shortcutInfoWithUnsupportedModifiers), ) private val groupWithSupportedAndUnsupportedModifierShortcut = KeyboardShortcutGroup( "Group with mix of supported and not supported modifiers", - listOf(standardShortcutInfo3, shortcutInfoWithUnsupportedModifiers) + listOf(standardShortcutInfo3, shortcutInfoWithUnsupportedModifiers), ) private val switchToNextLanguageShortcut = @@ -174,19 +203,19 @@ object TestShortcuts { private val subCategoryForInputLanguageSwitchShortcuts = ShortcutSubCategory( "Input", - listOf(switchToNextLanguageShortcut, switchToPreviousLanguageShortcut) + listOf(switchToNextLanguageShortcut, switchToPreviousLanguageShortcut), ) private val subCategoryWithUnsupportedShortcutsRemoved = ShortcutSubCategory( groupWithSupportedAndUnsupportedModifierShortcut.label!!.toString(), - listOf(standardShortcut3) + listOf(standardShortcut3), ) private val standardGroup1 = KeyboardShortcutGroup( "Standard group 1", - listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3) + listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3), ) private val standardPackageName1 = "standard.app.group1" @@ -194,38 +223,63 @@ object TestShortcuts { private val standardAppGroup1 = KeyboardShortcutGroup( "Standard app group 1", - listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3) + listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3), ) .apply { packageName = standardPackageName1 } + private val standardSystemAppSubcategoryWithCustomHomeShortcut = + ShortcutSubCategory("System controls", listOf(customGoHomeShortcut)) + private val standardSubCategory1 = ShortcutSubCategory( standardGroup1.label!!.toString(), - listOf(standardShortcut1, standardShortcut2, standardShortcut3) + listOf(standardShortcut1, standardShortcut2, standardShortcut3), ) private val standardGroup2 = KeyboardShortcutGroup( "Standard group 2", - listOf(standardShortcutInfo3, standardShortcutInfo2, standardShortcutInfo1) + listOf(standardShortcutInfo3, standardShortcutInfo2, standardShortcutInfo1), ) private val standardSubCategory2 = ShortcutSubCategory( standardGroup2.label!!.toString(), - listOf(standardShortcut3, standardShortcut2, standardShortcut1) + listOf(standardShortcut3, standardShortcut2, standardShortcut1), ) private val standardGroup3 = KeyboardShortcutGroup( "Standard group 3", - listOf(standardShortcutInfo2, standardShortcutInfo1) + listOf(standardShortcutInfo2, standardShortcutInfo1), ) private val standardSubCategory3 = ShortcutSubCategory( standardGroup3.label!!.toString(), - listOf(standardShortcut2, standardShortcut1) + listOf(standardShortcut2, standardShortcut1), + ) + + private val systemSubCategoryWithGoHomeShortcuts = + ShortcutSubCategory( + label = "System controls", + shortcuts = + listOf( + shortcut("Go to home screen") { + command { + key("Ctrl") + key("Alt") + key("B") + } + command { + key("Ctrl") + key("Alt") + key("A") + isCustom(true) + } + } + ), ) + val imeGroups = listOf(standardGroup1, standardGroup2, standardGroup3) val imeCategory = ShortcutCategory( @@ -235,8 +289,8 @@ object TestShortcuts { subCategoryForInputLanguageSwitchShortcuts, standardSubCategory1, standardSubCategory2, - standardSubCategory3 - ) + standardSubCategory3, + ), ) val currentAppGroups = listOf(standardAppGroup1) @@ -245,15 +299,33 @@ object TestShortcuts { val systemGroups = listOf(standardGroup3, standardGroup2, standardGroup1) val systemCategory = ShortcutCategory( - type = ShortcutCategoryType.System, - subCategories = listOf(standardSubCategory3, standardSubCategory2, standardSubCategory1) + type = System, + subCategories = listOf(standardSubCategory3, standardSubCategory2, standardSubCategory1), + ) + + val systemCategoryWithMergedGoHomeShortcut = + ShortcutCategory( + type = System, + subCategories = listOf(systemSubCategoryWithGoHomeShortcuts), + ) + + val systemCategoryWithCustomHomeShortcut = + ShortcutCategory( + type = System, + subCategories = + listOf( + standardSubCategory3, + standardSubCategory2, + standardSubCategory1, + standardSystemAppSubcategoryWithCustomHomeShortcut, + ), ) val multitaskingGroups = listOf(standardGroup2, standardGroup1) val multitaskingCategory = ShortcutCategory( - type = ShortcutCategoryType.MultiTasking, - subCategories = listOf(standardSubCategory2, standardSubCategory1) + type = MultiTasking, + subCategories = listOf(standardSubCategory2, standardSubCategory1), ) val groupsWithDuplicateShortcutLabels = @@ -266,14 +338,14 @@ object TestShortcuts { listOf( subCategoryForInputLanguageSwitchShortcuts, subCategoryWithGroupedRepeatedShortcutLabels, - subCategoryWithStandardShortcut + subCategoryWithStandardShortcut, ) val groupsWithUnsupportedModifier = listOf( groupWithStandardShortcutInfo, groupWithOnlyUnsupportedModifierShortcut, - groupWithSupportedAndUnsupportedModifierShortcut + groupWithSupportedAndUnsupportedModifierShortcut, ) val subCategoriesWithUnsupportedModifiersRemoved = @@ -283,8 +355,174 @@ object TestShortcuts { listOf( subCategoryForInputLanguageSwitchShortcuts, subCategoryWithStandardShortcut, - subCategoryWithUnsupportedShortcutsRemoved + subCategoryWithUnsupportedShortcutsRemoved, ) val groupsWithOnlyUnsupportedModifiers = listOf(groupWithOnlyUnsupportedModifierShortcut) + + private fun simpleInputGestureData( + keyCode: Int = KeyEvent.KEYCODE_A, + modifiers: Int = KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + keyGestureType: Int, + ): InputGestureData { + val builder = InputGestureData.Builder() + builder.setKeyGestureType(keyGestureType) + builder.setTrigger(createKeyTrigger(keyCode, modifiers)) + return builder.build() + } + + private fun simpleShortcutCategory( + category: ShortcutCategoryType, + subcategoryLabel: String, + shortcutLabel: String, + ): ShortcutCategory { + return ShortcutCategory( + type = category, + subCategories = + listOf( + ShortcutSubCategory( + label = subcategoryLabel, + shortcuts = listOf(simpleShortcut(shortcutLabel)), + ) + ), + ) + } + + private fun simpleShortcut(label: String) = + Shortcut( + label = label, + commands = + listOf( + ShortcutCommand( + isCustom = true, + keys = + listOf( + ShortcutKey.Text("Ctrl"), + ShortcutKey.Text("Alt"), + ShortcutKey.Text("A"), + ), + ) + ), + ) + + val customizableInputGestureWithUnknownKeyGestureType = + // These key gesture events are currently not supported by shortcut helper customizer + listOf( + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY), + ) + + val expectedShortcutCategoriesWithSimpleShortcutCombination = + listOf( + simpleShortcutCategory(System, "System apps", "Open assistant"), + simpleShortcutCategory(System, "System controls", "Go to home screen"), + simpleShortcutCategory(System, "System apps", "Open settings"), + simpleShortcutCategory(System, "System controls", "Lock screen"), + simpleShortcutCategory(System, "System controls", "View notifications"), + simpleShortcutCategory(System, "System apps", "Take a note"), + simpleShortcutCategory(System, "System controls", "Take screenshot"), + simpleShortcutCategory(System, "System controls", "Go back"), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Switch from split screen to full screen", + ), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Use split screen with current app on the left", + ), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Switch to app on left or above while using split screen", + ), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Use split screen with current app on the right", + ), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Switch to app on right or below while using split screen", + ), + simpleShortcutCategory(System, "System controls", "Show shortcuts"), + simpleShortcutCategory(System, "System controls", "View recent apps"), + simpleShortcutCategory(AppCategories, "Applications", "Calculator"), + simpleShortcutCategory(AppCategories, "Applications", "Calendar"), + simpleShortcutCategory(AppCategories, "Applications", "Browser"), + simpleShortcutCategory(AppCategories, "Applications", "Contacts"), + simpleShortcutCategory(AppCategories, "Applications", "Email"), + simpleShortcutCategory(AppCategories, "Applications", "Maps"), + simpleShortcutCategory(AppCategories, "Applications", "SMS"), + simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"), + ) + val customInputGestureTypeHome = + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + + val allCustomizableInputGesturesWithSimpleShortcutCombinations = + listOf( + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_BACK), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER + ), + ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt index 57c8b444b922..f7c77017e239 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt @@ -16,12 +16,24 @@ package com.android.systemui.keyboard.shortcut.domain.interactor +import android.content.Context +import android.content.Context.INPUT_SERVICE +import android.hardware.input.InputGestureData +import android.hardware.input.fakeInputManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags 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.coroutines.collectLastValue import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customInputGestureTypeHome +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.groupWithGoHomeShortcutInfo +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.systemCategoryWithCustomHomeShortcut +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.systemCategoryWithMergedGoHomeShortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking @@ -34,6 +46,8 @@ import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSourc import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.settings.userTracker import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,13 +56,18 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { + private val mockUserContext: Context = mock() private val systemShortcutsSource = FakeKeyboardShortcutGroupsSource() private val multitaskingShortcutsSource = FakeKeyboardShortcutGroupsSource() + @OptIn(ExperimentalCoroutinesApi::class) private val kosmos = testKosmos().also { @@ -57,17 +76,23 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } + private val fakeInputManager = kosmos.fakeInputManager private val testScope = kosmos.testScope - private val interactor = kosmos.shortcutHelperCategoriesInteractor + private lateinit var interactor: ShortcutHelperCategoriesInteractor private val helper = kosmos.shortcutHelperTestHelper + private val inter by lazy { kosmos.shortcutHelperCategoriesInteractor } @Before fun setShortcuts() { + interactor = kosmos.shortcutHelperCategoriesInteractor helper.setImeShortcuts(TestShortcuts.imeGroups) systemShortcutsSource.setGroups(TestShortcuts.systemGroups) multitaskingShortcutsSource.setGroups(TestShortcuts.multitaskingGroups) + whenever(mockUserContext.getSystemService(INPUT_SERVICE)) + .thenReturn(fakeInputManager.inputManager) } @Test @@ -120,7 +145,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { ShortcutCategory( type = InputMethodEditor, subCategories = - TestShortcuts.imeSubCategoriesWithGroupedDuplicatedShortcutLabels + TestShortcuts.imeSubCategoriesWithGroupedDuplicatedShortcutLabels, ), ) .inOrder() @@ -140,7 +165,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { ShortcutCategory( type = System, subCategories = - TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels + TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels, ), TestShortcuts.multitaskingCategory, TestShortcuts.imeCategory, @@ -163,7 +188,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { ShortcutCategory( type = MultiTasking, subCategories = - TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels + TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels, ), TestShortcuts.imeCategory, ) @@ -185,7 +210,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { ShortcutCategory( type = InputMethodEditor, subCategories = - TestShortcuts.imeSubCategoriesWithUnsupportedModifiersRemoved + TestShortcuts.imeSubCategoriesWithUnsupportedModifiersRemoved, ), ) .inOrder() @@ -203,7 +228,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { .containsExactly( ShortcutCategory( type = System, - subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved + subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved, ), TestShortcuts.multitaskingCategory, TestShortcuts.imeCategory, @@ -224,7 +249,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { TestShortcuts.systemCategory, ShortcutCategory( type = MultiTasking, - subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved + subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved, ), TestShortcuts.imeCategory, ) @@ -240,10 +265,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { helper.showFromActivity() assertThat(categories) - .containsExactly( - TestShortcuts.multitaskingCategory, - TestShortcuts.imeCategory, - ) + .containsExactly(TestShortcuts.multitaskingCategory, TestShortcuts.imeCategory) .inOrder() } @@ -256,10 +278,63 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { helper.showFromActivity() assertThat(categories) + .containsExactly(TestShortcuts.systemCategory, TestShortcuts.imeCategory) + .inOrder() + } + + @Test + @DisableFlags(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER) + fun categories_excludesCustomShortcutsWhenFlagIsOff() { + testScope.runTest { + setCustomInputGestures(allCustomizableInputGesturesWithSimpleShortcutCombinations) + helper.showFromActivity() + val categories by collectLastValue(interactor.shortcutCategories) + assertThat(categories) .containsExactly( TestShortcuts.systemCategory, + TestShortcuts.multitaskingCategory, + TestShortcuts.imeCategory, + ) + } + } + + @Test + @EnableFlags(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER) + fun categories_includesCustomShortcutsWhenFlagIsOn() { + testScope.runTest { + setCustomInputGestures(listOf(customInputGestureTypeHome)) + helper.showFromActivity() + val categories by collectLastValue(interactor.shortcutCategories) + assertThat(categories) + .containsExactly( + systemCategoryWithCustomHomeShortcut, + TestShortcuts.multitaskingCategory, + TestShortcuts.imeCategory, + ) + } + } + + @Test + @EnableFlags(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER) + fun categories_correctlyMergesDefaultAndCustomShortcutsOfSameType() { + testScope.runTest { + setCustomInputGestures(listOf(customInputGestureTypeHome)) + systemShortcutsSource.setGroups(groupWithGoHomeShortcutInfo) + helper.showFromActivity() + + val categories by collectLastValue(interactor.shortcutCategories) + + assertThat(categories) + .containsExactly( + systemCategoryWithMergedGoHomeShortcut, + TestShortcuts.multitaskingCategory, TestShortcuts.imeCategory, ) - .inOrder() } + } + + private fun setCustomInputGestures(customInputGestures: List<InputGestureData>) { + whenever(fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())) + .thenReturn(customInputGestures) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt index 1580ea5c1272..000024f9b814 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt @@ -16,6 +16,9 @@ package com.android.systemui.keyboard.shortcut.ui +import android.content.Context +import android.content.Context.INPUT_SERVICE +import android.hardware.input.fakeInputManager import androidx.test.annotation.UiThreadTest import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -36,6 +39,8 @@ import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.settings.userTracker import com.android.systemui.statusbar.phone.systemUIDialogFactory import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -44,6 +49,8 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -52,7 +59,7 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() { private val fakeSystemSource = FakeKeyboardShortcutGroupsSource() private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource() - + private val mockUserContext: Context = mock() private val kosmos = Kosmos().also { it.testCase = this @@ -62,8 +69,10 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() { it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } + private val inputManager = kosmos.fakeInputManager.inputManager private val testScope = kosmos.testScope private val testHelper = kosmos.shortcutHelperTestHelper private val dialogFactory = kosmos.systemUIDialogFactory @@ -85,6 +94,7 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() { fun setUp() { fakeSystemSource.setGroups(TestShortcuts.systemGroups) fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups) + whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt index 92764ae94271..74a0bafda931 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -17,6 +17,10 @@ package com.android.systemui.keyguard.ui.binder import android.app.IActivityTaskManager +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -25,8 +29,10 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransition import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock +import com.android.window.flags.Flags import com.android.wm.shell.keyguard.KeyguardTransitions import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq @@ -41,6 +47,9 @@ import org.mockito.kotlin.any @RunWith(AndroidJUnit4::class) @kotlinx.coroutines.ExperimentalCoroutinesApi class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { + + @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + private lateinit var underTest: WindowManagerLockscreenVisibilityManager private lateinit var executor: FakeExecutor @@ -68,32 +77,62 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { } @Test - fun testLockscreenVisible_andAodVisible() { + @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun testLockscreenVisible_andAodVisible_without_keyguard_shell_transitions() { underTest.setLockscreenShown(true) - underTest.setAodVisible(true) - verify(activityTaskManagerService).setLockScreenShown(true, false) + underTest.setAodVisible(true) verify(activityTaskManagerService).setLockScreenShown(true, true) + verifyNoMoreInteractions(activityTaskManagerService) } @Test - fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() { + @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun testLockscreenVisible_andAodVisible_with_keyguard_shell_transitions() { underTest.setLockscreenShown(true) + verify(keyguardTransitions).startKeyguardTransition(true, false) underTest.setAodVisible(true) + verify(keyguardTransitions).startKeyguardTransition(true, true) + verifyNoMoreInteractions(keyguardTransitions) + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible_without_keyguard_shell_transitions() { + underTest.setLockscreenShown(true) verify(activityTaskManagerService).setLockScreenShown(true, false) + underTest.setAodVisible(true) verify(activityTaskManagerService).setLockScreenShown(true, true) + verifyNoMoreInteractions(activityTaskManagerService) underTest.setSurfaceBehindVisibility(true) - verify(activityTaskManagerService).keyguardGoingAway(anyInt()) + verifyNoMoreInteractions(activityTaskManagerService) } @Test - fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() { + @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible_with_keyguard_shell_transitions() { + underTest.setLockscreenShown(true) + verify(keyguardTransitions).startKeyguardTransition(true, false) + underTest.setAodVisible(true) + verify(keyguardTransitions).startKeyguardTransition(true, true) + + verifyNoMoreInteractions(keyguardTransitions) + + underTest.setSurfaceBehindVisibility(true) + verify(keyguardTransitions).startKeyguardTransition(false, false) + + verifyNoMoreInteractions(keyguardTransitions) + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway_without_keyguard_shell_transitions() { underTest.setLockscreenShown(false) underTest.setAodVisible(false) @@ -106,7 +145,22 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { } @Test - fun testAodVisible_noLockscreenShownCallYet_doesNotShowLockscreenUntilLater() { + @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway_with_keyguard_shell_transitions() { + underTest.setLockscreenShown(false) + underTest.setAodVisible(false) + + verify(keyguardTransitions).startKeyguardTransition(false, false) + verifyNoMoreInteractions(keyguardTransitions) + + underTest.setSurfaceBehindVisibility(true) + + verifyNoMoreInteractions(keyguardTransitions) + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun testAodVisible_noLockscreenShownCallYet_doesNotShowLockscreenUntilLater_without_keyguard_shell_transitions() { underTest.setAodVisible(false) verifyNoMoreInteractions(activityTaskManagerService) @@ -116,7 +170,19 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { } @Test - fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall() { + @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun testAodVisible_noLockscreenShownCallYet_doesNotShowLockscreenUntilLater_with_keyguard_shell_transitions() { + underTest.setAodVisible(false) + verifyNoMoreInteractions(keyguardTransitions) + + underTest.setLockscreenShown(true) + verify(keyguardTransitions).startKeyguardTransition(true, false) + verifyNoMoreInteractions(activityTaskManagerService) + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall_without_keyguard_shell_transitions() { underTest.setLockscreenShown(true) underTest.setSurfaceBehindVisibility(true) verify(activityTaskManagerService).keyguardGoingAway(0) @@ -126,8 +192,27 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { } @Test - fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility() { + @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall_with_keyguard_shell_transitions() { + underTest.setLockscreenShown(true) + underTest.setSurfaceBehindVisibility(true) + verify(keyguardTransitions).startKeyguardTransition(false, false) + + underTest.setSurfaceBehindVisibility(true) + verifyNoMoreInteractions(keyguardTransitions) + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility_without_keyguard_shell_transitions() { underTest.setSurfaceBehindVisibility(false) verify(activityTaskManagerService).setLockScreenShown(eq(true), any()) } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) + fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility_with_keyguard_shell_transitions() { + underTest.setSurfaceBehindVisibility(false) + verify(keyguardTransitions).startKeyguardTransition(eq(true), any()) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt index d94c97af6f14..c0db95f9e5d2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteract import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel +import com.android.systemui.res.R import com.android.systemui.shared.R as sharedR import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import com.android.systemui.util.mockito.any @@ -68,6 +69,7 @@ class SmartspaceSectionTest : SysuiTestCase() { private val clockShouldBeCentered = MutableStateFlow(false) private val hasCustomWeatherDataDisplay = MutableStateFlow(false) private val isWeatherVisibleFlow = MutableStateFlow(false) + private val isShadeLayoutWide = MutableStateFlow(false) @Before fun setup() { @@ -80,7 +82,7 @@ class SmartspaceSectionTest : SysuiTestCase() { keyguardSmartspaceInteractor, lockscreenSmartspaceController, keyguardUnlockAnimationController, - blueprintInteractor + blueprintInteractor, ) constraintLayout = ConstraintLayout(mContext) whenever(lockscreenSmartspaceController.buildAndConnectView(any())) @@ -93,6 +95,7 @@ class SmartspaceSectionTest : SysuiTestCase() { whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered) whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true) whenever(keyguardSmartspaceViewModel.isWeatherVisible).thenReturn(isWeatherVisibleFlow) + whenever(keyguardSmartspaceViewModel.isShadeLayoutWide).thenReturn(isShadeLayoutWide) constraintSet = ConstraintSet() } @@ -125,6 +128,26 @@ class SmartspaceSectionTest : SysuiTestCase() { } @Test + fun testConstraintsWhenShadeLayoutIsNotWide() { + underTest.addViews(constraintLayout) + underTest.applyConstraints(constraintSet) + + val smartspaceConstraints = constraintSet.getConstraint(smartspaceView.id) + assertThat(smartspaceConstraints.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID) + } + + @Test + fun testConstraintsWhenShadeLayoutIsWide() { + isShadeLayoutWide.value = true + + underTest.addViews(constraintLayout) + underTest.applyConstraints(constraintSet) + + val smartspaceConstraints = constraintSet.getConstraint(smartspaceView.id) + assertThat(smartspaceConstraints.layout.endToEnd).isEqualTo(R.id.split_shade_guideline) + } + + @Test fun testConstraintsWhenNotHasCustomWeatherDataDisplay() { whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true) underTest.addViews(constraintLayout) @@ -160,6 +183,7 @@ class SmartspaceSectionTest : SysuiTestCase() { assertThat(constraintSet.getVisibility(weatherView.id)).isEqualTo(GONE) assertThat(constraintSet.getVisibility(dateView.id)).isEqualTo(VISIBLE) } + @Test fun testCustomDateWeatherVisibility() { hasCustomWeatherDataDisplay.value = true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt index 0c3fcb3ef759..adce9d65cbe0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyguard.data.repository.keyguardSmartspaceRepositor import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.clocks.ClockController +import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -96,4 +97,26 @@ class KeyguardSmartspaceViewModelTest : SysuiTestCase() { assertThat(isWeatherVisible).isEqualTo(false) } + + @Test + fun isShadeLayoutWide_withConfigTrue_true() = + with(kosmos) { + testScope.runTest { + val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide) + shadeRepository.setShadeLayoutWide(true) + + assertThat(isShadeLayoutWide).isTrue() + } + } + + @Test + fun isShadeLayoutWide_withConfigFalse_false() = + with(kosmos) { + testScope.runTest { + val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide) + shadeRepository.setShadeLayoutWide(false) + + assertThat(isShadeLayoutWide).isFalse() + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt index 450aadd70171..ebc00c3897cb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.Intent import android.graphics.drawable.Icon import android.os.UserHandle +import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import kotlinx.coroutines.CoroutineDispatcher @@ -33,14 +34,29 @@ import kotlinx.coroutines.CoroutineDispatcher class FakeNoteTaskBubbleController( unUsed1: Context, unsUsed2: CoroutineDispatcher, - private val optionalBubbles: Optional<Bubbles> + private val optionalBubbles: Optional<Bubbles>, ) : NoteTaskBubblesController(unUsed1, unsUsed2) { override suspend fun areBubblesAvailable() = optionalBubbles.isPresent - override suspend fun showOrHideAppBubble(intent: Intent, userHandle: UserHandle, icon: Icon) { + override suspend fun showOrHideAppBubble( + intent: Intent, + userHandle: UserHandle, + icon: Icon, + bubbleExpandBehavior: NoteTaskBubbleExpandBehavior, + ) { optionalBubbles.ifPresentOrElse( - { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) }, - { throw IllegalAccessException() } + { bubbles -> + if ( + bubbleExpandBehavior == NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED && + bubbles.isBubbleExpanded( + Bubble.getAppBubbleKeyForApp(intent.`package`, userHandle) + ) + ) { + return@ifPresentOrElse + } + bubbles.showOrHideAppBubble(intent, userHandle, icon) + }, + { throw IllegalAccessException() }, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt index 9ef6b9c13315..e55d6ad6c5a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt @@ -21,9 +21,9 @@ import android.graphics.drawable.Icon import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService +import com.android.systemui.res.R import com.android.wm.shell.bubbles.Bubbles import com.google.common.truth.Truth.assertThat import java.util.Optional @@ -33,6 +33,9 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.never +import org.mockito.kotlin.whenever /** atest SystemUITests:NoteTaskBubblesServiceTest */ @SmallTest @@ -61,12 +64,40 @@ internal class NoteTaskBubblesServiceTest : SysuiTestCase() { } @Test - fun showOrHideAppBubble() { + fun showOrHideAppBubble_defaultExpandBehavior_shouldCallBubblesApi() { val intent = Intent() val user = UserHandle.SYSTEM val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) + val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.DEFAULT + whenever(bubbles.isBubbleExpanded(any())).thenReturn(false) + + createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior) + + verify(bubbles).showOrHideAppBubble(intent, user, icon) + } + + @Test + fun showOrHideAppBubble_keepIfExpanded_bubbleShown_shouldNotCallBubblesApi() { + val intent = Intent().apply { setPackage("test") } + val user = UserHandle.SYSTEM + val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) + val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED + whenever(bubbles.isBubbleExpanded(any())).thenReturn(true) + + createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior) + + verify(bubbles, never()).showOrHideAppBubble(intent, user, icon) + } + + @Test + fun showOrHideAppBubble_keepIfExpanded_bubbleNotShown_shouldCallBubblesApi() { + val intent = Intent().apply { setPackage("test") } + val user = UserHandle.SYSTEM + val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) + val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED + whenever(bubbles.isBubbleExpanded(any())).thenReturn(false) - createServiceBinder().showOrHideAppBubble(intent, user, icon) + createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior) verify(bubbles).showOrHideAppBubble(intent, user, icon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt index 8f4078b88fc0..d3578fb9f69b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt @@ -19,6 +19,7 @@ import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.notetask.NoteTaskEntryPoint.QS_NOTES_TILE import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -44,10 +45,19 @@ internal class NoteTaskInfoTest : SysuiTestCase() { } @Test - fun launchMode_keyguardUnlocked_launchModeAppBubble() { + fun launchMode_keyguardUnlocked_launchModeAppBubble_withDefaultExpandBehavior() { val underTest = DEFAULT_INFO.copy(isKeyguardLocked = false) - assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble) + assertThat(underTest.launchMode) + .isEqualTo(NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.DEFAULT)) + } + + @Test + fun launchMode_keyguardUnlocked_qsTileEntryPoint_launchModeAppBubble_withKeepIfExpandedExpandBehavior() { + val underTest = DEFAULT_INFO.copy(isKeyguardLocked = false, entryPoint = QS_NOTES_TILE) + + assertThat(underTest.launchMode) + .isEqualTo(NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED)) } private companion object { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java index 83f95eaf4cda..e9633f49f76d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java @@ -21,9 +21,6 @@ import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.pa import static com.google.common.truth.Truth.assertThat; -import static kotlinx.coroutines.flow.FlowKt.asStateFlow; -import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; - import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -39,6 +36,9 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.flow.FlowKt.asStateFlow; +import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; + import android.content.res.Configuration; import android.content.res.Resources; import android.platform.test.annotations.DisableFlags; @@ -70,9 +70,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.util.animation.DisappearParameters; -import kotlinx.coroutines.flow.MutableStateFlow; -import kotlinx.coroutines.flow.StateFlow; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -88,6 +85,8 @@ import java.util.List; import javax.inject.Provider; +import kotlinx.coroutines.flow.MutableStateFlow; +import kotlinx.coroutines.flow.StateFlow; import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; @@ -232,6 +231,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { @After public void tearDown() { + mController.destroy(); disallowTestableLooperAsMainThread(); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt index 02c5b5ad214c..96f6a622e2f3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -1,10 +1,10 @@ package com.android.systemui.qs import android.content.res.Configuration -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest import android.testing.TestableResources import android.view.ContextThemeWrapper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase @@ -37,8 +37,8 @@ import org.mockito.Mockito.any import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) @@ -81,37 +81,39 @@ class QSPanelControllerTest : SysuiTestCase() { setShouldUseSplitShade(false) whenever(qsPanel.resources).thenReturn(testableResources.resources) whenever(qsPanel.context) - .thenReturn( ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)) + .thenReturn(ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)) whenever(qsPanel.getOrCreateTileLayout()).thenReturn(pagedTileLayout) whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false) whenever(qsPanel.setListening(anyBoolean())).then { whenever(qsPanel.isListening).thenReturn(it.getArgument(0)) } - controller = QSPanelController( - qsPanel, - tunerService, - qsHost, - qsCustomizerController, - /* usingMediaPlayer= */ usingMediaPlayer, - mediaHost, - qsTileRevealControllerFactory, - dumpManager, - metricsLogger, - uiEventLogger, - qsLogger, - brightnessControllerFactory, - brightnessSliderFactory, - falsingManager, - statusBarKeyguardViewManager, - ResourcesSplitShadeStateController(), - longPressEffectProvider, - mediaCarouselInteractor, - ) + controller = + QSPanelController( + qsPanel, + tunerService, + qsHost, + qsCustomizerController, + /* usingMediaPlayer= */ usingMediaPlayer, + mediaHost, + qsTileRevealControllerFactory, + dumpManager, + metricsLogger, + uiEventLogger, + qsLogger, + brightnessControllerFactory, + brightnessSliderFactory, + falsingManager, + statusBarKeyguardViewManager, + ResourcesSplitShadeStateController(), + longPressEffectProvider, + mediaCarouselInteractor, + ) } @After fun tearDown() { + controller.destroy() reset(mediaHost) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 369bb228494c..7880aceb53be 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.leak.RotationUtils +import javax.inject.Provider import org.junit.After import org.junit.Before import org.junit.Test @@ -45,9 +46,8 @@ import org.mockito.Mockito.any import org.mockito.Mockito.reset import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import javax.inject.Provider import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) @@ -108,6 +108,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { @After fun tearDown() { controller.onViewDetached() + controller.destroy() } @Test @@ -184,7 +185,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { dumpManager, ResourcesSplitShadeStateController(), longPressEffectProvider, - mediaCarouselInteractor + mediaCarouselInteractor, ) { private var rotation = RotationUtils.ROTATION_NONE diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index fb7252b24295..60a185537b0d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static android.app.Notification.CATEGORY_CALL; - import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -199,19 +197,6 @@ public class StatusBarIconViewTest extends SysuiTestCase { } @Test - public void testContentDescForNotification_noNotifContent() { - Notification n = new Notification.Builder(mContext, "test") - .setSmallIcon(0) - .setContentTitle("hello") - .setCategory(CATEGORY_CALL) - .build(); - assertThat(NotificationContentDescription.contentDescForNotification(mContext, n) - .toString()).startsWith("com.android.systemui.tests notification"); - assertThat(NotificationContentDescription.contentDescForNotification(mContext, n) - .toString()).doesNotContain("hello"); - } - - @Test @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS}) public void setIcon_withPreloaded_usesPreloaded() { Icon mockIcon = mock(Icon.class); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt index 2e6d0fc847bb..e25bf13c41f2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt @@ -27,7 +27,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -57,7 +57,7 @@ class SettingsProxyTest : SysuiTestCase() { @Before fun setUp() { testScope = TestScope(testDispatcher) - mSettings = FakeSettingsProxy(testDispatcher) + mSettings = FakeSettingsProxy(testScope) mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} } @@ -92,7 +92,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverSync( TEST_SETTING, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) @@ -104,7 +104,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserver( TEST_SETTING, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) @@ -116,7 +116,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverAsync( TEST_SETTING, notifyForDescendants = true, - mContentObserver + mContentObserver, ) testScope.advanceUntilIdle() verify(mSettings.getContentResolver()) @@ -154,7 +154,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverSync( TEST_SETTING_URI, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) @@ -166,7 +166,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserver( TEST_SETTING_URI, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) @@ -178,7 +178,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverAsync( TEST_SETTING_URI, notifyForDescendants = true, - mContentObserver + mContentObserver, ) testScope.advanceUntilIdle() verify(mSettings.getContentResolver()) @@ -202,7 +202,7 @@ class SettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, false, mContentObserver, - it + it, ) } } @@ -382,15 +382,15 @@ class SettingsProxyTest : SysuiTestCase() { assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F) } - private class FakeSettingsProxy(val testDispatcher: CoroutineDispatcher) : SettingsProxy { + private class FakeSettingsProxy(val testScope: CoroutineScope) : SettingsProxy { private val mContentResolver = mock(ContentResolver::class.java) private val settingToValueMap: MutableMap<String, String?> = mutableMapOf() override fun getContentResolver() = mContentResolver - override val backgroundDispatcher: CoroutineDispatcher - get() = testDispatcher + override val settingsScope: CoroutineScope + get() = testScope override fun getUriFor(name: String) = Uri.parse(StringBuilder().append("content://settings/").append(name).toString()) @@ -408,7 +408,7 @@ class SettingsProxyTest : SysuiTestCase() { name: String, value: String?, tag: String?, - makeDefault: Boolean + makeDefault: Boolean, ): Boolean { settingToValueMap[name] = value return true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt index 00b8cd04bdaf..5787f7dfc61f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt @@ -28,7 +28,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.StandardTestDispatcher @@ -36,7 +36,6 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock @@ -51,14 +50,9 @@ class UserSettingsProxyTest : SysuiTestCase() { private var userId = MAIN_USER_ID private val testDispatcher = StandardTestDispatcher() - private var mSettings: UserSettingsProxy = FakeUserSettingsProxy({ userId }, testDispatcher) + private val testScope = TestScope(testDispatcher) + private var mSettings: UserSettingsProxy = FakeUserSettingsProxy({ userId }, testScope) private var mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} - private lateinit var testScope: TestScope - - @Before - fun setUp() { - testScope = TestScope(testDispatcher) - } @Test fun registerContentObserverForUser_inputString_success() = @@ -69,7 +63,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -82,7 +76,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -96,7 +90,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -107,14 +101,14 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING, notifyForDescendants = true, mContentObserver, - userId + userId, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -125,16 +119,14 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING, notifyForDescendants = true, mContentObserver, - userId + userId, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), - eq( - true, - ), + eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -145,7 +137,7 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING, notifyForDescendants = true, mContentObserver, - userId + userId, ) testScope.advanceUntilIdle() verify(mSettings.getContentResolver()) @@ -153,7 +145,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -166,7 +158,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -179,7 +171,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -189,7 +181,7 @@ class UserSettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverForUserAsync( TEST_SETTING_URI, mContentObserver, - userId + userId, ) testScope.advanceUntilIdle() @@ -198,7 +190,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -213,7 +205,7 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, mContentObserver, userId, - runnable + runnable, ) testScope.advanceUntilIdle() assertThat(callbackCalled).isTrue() @@ -226,14 +218,14 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, notifyForDescendants = true, mContentObserver, - userId + userId, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -244,14 +236,14 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, notifyForDescendants = true, mContentObserver, - userId + userId, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -262,7 +254,7 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, notifyForDescendants = true, mContentObserver, - userId + userId, ) testScope.advanceUntilIdle() verify(mSettings.getContentResolver()) @@ -270,7 +262,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -283,7 +275,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } @@ -296,7 +288,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } @@ -309,7 +301,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } } @@ -320,14 +312,14 @@ class UserSettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverSync( TEST_SETTING_URI, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(0) + eq(0), ) } @@ -340,7 +332,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } @@ -354,7 +346,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } } @@ -557,7 +549,7 @@ class UserSettingsProxyTest : SysuiTestCase() { */ private class FakeUserSettingsProxy( override val currentUserProvider: SettingsProxy.CurrentUserIdProvider, - val testDispatcher: CoroutineDispatcher + val testScope: CoroutineScope, ) : UserSettingsProxy { private val mContentResolver = mock(ContentResolver::class.java) @@ -569,8 +561,8 @@ class UserSettingsProxyTest : SysuiTestCase() { override fun getUriFor(name: String) = Uri.parse(StringBuilder().append(URI_PREFIX).append(name).toString()) - override val backgroundDispatcher: CoroutineDispatcher - get() = testDispatcher + override val settingsScope: CoroutineScope + get() = testScope override fun getStringForUser(name: String, userHandle: Int) = userIdToSettingsValueMap[userHandle]?.get(name) ?: "" @@ -578,7 +570,7 @@ class UserSettingsProxyTest : SysuiTestCase() { override fun putString( name: String, value: String?, - overrideableByRestore: Boolean + overrideableByRestore: Boolean, ): Boolean { userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value) return true @@ -588,7 +580,7 @@ class UserSettingsProxyTest : SysuiTestCase() { name: String, value: String?, tag: String?, - makeDefault: Boolean + makeDefault: Boolean, ): Boolean { putStringForUser(name, value, DEFAULT_USER_ID) return true @@ -605,7 +597,7 @@ class UserSettingsProxyTest : SysuiTestCase() { tag: String?, makeDefault: Boolean, userHandle: Int, - overrideableByRestore: Boolean + overrideableByRestore: Boolean, ): Boolean { userIdToSettingsValueMap[userHandle]?.set(name, value) return true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 191486786329..75f3386ed695 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; 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; @@ -39,14 +40,13 @@ import android.media.IAudioService; import android.media.session.MediaSession; import android.os.Handler; import android.os.Process; -import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper; import android.view.accessibility.AccessibilityManager; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.settingslib.flags.Flags; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -64,6 +64,10 @@ import com.android.systemui.util.concurrency.ThreadFactory; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.volume.domain.interactor.AudioSharingInteractor; +import com.android.systemui.volume.domain.interactor.FakeAudioSharingInteractor; +import com.android.systemui.volume.shared.VolumeLogger; + +import kotlinx.coroutines.test.TestScope; import org.junit.Before; import org.junit.Test; @@ -94,6 +98,9 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { private RingerModeLiveData mRingerModeInternalLiveData; private final FakeThreadFactory mThreadFactory = new FakeThreadFactory( new FakeExecutor(new FakeSystemClock())); + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope); + private FakeAudioSharingInteractor mFakeAudioSharingInteractor; @Mock private AudioManager mAudioManager; @Mock @@ -117,9 +124,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock - private AudioSharingInteractor mAudioSharingInteractor; - @Mock - private JavaAdapter mJavaAdapter; + private VolumeLogger mVolumeLogger; @Before @@ -136,6 +141,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { mCallback = mock(VolumeDialogControllerImpl.C.class); mThreadFactory.setLooper(TestableLooper.get(this).getLooper()); + mFakeAudioSharingInteractor = spy(new FakeAudioSharingInteractor()); + mFakeAudioSharingInteractor.setAudioSharingVolumeBarAvailable(true); mVolumeController = new TestableVolumeDialogControllerImpl( mContext, @@ -155,8 +162,9 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { mUserTracker, mDumpManager, mCallback, - mAudioSharingInteractor, - mJavaAdapter); + mFakeAudioSharingInteractor, + mJavaAdapter, + mVolumeLogger); mVolumeController.setEnableDialogs(true, true); } @@ -305,13 +313,13 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { } @Test - @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX) public void testSetStreamVolume_setSecondaryDeviceVolume() { mVolumeController.setStreamVolume( VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST, /* level= */ 100); Objects.requireNonNull(TestableLooper.get(this)).processAllMessages(); + mTestScope.getTestScheduler().advanceUntilIdle(); - verify(mAudioSharingInteractor).setStreamVolume(100); + verify(mFakeAudioSharingInteractor).setStreamVolume(100); } static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl { @@ -336,7 +344,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { DumpManager dumpManager, C callback, AudioSharingInteractor audioSharingInteractor, - JavaAdapter javaAdapter) { + JavaAdapter javaAdapter, + VolumeLogger volumeLogger) { super( context, broadcastDispatcher, @@ -355,7 +364,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { userTracker, dumpManager, audioSharingInteractor, - javaAdapter); + javaAdapter, + volumeLogger); mCallbacks = callback; ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt index 76b7b8f946b4..a9c352df9a26 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt @@ -49,9 +49,10 @@ import com.android.systemui.testKosmos import com.android.systemui.util.RingerModeLiveData import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.concurrency.FakeThreadFactory +import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.time.fakeSystemClock import com.android.systemui.volume.data.repository.audioRepository -import com.android.systemui.volume.domain.interactor.audioSharingInteractor +import com.android.systemui.volume.domain.interactor.FakeAudioSharingInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -76,6 +77,9 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { private val kosmos: Kosmos = testKosmos() private val audioManager: AudioManager = mock {} private val callbacks: VolumeDialogController.Callbacks = mock {} + private val javaAdapter: JavaAdapter = JavaAdapter(kosmos.testScope) + private val fakeAudioSharingInteractor: FakeAudioSharingInteractor = + FakeAudioSharingInteractor() private lateinit var threadFactory: FakeThreadFactory private lateinit var underTest: VolumeDialogControllerImpl @@ -87,6 +91,8 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { threadFactory = FakeThreadFactory(FakeExecutor(fakeSystemClock)).apply { setLooper(looper) } broadcastDispatcherContext = testableContext + fakeAudioSharingInteractor.setAudioSharingVolumeBarAvailable(true) + underTest = VolumeDialogControllerImpl( applicationContext, @@ -108,7 +114,8 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { activityManager, mock { on { userContext }.thenReturn(applicationContext) }, dumpManager, - audioSharingInteractor, + fakeAudioSharingInteractor, + javaAdapter, mock {}, ) .apply { @@ -129,6 +136,7 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { }, ) testableLooper.processAllMessages() + testScheduler.advanceUntilIdle() verify(callbacks) { 1 * { onStateChanged(any()) } } } @@ -163,6 +171,7 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { emitVolumeChange(AudioManager.STREAM_SYSTEM, AudioManager.FLAG_SHOW_UI) runCurrent() TestableLooper.get(this@VolumeDialogControllerImplTestKt).processAllMessages() + testScheduler.advanceUntilIdle() verify(callbacks) { 1 * { onShowRequested(any(), any(), any()) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt index 7c5a48728a11..3f995c69c32f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt @@ -155,6 +155,30 @@ class VolumeDialogSlidersInteractorTest : SysuiTestCase() { } } + @Test + fun activeStreamChanges_showBoth() { + with(kosmos) { + testScope.runTest { + runCurrent() + fakeVolumeDialogController.updateState { + activeStream = AudioManager.STREAM_SYSTEM + states.put(AudioManager.STREAM_MUSIC, buildStreamState()) + states.put(AudioManager.STREAM_SYSTEM, buildStreamState()) + } + val slidersModel by collectLastValue(underTest.sliders) + runCurrent() + + fakeVolumeDialogController.updateState { activeStream = AudioManager.STREAM_MUSIC } + runCurrent() + + assertThat(slidersModel!!.slider) + .isEqualTo(VolumeDialogSliderType.Stream(AudioManager.STREAM_MUSIC)) + assertThat(slidersModel!!.floatingSliders) + .containsExactly(VolumeDialogSliderType.Stream(AudioManager.STREAM_SYSTEM)) + } + } + } + private fun buildStreamState( build: VolumeDialogController.StreamState.() -> Unit = {} ): VolumeDialogController.StreamState { diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java index d68501f69bc0..6b909529e33a 100644 --- a/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java @@ -25,7 +25,7 @@ import android.provider.Settings; import com.android.systemui.util.settings.SettingsSingleThreadBackground; -import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; import javax.inject.Inject; @@ -33,13 +33,13 @@ import javax.inject.Inject; @SuppressLint("StaticSettingsProvider") class GlobalSettingsImpl implements GlobalSettings { private final ContentResolver mContentResolver; - private final CoroutineDispatcher mBgDispatcher; + private final CoroutineScope mSettingsScope; @Inject GlobalSettingsImpl(ContentResolver contentResolver, - @SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) { + @SettingsSingleThreadBackground CoroutineScope settingsScope) { mContentResolver = contentResolver; - mBgDispatcher = bgDispatcher; + mSettingsScope = settingsScope; } @NonNull @@ -56,8 +56,8 @@ class GlobalSettingsImpl implements GlobalSettings { @NonNull @Override - public CoroutineDispatcher getBackgroundDispatcher() { - return mBgDispatcher; + public CoroutineScope getSettingsScope() { + return mSettingsScope; } @Override diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java index 211a6f48e475..ae89a5f021c2 100644 --- a/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java @@ -23,23 +23,23 @@ import android.provider.Settings; import com.android.systemui.util.settings.SettingsSingleThreadBackground; -import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; import javax.inject.Inject; class SecureSettingsImpl implements SecureSettings { private final ContentResolver mContentResolver; private final CurrentUserIdProvider mCurrentUserProvider; - private final CoroutineDispatcher mBgDispatcher; + private final CoroutineScope mSettingsScope; @Inject SecureSettingsImpl( ContentResolver contentResolver, CurrentUserIdProvider currentUserProvider, - @SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) { + @SettingsSingleThreadBackground CoroutineScope settingsScope) { mContentResolver = contentResolver; mCurrentUserProvider = currentUserProvider; - mBgDispatcher = bgDispatcher; + mSettingsScope = settingsScope; } @NonNull @@ -62,8 +62,8 @@ class SecureSettingsImpl implements SecureSettings { @NonNull @Override - public CoroutineDispatcher getBackgroundDispatcher() { - return mBgDispatcher; + public CoroutineScope getSettingsScope() { + return mSettingsScope; } @Override diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt index 5d0b0d55e1f6..154d3cc9eda1 100644 --- a/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt @@ -23,10 +23,12 @@ import android.provider.Settings.SettingNotFoundException import androidx.annotation.AnyThread import androidx.annotation.WorkerThread import com.android.app.tracing.TraceUtils.trace -import com.android.systemui.coroutines.newTracingContext +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.app.tracing.coroutines.nameCoroutine +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext /** @@ -47,11 +49,14 @@ interface SettingsProxy { /** Returns the [ContentResolver] this instance was constructed with. */ fun getContentResolver(): ContentResolver - /** - * Returns the background [CoroutineDispatcher] that the async APIs will use for a specific - * implementation. - */ - val backgroundDispatcher: CoroutineDispatcher + /** Returns the [CoroutineScope] that the async APIs will use. */ + val settingsScope: CoroutineScope + + @OptIn(ExperimentalStdlibApi::class) + fun settingsDispatcherContext(name: String): CoroutineContext { + return (settingsScope.coroutineContext[CoroutineDispatcher] ?: EmptyCoroutineContext) + + nameCoroutine(name) + } /** * Construct the content URI for a particular name/value pair, useful for monitoring changes @@ -82,7 +87,7 @@ interface SettingsProxy { * wish to synchronize execution. */ suspend fun registerContentObserver(name: String, settingsObserver: ContentObserver) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-A")) { registerContentObserverSync(getUriFor(name), settingsObserver) } } @@ -94,7 +99,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-A")).launch { + settingsScope.launch("registerContentObserverAsync-A") { registerContentObserverSync(getUriFor(name), settingsObserver) } @@ -111,7 +116,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-B")).launch { + settingsScope.launch("registerContentObserverAsync-B") { registerContentObserverSync(getUriFor(name), settingsObserver) registered.run() } @@ -134,7 +139,9 @@ interface SettingsProxy { * wish to synchronize execution. */ suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { - withContext(backgroundDispatcher) { registerContentObserverSync(uri, settingsObserver) } + withContext(settingsDispatcherContext("registerContentObserver-B")) { + registerContentObserverSync(uri, settingsObserver) + } } /** @@ -144,7 +151,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-C")).launch { + settingsScope.launch("registerContentObserverAsync-C") { registerContentObserverSync(uri, settingsObserver) } @@ -161,7 +168,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-D")).launch { + settingsScope.launch("registerContentObserverAsync-D") { registerContentObserverSync(uri, settingsObserver) registered.run() } @@ -188,9 +195,9 @@ interface SettingsProxy { suspend fun registerContentObserver( name: String, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-C")) { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) } } @@ -206,7 +213,7 @@ interface SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-E")).launch { + settingsScope.launch("registerContentObserverAsync-E") { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) } @@ -224,7 +231,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-F")).launch { + settingsScope.launch("registerContentObserverAsync-F") { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) registered.run() } @@ -239,7 +246,7 @@ interface SettingsProxy { fun registerContentObserverSync( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { trace({ "SP#registerObserver#[$uri]" }) { getContentResolver() @@ -257,9 +264,9 @@ interface SettingsProxy { suspend fun registerContentObserver( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-D")) { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) } } @@ -275,7 +282,7 @@ interface SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-G")).launch { + settingsScope.launch("registerContentObserverAsync-G") { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) } @@ -293,7 +300,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-H")).launch { + settingsScope.launch("registerContentObserverAsync-H") { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) registered.run() } @@ -319,7 +326,9 @@ interface SettingsProxy { * async block if they wish to synchronize execution. */ suspend fun unregisterContentObserver(settingsObserver: ContentObserver) { - withContext(backgroundDispatcher) { unregisterContentObserverSync(settingsObserver) } + withContext(settingsDispatcherContext("unregisterContentObserver")) { + unregisterContentObserverSync(settingsObserver) + } } /** @@ -330,7 +339,7 @@ interface SettingsProxy { */ @AnyThread fun unregisterContentObserverAsync(settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-I")).launch { + settingsScope.launch("unregisterContentObserverAsync") { unregisterContentObserver(settingsObserver) } diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java index 1b3f74e0c983..65d1c276cbfa 100644 --- a/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java @@ -23,22 +23,22 @@ import android.provider.Settings; import com.android.systemui.util.settings.SettingsSingleThreadBackground; -import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; import javax.inject.Inject; class SystemSettingsImpl implements SystemSettings { private final ContentResolver mContentResolver; private final CurrentUserIdProvider mCurrentUserProvider; - private final CoroutineDispatcher mBgCoroutineDispatcher; + private final CoroutineScope mSettingsScope; @Inject SystemSettingsImpl(ContentResolver contentResolver, CurrentUserIdProvider currentUserProvider, - @SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) { + @SettingsSingleThreadBackground CoroutineScope settingsScope) { mContentResolver = contentResolver; mCurrentUserProvider = currentUserProvider; - mBgCoroutineDispatcher = bgDispatcher; + mSettingsScope = settingsScope; } @NonNull @@ -61,8 +61,8 @@ class SystemSettingsImpl implements SystemSettings { @NonNull @Override - public CoroutineDispatcher getBackgroundDispatcher() { - return mBgCoroutineDispatcher; + public CoroutineScope getSettingsScope() { + return mSettingsScope; } @Override diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt index 4b03df6b0070..1a5517059ca4 100644 --- a/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -23,13 +23,11 @@ import android.net.Uri import android.os.UserHandle import android.provider.Settings.SettingNotFoundException import com.android.app.tracing.TraceUtils.trace -import com.android.systemui.coroutines.newTracingContext +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault -import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext /** @@ -73,13 +71,13 @@ interface UserSettingsProxy : SettingsProxy { } override suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-A")) { registerContentObserverForUserSync(uri, settingsObserver, userId) } } override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-A")).launch { + settingsScope.launch("registerContentObserverAsync-A") { registerContentObserverForUserSync(uri, settingsObserver, userId) } @@ -96,9 +94,9 @@ interface UserSettingsProxy : SettingsProxy { override suspend fun registerContentObserver( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-B")) { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } } @@ -113,7 +111,7 @@ interface UserSettingsProxy : SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-B")).launch { + settingsScope.launch("registerContentObserverAsync-B") { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } @@ -126,7 +124,7 @@ interface UserSettingsProxy : SettingsProxy { fun registerContentObserverForUserSync( name: String, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } @@ -141,9 +139,9 @@ interface UserSettingsProxy : SettingsProxy { suspend fun registerContentObserverForUser( name: String, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserverForUser-A")) { registerContentObserverForUserSync(name, settingsObserver, userHandle) } } @@ -158,7 +156,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-C")).launch { + settingsScope.launch("registerContentObserverForUserAsync-A") { registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } @@ -167,7 +165,7 @@ interface UserSettingsProxy : SettingsProxy { fun registerContentObserverForUserSync( uri: Uri, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { registerContentObserverForUserSync(uri, false, settingsObserver, userHandle) } @@ -182,9 +180,9 @@ interface UserSettingsProxy : SettingsProxy { suspend fun registerContentObserverForUser( uri: Uri, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserverForUser-B")) { registerContentObserverForUserSync(uri, settingsObserver, userHandle) } } @@ -199,7 +197,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-D")).launch { + settingsScope.launch("registerContentObserverForUserAsync-B") { registerContentObserverForUserSync(uri, settingsObserver, userHandle) } @@ -216,7 +214,7 @@ interface UserSettingsProxy : SettingsProxy { userHandle: Int, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-E")).launch { + settingsScope.launch("registerContentObserverForUserAsync-C") { registerContentObserverForUserSync(uri, settingsObserver, userHandle) registered.run() } @@ -231,13 +229,13 @@ interface UserSettingsProxy : SettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { registerContentObserverForUserSync( getUriFor(name), notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } @@ -252,14 +250,14 @@ interface UserSettingsProxy : SettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserverForUser-C")) { registerContentObserverForUserSync( name, notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } } @@ -275,7 +273,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int, ) { - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-F")).launch { + settingsScope.launch("registerContentObserverForUserAsync-D") { registerContentObserverForUserSync( getUriFor(name), notifyForDescendants, @@ -291,7 +289,7 @@ interface UserSettingsProxy : SettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { trace({ "USP#registerObserver#[$uri]" }) { getContentResolver() @@ -299,7 +297,7 @@ interface UserSettingsProxy : SettingsProxy { uri, notifyForDescendants, settingsObserver, - getRealUserHandle(userHandle) + getRealUserHandle(userHandle), ) Unit } @@ -316,14 +314,14 @@ interface UserSettingsProxy : SettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserverForUser-D")) { registerContentObserverForUserSync( uri, notifyForDescendants, settingsObserver, - getRealUserHandle(userHandle) + getRealUserHandle(userHandle), ) } } @@ -339,7 +337,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-G")).launch { + settingsScope.launch("registerContentObserverForUserAsync-E") { registerContentObserverForUserSync( uri, notifyForDescendants, @@ -385,7 +383,7 @@ interface UserSettingsProxy : SettingsProxy { tag: String?, makeDefault: Boolean, @UserIdInt userHandle: Int, - overrideableByRestore: Boolean + overrideableByRestore: Boolean, ): Boolean override fun getInt(name: String, default: Int): Int { diff --git a/packages/SystemUI/res/layout/activity_rear_display_front_screen_on.xml b/packages/SystemUI/res/layout/activity_rear_display_front_screen_on.xml new file mode 100644 index 000000000000..a8d4d2ece07f --- /dev/null +++ b/packages/SystemUI/res/layout/activity_rear_display_front_screen_on.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + android:paddingTop="@dimen/dialog_top_padding" + android:paddingBottom="@dimen/dialog_bottom_padding"> + + <androidx.cardview.widget.CardView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:cardElevation="0dp" + app:cardCornerRadius="28dp" + app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color"> + + <com.android.systemui.reardisplay.RearDisplayEducationLottieViewWrapper + android:id="@+id/rear_display_folded_animation" + android:importantForAccessibility="no" + android:layout_width="@dimen/rear_display_animation_width_opened" + android:layout_height="@dimen/rear_display_animation_height_opened" + android:layout_gravity="center" + android:contentDescription="@string/rear_display_accessibility_unfolded_animation" + android:scaleType="fitXY" + app:lottie_rawRes="@raw/rear_display_turnaround" + app:lottie_autoPlay="true" + app:lottie_repeatMode="reverse"/> + </androidx.cardview.widget.CardView> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/rear_display_unfolded_front_screen_on" + android:textAppearance="@style/TextAppearance.Dialog.Title" + android:lineSpacingExtra="2sp" + android:translationY="-1.24sp" + android:gravity="center_horizontal" /> + + <!-- Buttons --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginTop="36dp"> + <Space + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1"/> + <TextView + android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:layout_gravity="start" + android:text="@string/cancel" + style="@style/Widget.Dialog.Button.BorderButton" /> + </LinearLayout> + +</LinearLayout> diff --git a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml index a313833e2a66..4b2f45f87290 100644 --- a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml +++ b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml @@ -27,12 +27,13 @@ android:layout_height="@dimen/conversation_single_line_face_pile_size" android:paddingHorizontal="16dp" > - <ImageView + <com.android.internal.widget.CachingIconView android:id="@*android:id/conversation_icon" android:layout_width="@dimen/conversation_single_line_avatar_size" android:layout_height="@dimen/conversation_single_line_avatar_size" android:layout_gravity="center_vertical|end" - /> + android:scaleType="centerCrop" + /> <ViewStub android:id="@*android:id/conversation_face_pile" diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid.xml b/packages/SystemUI/res/layout/notification_2025_hybrid.xml new file mode 100644 index 000000000000..8c34cd4165e0 --- /dev/null +++ b/packages/SystemUI/res/layout/notification_2025_hybrid.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<!-- extends LinearLayout --> +<com.android.systemui.statusbar.notification.row.HybridNotificationView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="bottom|start" + android:paddingStart="@*android:dimen/notification_2025_content_margin_start" + android:paddingEnd="12dp"> + <TextView + android:id="@+id/notification_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" + android:paddingEnd="4dp" + /> + <TextView + android:id="@+id/notification_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:paddingEnd="4dp" + style="@*android:style/Widget.DeviceDefault.Notification.Text" + /> +</com.android.systemui.statusbar.notification.row.HybridNotificationView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml new file mode 100644 index 000000000000..ff5d9d3567f6 --- /dev/null +++ b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<!-- extends LinearLayout --> +<com.android.systemui.statusbar.notification.row.HybridConversationNotificationView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical|start" + android:paddingStart="@*android:dimen/notification_2025_content_margin_start" + android:paddingEnd="12dp"> + + <FrameLayout + android:layout_width="@dimen/notification_2025_single_line_face_pile_size" + android:layout_height="@dimen/notification_2025_single_line_face_pile_size" + android:layout_marginEnd="8dp" + > + <ImageView + android:id="@*android:id/conversation_icon" + android:layout_width="@dimen/notification_2025_single_line_avatar_size" + android:layout_height="@dimen/notification_2025_single_line_avatar_size" + android:layout_gravity="center_vertical|end" + /> + + <ViewStub + android:id="@*android:id/conversation_face_pile" + android:layout="@*android:layout/conversation_face_pile_layout" + android:layout_width="@dimen/notification_2025_single_line_face_pile_size" + android:layout_height="@dimen/notification_2025_single_line_face_pile_size" + android:layout_gravity="center_vertical|end" + /> + </FrameLayout> + + <TextView + android:id="@+id/notification_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:paddingEnd="4dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" + /> + + <TextView + android:id="@+id/conversation_notification_sender" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:paddingEnd="4dp" + style="@*android:style/Widget.DeviceDefault.Notification.Text" + /> + + <TextView + android:id="@+id/notification_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:paddingEnd="4dp" + style="@*android:style/Widget.DeviceDefault.Notification.Text" + /> +</com.android.systemui.statusbar.notification.row.HybridConversationNotificationView> diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml index d0a1ce8ae629..215e4e457060 100644 --- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml @@ -55,9 +55,14 @@ /> <!-- Shows generic text. --> + <!-- Since there's so little room in the status bar chip area, don't ellipsize the text and + instead just fade it out a bit at the end. --> <TextView android:id="@+id/ongoing_activity_chip_text" style="@style/StatusBar.Chip.Text" + android:ellipsize="none" + android:requiresFadingEdge="horizontal" + android:fadingEdgeLength="@dimen/ongoing_activity_chip_text_fading_edge_length" android:visibility="gone" /> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 235015b5286f..70ae5c1832af 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -91,7 +91,6 @@ <dimen name="notification_blocker_channel_list_height">128dp</dimen> <dimen name="keyguard_indication_margin_bottom">8dp</dimen> - <dimen name="lock_icon_margin_bottom">24dp</dimen> <!-- Keyboard shortcuts helper --> <dimen name="ksh_container_horizontal_margin">48dp</dimen> diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index 75bee9f9266a..055c3a641d1b 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -20,7 +20,6 @@ <!-- keyguard--> <dimen name="keyguard_indication_margin_bottom">25dp</dimen> <dimen name="ambient_indication_margin_bottom">115dp</dimen> - <dimen name="lock_icon_margin_bottom">60dp</dimen> <!-- margin from keyguard status bar to clock. For split shade it should be keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp --> <dimen name="keyguard_clock_top_margin">8dp</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4954f9122788..813bb9c52aeb 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -788,6 +788,18 @@ <!-- Size of an avatar shown on one-line (children of a group) conversation notifications --> <dimen name="conversation_single_line_avatar_size">24dp</dimen> + <!-- Size of the face pile shown on one-line (children of a group) conversation notifications + (2025 redesign version) --> + <dimen name="notification_2025_single_line_face_pile_size">16dp</dimen> + + <!-- Size of the avatars within a face pile shown on one-line (children of a group) conversation + notifications (2025 redesign version) --> + <dimen name="notification_2025_single_line_face_pile_avatar_size">11dp</dimen> + + <!-- Size of an avatar shown on one-line (children of a group) conversation notifications + (2025 redesign version) --> + <dimen name="notification_2025_single_line_avatar_size">16dp</dimen> + <!-- Border width for avatars in the face pile shown on one-line (children of a group) conversation notifications --> <dimen name="conversation_single_line_face_pile_protection_width">1dp</dimen> @@ -936,7 +948,6 @@ <dimen name="keyguard_translate_distance_on_swipe_up">-200dp</dimen> <dimen name="keyguard_indication_margin_bottom">32dp</dimen> - <dimen name="lock_icon_margin_bottom">74dp</dimen> <dimen name="ambient_indication_margin_bottom">71dp</dimen> @@ -1749,6 +1760,7 @@ <dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen> <!-- The end padding for the timer text view. Only used if an embedded padding icon is used. --> <dimen name="ongoing_activity_chip_text_end_padding_for_embedded_padding_icon">6dp</dimen> + <dimen name="ongoing_activity_chip_text_fading_edge_length">12dp</dimen> <dimen name="ongoing_activity_chip_corner_radius">28dp</dimen> <!-- Status bar user chip --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b45aaddef183..c3f4222a5eb8 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3214,6 +3214,9 @@ <!-- Text to display when copying the build number off QS [CHAR LIMIT=NONE]--> <string name="build_number_copy_toast">Build number copied to clipboard.</string> + <!-- Text for accessibility action for copying content to clipboard [CHAR LIMIT=NONE]--> + <string name="copy_to_clipboard_a11y_action">copy to clipboard.</string> + <!-- Status for conversation without interaction data [CHAR LIMIT=120] --> <string name="basic_status">Open conversation</string> <!--Title text for Conversation widget set up screen [CHAR LIMIT=180] --> @@ -3568,6 +3571,8 @@ <string name="rear_display_accessibility_folded_animation">Foldable device being unfolded</string> <!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] --> <string name="rear_display_accessibility_unfolded_animation">Foldable device being flipped around</string> + <!-- Text for a dialog telling the user that the front screen is turned on. [CHAR_LIMIT=NONE] --> + <string name="rear_display_unfolded_front_screen_on">Front screen turned on</string> <!-- QuickSettings: Additional label for the auto-rotation quicksettings tile indicating that the setting corresponds to the folded posture for a foldable device [CHAR LIMIT=32] --> <string name="quick_settings_rotation_posture_folded">folded</string> diff --git a/packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt b/packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt new file mode 100644 index 000000000000..a8fa9797ebfd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt @@ -0,0 +1,79 @@ +/* + * 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.systemui.development.data.repository + +import android.content.pm.UserInfo +import android.os.Build +import android.os.UserManager +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.kotlin.emitOnStart +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +@SysUISingleton +class DevelopmentSettingRepository +@Inject +constructor( + private val globalSettings: GlobalSettings, + private val userManager: UserManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + private val settingFlow = globalSettings.observerFlow(SETTING) + + /** + * Indicates whether development settings is enabled for this user. The conditions are: + * * Setting is enabled (defaults to true in eng builds) + * * User is an admin + * * User is not restricted from Debugging features. + */ + fun isDevelopmentSettingEnabled(userInfo: UserInfo): Flow<Boolean> { + return settingFlow + .emitOnStart() + .map { checkDevelopmentSettingEnabled(userInfo) } + .flowOn(backgroundDispatcher) + } + + private suspend fun checkDevelopmentSettingEnabled(userInfo: UserInfo): Boolean { + val hasUserRestriction = + withContext(backgroundDispatcher) { + userManager.hasUserRestrictionForUser( + UserManager.DISALLOW_DEBUGGING_FEATURES, + userInfo.userHandle, + ) + } + val isSettingEnabled = + withContext(backgroundDispatcher) { + globalSettings.getInt(SETTING, DEFAULT_ENABLED) != 0 + } + val isAdmin = userInfo.isAdmin + return isAdmin && !hasUserRestriction && isSettingEnabled + } + + private companion object { + val DEFAULT_ENABLED = if (Build.TYPE == "eng") 1 else 0 + + const val SETTING = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED + } +} diff --git a/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt new file mode 100644 index 000000000000..4d786fe76bc1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt @@ -0,0 +1,89 @@ +/* + * 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.systemui.development.domain.interactor + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.res.Resources +import android.os.Build +import android.os.UserHandle +import com.android.internal.R as InternalR +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.development.data.repository.DevelopmentSettingRepository +import com.android.systemui.development.shared.model.BuildNumber +import com.android.systemui.res.R as SystemUIR +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.user.utils.UserScopedService +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class BuildNumberInteractor +@Inject +constructor( + repository: DevelopmentSettingRepository, + @Main resources: Resources, + private val userRepository: UserRepository, + private val clipboardManagerProvider: UserScopedService<ClipboardManager>, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + + /** + * Build number, or `null` if Development Settings is not enabled for the current user. + * + * @see DevelopmentSettingRepository.isDevelopmentSettingEnabled + */ + val buildNumber: Flow<BuildNumber?> = + userRepository.selectedUserInfo + .flatMapConcat { userInfo -> repository.isDevelopmentSettingEnabled(userInfo) } + .map { enabled -> buildText.takeIf { enabled } } + + private val buildText = + BuildNumber( + resources.getString( + InternalR.string.bugreport_status, + Build.VERSION.RELEASE_OR_CODENAME, + Build.ID, + ) + ) + + private val clipLabel = resources.getString(SystemUIR.string.build_number_clip_data_label) + + private val currentUserHandle: UserHandle + get() = userRepository.getSelectedUserInfo().userHandle + + /** + * Copy to the clipboard the build number for the current user. + * + * This can be performed regardless of the current user having Development Settings enabled + */ + suspend fun copyBuildNumber() { + withContext(backgroundDispatcher) { + clipboardManagerProvider + .forUser(currentUserHandle) + .setPrimaryClip(ClipData.newPlainText(clipLabel, buildText.value)) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt b/packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt new file mode 100644 index 000000000000..5bd713fc4840 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt @@ -0,0 +1,19 @@ +/* + * 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.systemui.development.shared.model + +@JvmInline value class BuildNumber(val value: String) diff --git a/packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt b/packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt new file mode 100644 index 000000000000..72e1cede0002 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt @@ -0,0 +1,81 @@ +/* + * 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.systemui.development.ui.compose + +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.Text +import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.onLongClick +import androidx.compose.ui.semantics.semantics +import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture +import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel +import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.res.R + +@Composable +fun BuildNumber( + viewModelFactory: BuildNumberViewModel.Factory, + textColor: Color, + modifier: Modifier = Modifier, +) { + val viewModel = rememberViewModel(traceName = "BuildNumber") { viewModelFactory.create() } + + val buildNumber = viewModel.buildNumber + + if (buildNumber != null) { + val haptics = LocalHapticFeedback.current + val copyToClipboardActionLabel = stringResource(id = R.string.copy_to_clipboard_a11y_action) + + Text( + text = buildNumber.value, + modifier = + modifier + .focusable() + .wrapContentWidth() + // Using this instead of combinedClickable because this node should not support + // single click + .pointerInput(Unit) { + detectLongPressGesture { + haptics.performHapticFeedback(HapticFeedbackType.LongPress) + viewModel.onBuildNumberLongPress() + } + } + .semantics { + onLongClick(copyToClipboardActionLabel) { + viewModel.onBuildNumberLongPress() + true + } + } + .basicMarquee(iterations = 1, initialDelayMillis = 2000) + .minimumInteractiveComponentSize(), + color = textColor, + maxLines = 1, + ) + } else { + Spacer(modifier) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt new file mode 100644 index 000000000000..68c51ea80ffd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt @@ -0,0 +1,66 @@ +/* + * 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.systemui.development.ui.viewmodel + +import androidx.compose.runtime.getValue +import com.android.systemui.development.domain.interactor.BuildNumberInteractor +import com.android.systemui.development.shared.model.BuildNumber +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +/** View model for UI that (optionally) shows the build number and copies it on long press. */ +class BuildNumberViewModel +@AssistedInject +constructor(private val buildNumberInteractor: BuildNumberInteractor) : ExclusiveActivatable() { + + private val hydrator = Hydrator("BuildNumberViewModel") + + private val copyRequests = Channel<Unit>() + + val buildNumber: BuildNumber? by + hydrator.hydratedStateOf( + traceName = "buildNumber", + initialValue = null, + source = buildNumberInteractor.buildNumber, + ) + + fun onBuildNumberLongPress() { + copyRequests.trySend(Unit) + } + + override suspend fun onActivated(): Nothing { + coroutineScope { + launch { hydrator.activate() } + launch { + copyRequests.receiveAsFlow().collect { buildNumberInteractor.copyBuildNumber() } + } + awaitCancellation() + } + } + + @AssistedFactory + interface Factory { + fun create(): BuildNumberViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 1a5654144b65..400d09742d13 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.deviceentry.domain.interactor +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.policy.IKeyguardDismissCallback import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel @@ -43,7 +44,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch /** * Hosts application business logic related to device entry. @@ -174,6 +174,14 @@ constructor( } /** + * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically + * dismissed once the authentication challenge is completed. For example, completing a biometric + * authentication challenge via face unlock or fingerprint sensor can automatically bypass the + * lockscreen. + */ + val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled + + /** * Attempt to enter the device and dismiss the lockscreen. If authentication is required to * unlock the device it will transition to bouncer. * @@ -238,11 +246,8 @@ constructor( isLockscreenEnabled() } - /** - * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically - * dismissed once the authentication challenge is completed. For example, completing a biometric - * authentication challenge via face unlock or fingerprint sensor can automatically bypass the - * lockscreen. - */ - val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled + /** Locks the device instantly. */ + fun lockNow() { + deviceUnlockedInteractor.lockNow() + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt index 35eed5e6a6d9..7d684cab39f7 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt @@ -43,6 +43,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -57,6 +58,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch @OptIn(ExperimentalCoroutinesApi::class) @@ -178,6 +180,8 @@ constructor( val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> = repository.deviceUnlockStatus.asStateFlow() + private val lockNowRequests = Channel<Unit>() + override suspend fun onActivated(): Nothing { authenticationInteractor.authenticationMethod.collectLatest { authMethod -> if (!authMethod.isSecure) { @@ -196,6 +200,11 @@ constructor( awaitCancellation() } + /** Locks the device instantly. */ + fun lockNow() { + lockNowRequests.trySend(Unit) + } + private suspend fun handleLockAndUnlockEvents() { try { Log.d(TAG, "started watching for lock and unlock events") @@ -225,10 +234,12 @@ constructor( .map { (isAsleep, lastSleepReason) -> if (isAsleep) { if ( - lastSleepReason == WakeSleepReason.POWER_BUTTON && + (lastSleepReason == WakeSleepReason.POWER_BUTTON) && authenticationInteractor.getPowerButtonInstantlyLocks() ) { LockImmediately("locked instantly from power button") + } else if (lastSleepReason == WakeSleepReason.SLEEP_BUTTON) { + LockImmediately("locked instantly from sleep button") } else { LockWithDelay("entering sleep") } @@ -256,6 +267,7 @@ constructor( emptyFlow() } }, + lockNowRequests.receiveAsFlow().map { LockImmediately("lockNow") }, ) .collectLatest(::onLockEvent) } diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt index 589dbf92de38..e862525623fe 100644 --- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt @@ -30,6 +30,8 @@ import com.android.systemui.display.data.repository.FocusedDisplayRepository import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl +import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor +import com.android.systemui.display.domain.interactor.RearDisplayStateInteractorImpl import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import dagger.Binds import dagger.Lazy @@ -46,6 +48,11 @@ interface DisplayModule { provider: ConnectedDisplayInteractorImpl ): ConnectedDisplayInteractor + @Binds + fun bindRearDisplayStateInteractor( + provider: RearDisplayStateInteractorImpl + ): RearDisplayStateInteractor + @Binds fun bindsDisplayRepository(displayRepository: DisplayRepositoryImpl): DisplayRepository @Binds diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt index 1da5351ac2a3..29044d017d2d 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt @@ -20,6 +20,7 @@ import android.content.Context import android.hardware.devicestate.DeviceState as PlatformDeviceState import android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT import android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY +import android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN @@ -49,6 +50,15 @@ interface DeviceStateRepository { UNFOLDED, /** Device state that corresponds to the device being in rear display mode */ REAR_DISPLAY, + /** + * Device state that corresponds to the device being in rear display mode with the inner + * display showing a system-provided affordance to cancel the mode. + * + * TODO(b/371095273): This state will be removed after the RDM_V2 flag lifecycle is complete + * at which point the REAR_DISPLAY state will be the will be the new and only rear display + * mode. + */ + REAR_DISPLAY_OUTER_DEFAULT, /** Device state in that corresponds to the device being in concurrent display mode */ CONCURRENT_DISPLAY, /** Device state in none of the other arrays. */ @@ -62,7 +72,7 @@ constructor( val context: Context, val deviceStateManager: DeviceStateManager, @Background bgScope: CoroutineScope, - @Background executor: Executor + @Background executor: Executor, ) : DeviceStateRepository { override val state: StateFlow<DeviceState> = @@ -105,6 +115,12 @@ constructor( */ private fun PlatformDeviceState.toDeviceStateEnum(): DeviceState { return when { + hasProperties( + PROPERTY_FEATURE_REAR_DISPLAY, + PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT, + ) -> { + DeviceState.REAR_DISPLAY_OUTER_DEFAULT + } hasProperty(PROPERTY_FEATURE_REAR_DISPLAY) -> DeviceState.REAR_DISPLAY hasProperty(PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT) -> { DeviceState.CONCURRENT_DISPLAY @@ -112,7 +128,7 @@ constructor( hasProperty(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY) -> DeviceState.FOLDED hasProperties( PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY, - PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN + PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN, ) -> DeviceState.HALF_FOLDED hasProperty(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY) -> { DeviceState.UNFOLDED diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/RearDisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/RearDisplayStateInteractor.kt new file mode 100644 index 000000000000..b743377881bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/RearDisplayStateInteractor.kt @@ -0,0 +1,72 @@ +/* + * 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.systemui.display.domain.interactor + +import android.view.Display +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.display.data.repository.DeviceStateRepository +import com.android.systemui.display.data.repository.DisplayRepository +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combineTransform +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn + +/** Provides information about the status of Rear Display Mode. */ +interface RearDisplayStateInteractor { + + /** A flow notifying the subscriber of Rear Display state changes */ + val state: Flow<State> + + sealed class State { + /** Indicates that the rear display is disabled */ + data object Disabled : State() + + /** + * Indicates that the device is in Rear Display Mode, and that the inner display is ready to + * show a system-provided affordance allowing the user to cancel out of the Rear Display + * Mode. + */ + data class Enabled(val innerDisplay: Display) : State() + } +} + +@SysUISingleton +class RearDisplayStateInteractorImpl +@Inject +constructor( + displayRepository: DisplayRepository, + deviceStateRepository: DeviceStateRepository, + @Background backgroundCoroutineDispatcher: CoroutineDispatcher, +) : RearDisplayStateInteractor { + + override val state: Flow<RearDisplayStateInteractor.State> = + deviceStateRepository.state + .combineTransform(displayRepository.displays) { state, displays -> + val innerDisplay = displays.find { it.flags and Display.FLAG_REAR != 0 } + + if (state != DeviceStateRepository.DeviceState.REAR_DISPLAY_OUTER_DEFAULT) { + emit(RearDisplayStateInteractor.State.Disabled) + } else if (innerDisplay != null) { + emit(RearDisplayStateInteractor.State.Enabled(innerDisplay)) + } + } + .distinctUntilChanged() + .flowOn(backgroundCoroutineDispatcher) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt index 7b3380a6a608..1af7340ad7b4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt @@ -18,6 +18,9 @@ package com.android.systemui.keyboard.shortcut import com.android.systemui.CoreStartable import com.android.systemui.Flags.keyboardShortcutHelperRewrite +import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository +import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource @@ -27,6 +30,8 @@ import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsS import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts +import com.android.systemui.keyboard.shortcut.qualifiers.CustomShortcutCategories +import com.android.systemui.keyboard.shortcut.qualifiers.DefaultShortcutCategories import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts @@ -63,6 +68,18 @@ interface ShortcutHelperModule { impl: AppCategoriesShortcutsSource ): KeyboardShortcutGroupsSource + @Binds + @DefaultShortcutCategories + fun defaultShortcutCategoriesRepository( + impl: DefaultShortcutCategoriesRepository + ): ShortcutCategoriesRepository + + @Binds + @CustomShortcutCategories + fun customShortcutCategoriesRepository( + impl: CustomShortcutCategoriesRepository + ): ShortcutCategoriesRepository + companion object { @Provides @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt index ec1d358b6bd2..da5590ae27fa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt @@ -23,19 +23,24 @@ import android.hardware.input.InputGestureData.KeyTrigger import android.hardware.input.InputManager import android.hardware.input.InputSettings import android.hardware.input.KeyGestureEvent +import com.android.systemui.Flags.shortcutHelperKeyGlyph import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo +import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -60,6 +65,8 @@ constructor( private val inputManager: InputManager get() = userContext.getSystemService(INPUT_SERVICE) as InputManager + private val _selectedKeyCombination = MutableStateFlow<KeyCombination?>(null) + private val activeInputDevice = stateRepository.state.map { if (it is Active) { @@ -69,6 +76,41 @@ constructor( } } + val pressedKeys = + _selectedKeyCombination + .combine(activeInputDevice) { keyCombination, inputDevice -> + if (inputDevice == null || keyCombination == null) { + return@combine emptyList() + } else { + val keyGlyphMap = + if (shortcutHelperKeyGlyph()) { + inputManager.getKeyGlyphMap(inputDevice.id) + } else null + val modifiers = + shortcutCategoriesUtils.toShortcutModifierKeys( + keyCombination.modifiers, + keyGlyphMap, + ) + val triggerKey = + keyCombination.keyCode?.let { + shortcutCategoriesUtils.toShortcutKey( + keyGlyphMap, + inputDevice.keyCharacterMap, + keyCode = it, + ) + } + val keys = mutableListOf<ShortcutKey>() + modifiers?.let { keys += it } + triggerKey?.let { keys += it } + return@combine keys + } + } + .stateIn( + scope = backgroundScope, + started = SharingStarted.Lazily, + initialValue = emptyList(), + ) + override val categories: Flow<List<ShortcutCategory>> = activeInputDevice .map { inputDevice -> @@ -104,6 +146,10 @@ constructor( started = SharingStarted.Lazily, ) + fun updateUserKeyCombination(keyCombination: KeyCombination?) { + _selectedKeyCombination.value = keyCombination + } + private fun toInternalGroupSources( inputGestures: List<InputGestureData> ): List<InternalGroupsSource> { @@ -148,7 +194,7 @@ constructor( private fun fetchGroupLabelByGestureType( @KeyGestureEvent.KeyGestureType keyGestureType: Int ): String? { - InputGestures.gestureToInternalKeyboardShortcutGroupLabelMap[keyGestureType]?.let { + InputGestures.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let { return context.getString(it) } ?: return null } @@ -156,7 +202,7 @@ constructor( private fun fetchShortcutInfoLabelByGestureType( @KeyGestureEvent.KeyGestureType keyGestureType: Int ): String? { - InputGestures.gestureToInternalKeyboardShortcutInfoLabelMap[keyGestureType]?.let { + InputGestures.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let { return context.getString(it) } ?: return null } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt index 90be9888e622..7bb294df98cd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt @@ -81,7 +81,7 @@ object InputGestures { KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to AppCategories, ) - val gestureToInternalKeyboardShortcutGroupLabelMap = + val gestureToInternalKeyboardShortcutGroupLabelResIdMap = mapOf( // System Category KEY_GESTURE_TYPE_HOME to R.string.shortcut_helper_category_system_controls, @@ -129,7 +129,7 @@ object InputGestures { R.string.keyboard_shortcut_group_applications, ) - val gestureToInternalKeyboardShortcutInfoLabelMap = + val gestureToInternalKeyboardShortcutInfoLabelResIdMap = mapOf( // System Category KEY_GESTURE_TYPE_HOME to R.string.group_system_access_home_screen, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt index 899fd15d6115..3988d1f155bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt @@ -24,6 +24,7 @@ import android.util.Log import android.view.InputDevice import android.view.KeyCharacterMap import android.view.KeyEvent +import android.view.KeyEvent.META_META_ON import com.android.systemui.Flags.shortcutHelperKeyGlyph import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup @@ -92,7 +93,7 @@ constructor( } .filter { it.shortcuts.isNotEmpty() } return if (subCategories.isEmpty()) { - Log.wtf(TAG, "Empty sub categories after converting $shortcutGroups") + Log.w(TAG, "Empty sub categories after converting $shortcutGroups") null } else { ShortcutCategory(type, subCategories) @@ -161,7 +162,7 @@ constructor( } if (remainingModifiers != 0) { // There is a remaining modifier we don't support - Log.wtf(TAG, "Unsupported modifiers remaining: $remainingModifiers") + Log.w(TAG, "Unsupported modifiers remaining: $remainingModifiers") return null } if (info.keycode != 0 || info.baseCharacter > Char.MIN_VALUE) { @@ -170,21 +171,32 @@ constructor( ?: return null } if (keys.isEmpty()) { - Log.wtf(TAG, "No keys for $info") + Log.w(TAG, "No keys for $info") return null } return ShortcutCommand(keys = keys, isCustom = info.isCustomShortcut) } + fun toShortcutModifierKeys(modifiers: Int, keyGlyphMap: KeyGlyphMap?): List<ShortcutKey>? { + val keys: MutableList<ShortcutKey> = mutableListOf() + var remainingModifiers = modifiers + SUPPORTED_MODIFIERS.forEach { supportedModifier -> + if ((supportedModifier and remainingModifiers) != 0) { + keys += toShortcutModifierKey(keyGlyphMap, supportedModifier) ?: return null + remainingModifiers = remainingModifiers and supportedModifier.inv() + } + } + return keys + } + private fun toShortcutModifierKey(keyGlyphMap: KeyGlyphMap?, modifierMask: Int): ShortcutKey? { val modifierDrawable = keyGlyphMap?.getDrawableForModifierState(context, modifierMask) if (modifierDrawable != null) { return ShortcutKey.Icon.DrawableIcon(drawable = modifierDrawable) } - val iconResId = ShortcutHelperKeys.keyIcons[modifierMask] - if (iconResId != null) { - return ShortcutKey.Icon.ResIdIcon(iconResId) + if (modifierMask == META_META_ON) { + return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.metaModifierIconResId) } val modifierLabel = ShortcutHelperKeys.modifierLabels[modifierMask] @@ -195,7 +207,7 @@ constructor( return null } - private fun toShortcutKey( + fun toShortcutKey( keyGlyphMap: KeyGlyphMap?, keyCharacterMap: KeyCharacterMap, keyCode: Int, @@ -222,7 +234,7 @@ constructor( if (displayLabelCharacter.code != 0) { return ShortcutKey.Text(displayLabelCharacter.toString()) } - Log.wtf(TAG, "Couldn't find label or icon for key: $keyCode") + Log.w(TAG, "Couldn't find label or icon for key: $keyCode") return null } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt index 288efa275219..e47b33f8c37c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt @@ -116,9 +116,10 @@ import com.android.systemui.res.R object ShortcutHelperKeys { + val metaModifierIconResId = R.drawable.ic_ksh_key_meta + val keyIcons = mapOf( - META_META_ON to R.drawable.ic_ksh_key_meta, KEYCODE_BACK to R.drawable.ic_arrow_back_2, KEYCODE_HOME to R.drawable.ic_radio_button_unchecked, KEYCODE_RECENT_APPS to R.drawable.ic_check_box_outline_blank, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt index 85d22144f201..aad55dc11c16 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt @@ -16,13 +16,22 @@ package com.android.systemui.keyboard.shortcut.domain.interactor -import android.view.KeyEvent.META_META_ON +import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys +import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import javax.inject.Inject -class ShortcutCustomizationInteractor @Inject constructor() { +class ShortcutCustomizationInteractor +@Inject +constructor(private val customShortcutRepository: CustomShortcutCategoriesRepository) { + val pressedKeys = customShortcutRepository.pressedKeys + + fun updateUserSelectedKeyCombination(keyCombination: KeyCombination?) { + customShortcutRepository.updateUserKeyCombination(keyCombination) + } + fun getDefaultCustomShortcutModifierKey(): ShortcutKey.Icon.ResIdIcon { - return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.keyIcons[META_META_ON]!!) + return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.metaModifierIconResId) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt index 39fc27d35082..0381eaec5026 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt @@ -16,37 +16,66 @@ package com.android.systemui.keyboard.shortcut.domain.interactor +import com.android.systemui.Flags.keyboardShortcutHelperShortcutCustomizer import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesRepository +import com.android.systemui.keyboard.shortcut.qualifiers.CustomShortcutCategories +import com.android.systemui.keyboard.shortcut.qualifiers.DefaultShortcutCategories import com.android.systemui.keyboard.shortcut.shared.model.Shortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf @SysUISingleton class ShortcutHelperCategoriesInteractor @Inject -constructor(categoriesRepository: DefaultShortcutCategoriesRepository) { - +constructor( + @DefaultShortcutCategories defaultCategoriesRepository: ShortcutCategoriesRepository, + @CustomShortcutCategories customCategoriesRepositoryLazy: Lazy<ShortcutCategoriesRepository>, +) { val shortcutCategories: Flow<List<ShortcutCategory>> = - categoriesRepository.categories.map { categories -> - categories.map { category -> groupSubCategoriesInCategory(category) } + defaultCategoriesRepository.categories.combine( + if (keyboardShortcutHelperShortcutCustomizer()) { + customCategoriesRepositoryLazy.get().categories + } else { + flowOf(emptyList()) + } + ) { defaultShortcutCategories, customShortcutCategories -> + groupCategories(defaultShortcutCategories + customShortcutCategories) } - private fun groupSubCategoriesInCategory(shortcutCategory: ShortcutCategory): ShortcutCategory { - val subCategoriesWithGroupedShortcuts = - shortcutCategory.subCategories.map { + private fun groupCategories( + shortcutCategories: List<ShortcutCategory> + ): List<ShortcutCategory> { + return shortcutCategories + .groupBy { it.type } + .entries + .map { (categoryType, groupedCategories) -> + ShortcutCategory( + type = categoryType, + subCategories = + groupSubCategories(groupedCategories.flatMap { it.subCategories }), + ) + } + } + + private fun groupSubCategories( + subCategories: List<ShortcutSubCategory> + ): List<ShortcutSubCategory> { + return subCategories + .groupBy { it.label } + .entries + .map { (label, groupedSubcategories) -> ShortcutSubCategory( - label = it.label, - shortcuts = groupShortcutsInSubcategory(it.shortcuts), + label = label, + shortcuts = + groupShortcutsInSubcategory(groupedSubcategories.flatMap { it.shortcuts }), ) } - return ShortcutCategory( - type = shortcutCategory.type, - subCategories = subCategoriesWithGroupedShortcuts, - ) } private fun groupShortcutsInSubcategory(shortcuts: List<Shortcut>) = diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CustomShortcutCategories.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CustomShortcutCategories.kt new file mode 100644 index 000000000000..8acb9eebbd4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CustomShortcutCategories.kt @@ -0,0 +1,21 @@ +/* + * 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.systemui.keyboard.shortcut.qualifiers + +import javax.inject.Qualifier + +@Qualifier annotation class CustomShortcutCategories diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/DefaultShortcutCategories.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/DefaultShortcutCategories.kt new file mode 100644 index 000000000000..f94e10d17964 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/DefaultShortcutCategories.kt @@ -0,0 +1,21 @@ +/* + * 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.systemui.keyboard.shortcut.qualifiers + +import javax.inject.Qualifier + +@Qualifier annotation class DefaultShortcutCategories diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/KeyCombination.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/KeyCombination.kt new file mode 100644 index 000000000000..5e4031b29502 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/KeyCombination.kt @@ -0,0 +1,19 @@ +/* + * 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.systemui.keyboard.shortcut.shared.model + +data class KeyCombination(val modifiers: Int, val keyCode: Int?) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt index 3666de460d91..b6b5d1716c79 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -96,6 +96,7 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.isTraversalGroup import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.SpanStyle @@ -400,7 +401,7 @@ private fun ShortcutHelperTwoPane( Row(Modifier.fillMaxWidth()) { StartSidePanel( onSearchQueryChanged = onSearchQueryChanged, - modifier = Modifier.width(240.dp), + modifier = Modifier.width(240.dp).semantics { isTraversalGroup = true }, categories = categories, onKeyboardSettingsClicked = onKeyboardSettingsClicked, selectedCategory = selectedCategoryType, @@ -409,7 +410,7 @@ private fun ShortcutHelperTwoPane( Spacer(modifier = Modifier.width(24.dp)) EndSidePanel( searchQuery, - Modifier.fillMaxSize().padding(top = 8.dp), + Modifier.fillMaxSize().padding(top = 8.dp).semantics { isTraversalGroup = true }, selectedCategory, isCustomizing = isCustomizing, onCustomizationRequested = onCustomizationRequested, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt index 912bfe99cea8..08fd0af81006 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt @@ -123,7 +123,7 @@ constructor( userContext.packageManager.getApplicationIcon(type.packageName) IconSource(painter = DrawablePainter(drawable = iconDrawable)) } catch (e: NameNotFoundException) { - Log.wtf( + Log.w( "ShortcutHelperViewModel", "Package not found when retrieving icon for ${type.packageName}", ) @@ -153,7 +153,7 @@ constructor( packageManagerForUser.getApplicationInfo(type.packageName, /* flags= */ 0) return packageManagerForUser.getApplicationLabel(currentAppInfo).toString() } catch (e: NameNotFoundException) { - Log.wtf( + Log.w( "ShortcutHelperViewModel", "Package Not found when retrieving Label for ${type.packageName}", ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 32c2bc7c8620..7097c1d2fc4e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -662,7 +662,9 @@ public class KeyguardService extends Service { trace("doKeyguardTimeout"); checkPermission(); - if (KeyguardWmStateRefactor.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { + mDeviceEntryInteractorLazy.get().lockNow(); + } else if (KeyguardWmStateRefactor.isEnabled()) { mKeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout(options); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index b51bb7b229ee..aa7eb2933992 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.biometrics.AuthController +import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -115,7 +116,7 @@ constructor( val scaleFactor: Float = authController.scaleFactor val mBottomPaddingPx = - context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) + context.resources.getDimensionPixelSize(customR.dimen.lock_icon_margin_bottom) val bounds = windowManager.currentWindowMetrics.bounds var widthPixels = bounds.right.toFloat() if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 7ad2ec5e3bf8..d54d411b7de6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -124,7 +124,7 @@ constructor( ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, - horizontalPaddingStart + horizontalPaddingStart, ) // migrate addSmartspaceView from KeyguardClockSwitchController @@ -135,15 +135,15 @@ constructor( ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, - horizontalPaddingStart + horizontalPaddingStart, ) connect( sharedR.id.bc_smartspace_view, ConstraintSet.END, - if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID - else R.id.split_shade_guideline, + if (keyguardSmartspaceViewModel.isShadeLayoutWide.value) R.id.split_shade_guideline + else ConstraintSet.PARENT_ID, ConstraintSet.END, - horizontalPaddingEnd + horizontalPaddingEnd, ) if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) { @@ -152,7 +152,7 @@ constructor( sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM, sharedR.id.bc_smartspace_view, - ConstraintSet.TOP + ConstraintSet.TOP, ) } else { clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM) @@ -160,13 +160,13 @@ constructor( sharedR.id.date_smartspace_view, ConstraintSet.TOP, customR.id.lockscreen_clock_view, - ConstraintSet.BOTTOM + ConstraintSet.BOTTOM, ) connect( sharedR.id.bc_smartspace_view, ConstraintSet.TOP, sharedR.id.date_smartspace_view, - ConstraintSet.BOTTOM + ConstraintSet.BOTTOM, ) } @@ -174,10 +174,7 @@ constructor( R.id.smart_space_barrier_bottom, Barrier.BOTTOM, 0, - *intArrayOf( - sharedR.id.bc_smartspace_view, - sharedR.id.date_smartspace_view, - ) + *intArrayOf(sharedR.id.bc_smartspace_view, sharedR.id.date_smartspace_view), ) } updateVisibility(constraintSet) @@ -212,7 +209,7 @@ constructor( setVisibility(sharedR.id.weather_smartspace_view, weatherVisibility) setAlpha( sharedR.id.weather_smartspace_view, - if (weatherVisibility == View.VISIBLE) 1f else 0f + if (weatherVisibility == View.VISIBLE) 1f else 0f, ) val dateVisibility = if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE @@ -220,7 +217,7 @@ constructor( setVisibility(sharedR.id.date_smartspace_view, dateVisibility) setAlpha( sharedR.id.date_smartspace_view, - if (dateVisibility == ConstraintSet.VISIBLE) 1f else 0f + if (dateVisibility == ConstraintSet.VISIBLE) 1f else 0f, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt index de0927ec27cb..3266dc45427a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt @@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -39,6 +40,7 @@ constructor( smartspaceController: LockscreenSmartspaceController, keyguardClockViewModel: KeyguardClockViewModel, smartspaceInteractor: KeyguardSmartspaceInteractor, + shadeInteractor: ShadeInteractor, ) { /** Whether the smartspace section is available in the build. */ val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled @@ -89,6 +91,8 @@ constructor( /* trigger clock and smartspace constraints change when smartspace appears */ val bcSmartspaceVisibility: StateFlow<Int> = smartspaceInteractor.bcSmartspaceVisibility + val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide + companion object { fun getSmartspaceStartMargin(context: Context): Int { return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) + diff --git a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl index 3e947d9d2b16..7803f229dda7 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl +++ b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl @@ -16,6 +16,7 @@ package com.android.systemui.notetask; +import com.android.systemui.notetask.NoteTaskBubbleExpandBehavior; import android.content.Intent; import android.graphics.drawable.Icon; import android.os.UserHandle; @@ -25,5 +26,6 @@ interface INoteTaskBubblesService { boolean areBubblesAvailable(); - void showOrHideAppBubble(in Intent intent, in UserHandle userHandle, in Icon icon); + void showOrHideAppBubble(in Intent intent, in UserHandle userHandle, in Icon icon, + in NoteTaskBubbleExpandBehavior bubbleExpandBehavior); } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl new file mode 100644 index 000000000000..86a06a568a4b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl @@ -0,0 +1,3 @@ +package com.android.systemui.notetask; + +parcelable NoteTaskBubbleExpandBehavior;
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt new file mode 100644 index 000000000000..63b38a1b73f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt @@ -0,0 +1,50 @@ +/* + * 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.systemui.notetask + +import android.os.Parcel +import android.os.Parcelable + +enum class NoteTaskBubbleExpandBehavior : Parcelable { + /** + * The default bubble expand behavior for note task bubble: The bubble will collapse if there is + * already an expanded bubble, The bubble will expand if there is a collapsed bubble. + */ + DEFAULT, + /** + * The special bubble expand behavior for note task bubble: The bubble will stay expanded, not + * collapse, if there is already an expanded bubble, The bubble will expand if there is a + * collapsed bubble. + */ + KEEP_IF_EXPANDED; + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(name) + } + + companion object CREATOR : Parcelable.Creator<NoteTaskBubbleExpandBehavior> { + override fun createFromParcel(parcel: Parcel?): NoteTaskBubbleExpandBehavior { + return parcel?.readString()?.let { valueOf(it) } ?: DEFAULT + } + + override fun newArray(size: Int) = arrayOfNulls<NoteTaskBubbleExpandBehavior>(size) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt index ec205f87d9fa..169285f6742e 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt @@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.DebugLogger.debugLog +import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import javax.inject.Inject @@ -48,7 +49,7 @@ open class NoteTaskBubblesController @Inject constructor( @Application private val context: Context, - @Background private val bgDispatcher: CoroutineDispatcher + @Background private val bgDispatcher: CoroutineDispatcher, ) { private val serviceConnector: ServiceConnector<INoteTaskBubblesService> = @@ -57,7 +58,7 @@ constructor( Intent(context, NoteTaskBubblesService::class.java), Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, UserHandle.USER_SYSTEM, - INoteTaskBubblesService.Stub::asInterface + INoteTaskBubblesService.Stub::asInterface, ) /** Returns whether notes app bubble is supported. */ @@ -79,11 +80,12 @@ constructor( open suspend fun showOrHideAppBubble( intent: Intent, userHandle: UserHandle, - icon: Icon + icon: Icon, + bubbleExpandBehavior: NoteTaskBubbleExpandBehavior, ) { withContext(bgDispatcher) { serviceConnector - .post { it.showOrHideAppBubble(intent, userHandle, icon) } + .post { it.showOrHideAppBubble(intent, userHandle, icon, bubbleExpandBehavior) } .whenComplete { _, error -> if (error != null) { debugLog(error = error) { @@ -120,16 +122,28 @@ constructor( override fun showOrHideAppBubble( intent: Intent, userHandle: UserHandle, - icon: Icon + icon: Icon, + bubbleExpandBehavior: NoteTaskBubbleExpandBehavior, ) { mOptionalBubbles.ifPresentOrElse( - { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) }, + { bubbles -> + if ( + bubbleExpandBehavior == + NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED && + bubbles.isBubbleExpanded( + Bubble.getAppBubbleKeyForApp(intent.`package`, userHandle) + ) + ) { + return@ifPresentOrElse + } + bubbles.showOrHideAppBubble(intent, userHandle, icon) + }, { debugLog { "Failed to show or hide bubble for intent $intent," + "user $user, and icon $icon as bubble is empty." } - } + }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 1fa5baaa21ae..a615963ed2ca 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -84,7 +84,7 @@ constructor( private val userTracker: UserTracker, private val secureSettings: SecureSettings, @Application private val applicationScope: CoroutineScope, - @Background private val bgCoroutineContext: CoroutineContext + @Background private val bgCoroutineContext: CoroutineContext, ) { @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>() @@ -98,7 +98,7 @@ constructor( if (key != Bubble.getAppBubbleKeyForApp(info.packageName, info.user)) return // Safe guard mechanism, this callback should only be called for app bubbles. - if (info.launchMode != NoteTaskLaunchMode.AppBubble) return + if (info.launchMode !is NoteTaskLaunchMode.AppBubble) return if (isExpanding) { debugLog { "onBubbleExpandChanged - expanding: $info" } @@ -117,10 +117,8 @@ constructor( } else { getUserForHandlingNotesTaking(entryPoint) } - activityContext.startActivityAsUser( - createNotesRoleHolderSettingsIntent(), - user - ) + + activityContext.startActivityAsUser(createNotesRoleHolderSettingsIntent(), user) } /** @@ -140,8 +138,7 @@ constructor( entryPoint == QUICK_AFFORDANCE -> { userTracker.userProfiles .firstOrNull { userManager.isManagedProfile(it.id) } - ?.userHandle - ?: userTracker.userHandle + ?.userHandle ?: userTracker.userHandle } // On work profile devices, SysUI always run in the main user. else -> userTracker.userHandle @@ -158,19 +155,14 @@ constructor( * * That will let users open other apps in full screen, and take contextual notes. */ - fun showNoteTask( - entryPoint: NoteTaskEntryPoint, - ) { + fun showNoteTask(entryPoint: NoteTaskEntryPoint) { if (!isEnabled) return showNoteTaskAsUser(entryPoint, getUserForHandlingNotesTaking(entryPoint)) } /** A variant of [showNoteTask] which launches note task in the given [user]. */ - fun showNoteTaskAsUser( - entryPoint: NoteTaskEntryPoint, - user: UserHandle, - ) { + fun showNoteTaskAsUser(entryPoint: NoteTaskEntryPoint, user: UserHandle) { if (!isEnabled) return applicationScope.launch("$TAG#showNoteTaskAsUser") { @@ -178,10 +170,7 @@ constructor( } } - private suspend fun awaitShowNoteTaskAsUser( - entryPoint: NoteTaskEntryPoint, - user: UserHandle, - ) { + private suspend fun awaitShowNoteTaskAsUser(entryPoint: NoteTaskEntryPoint, user: UserHandle) { if (!isEnabled) return if (!noteTaskBubblesController.areBubblesAvailable()) { @@ -222,7 +211,13 @@ constructor( val intent = createNoteTaskIntent(info) val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) - noteTaskBubblesController.showOrHideAppBubble(intent, user, icon) + noteTaskBubblesController.showOrHideAppBubble( + intent, + user, + icon, + info.launchMode.bubbleExpandBehavior, + ) + // App bubble logging happens on `onBubbleExpandChanged`. debugLog { "onShowNoteTask - opened as app bubble: $info" } } @@ -399,8 +394,8 @@ constructor( const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package" /** Returns notes role holder settings intent. */ - fun createNotesRoleHolderSettingsIntent() = Intent(Intent.ACTION_MANAGE_DEFAULT_APP). - putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES) + fun createNotesRoleHolderSettingsIntent() = + Intent(Intent.ACTION_MANAGE_DEFAULT_APP).putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES) } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt index 269eb870686c..8319e07525d8 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt @@ -31,6 +31,10 @@ data class NoteTaskInfo( if (isKeyguardLocked || entryPoint == WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE) { NoteTaskLaunchMode.Activity } else { - NoteTaskLaunchMode.AppBubble + if (entryPoint == NoteTaskEntryPoint.QS_NOTES_TILE) { + NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED) + } else { + NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.DEFAULT) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt index 836e103f4d69..6c85f20f2bce 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt @@ -26,7 +26,8 @@ import com.android.wm.shell.bubbles.Bubbles sealed class NoteTaskLaunchMode { /** @see Bubbles.showOrHideAppBubble */ - object AppBubble : NoteTaskLaunchMode() + data class AppBubble(val bubbleExpandBehavior: NoteTaskBubbleExpandBehavior) : + NoteTaskLaunchMode() /** @see Context.startActivity */ object Activity : NoteTaskLaunchMode() diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt index a1c5c9c682c3..5d5465633f9f 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt @@ -41,6 +41,7 @@ import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel import com.android.systemui.res.R import dagger.Binds import dagger.Module @@ -106,12 +107,14 @@ interface NoteTaskModule { stateInteractor: NotesTileDataInteractor, userActionInteractor: NotesTileUserActionInteractor, ): QSTileViewModel = - factory.create( - TileSpec.create(NOTES_TILE_SPEC), - userActionInteractor, - stateInteractor, - mapper, - ) + if (com.android.systemui.Flags.qsNewTilesFuture()) + factory.create( + TileSpec.create(NOTES_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + else StubQSTileViewModel @Provides @IntoMap @@ -120,10 +123,10 @@ interface NoteTaskModule { QSTileConfig( tileSpec = TileSpec.create(NOTES_TILE_SPEC), uiConfig = - QSTileUIConfig.Resource( - iconRes = R.drawable.ic_qs_notes, - labelRes = R.string.quick_settings_notes_label, - ), + QSTileUIConfig.Resource( + iconRes = R.drawable.ic_qs_notes, + labelRes = R.string.quick_settings_notes_label, + ), instanceId = uiEventLogger.getNewInstanceId(), category = TileCategory.UTILITIES, ) diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt index 776a8f47f056..c57b53bab442 100644 --- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt +++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt @@ -26,6 +26,9 @@ enum class WakeSleepReason( /** The physical power button was pressed to wake up or sleep the device. */ POWER_BUTTON(isTouch = false, PowerManager.WAKE_REASON_POWER_BUTTON), + /** The sleep button was pressed to sleep the device. */ + SLEEP_BUTTON(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON), + /** The user has tapped or double tapped to wake the screen. */ TAP(isTouch = true, PowerManager.WAKE_REASON_TAP), @@ -78,6 +81,7 @@ enum class WakeSleepReason( fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason { return when (reason) { PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON + PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON -> SLEEP_BUTTON PowerManager.GO_TO_SLEEP_REASON_TIMEOUT -> TIMEOUT PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD -> FOLD else -> OTHER diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt index 8b0694219630..0d464f5a0936 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt @@ -28,6 +28,7 @@ import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Compa import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -48,7 +49,7 @@ class QSHostAdapter @Inject constructor( private val interactor: CurrentTilesInteractor, - private val context: Context, + @ShadeDisplayAware private val context: Context, private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder, @Application private val scope: CoroutineScope, dumpManager: DumpManager, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 85bcc25e140c..afb852ae824c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -50,6 +50,7 @@ import com.android.systemui.util.kotlin.JavaAdapterKt; import kotlin.Unit; import kotlin.jvm.functions.Function1; +import kotlinx.coroutines.DisposableHandle; import kotlinx.coroutines.flow.StateFlow; import java.io.PrintWriter; @@ -107,6 +108,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr setLayoutForMediaInScene(); }; + private DisposableHandle mJavaAdapterDisposableHandle; + private boolean mLastListening; @VisibleForTesting @@ -221,6 +224,9 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mView.removeTile(record); } mRecords.clear(); + if (mJavaAdapterDisposableHandle != null) { + mJavaAdapterDisposableHandle.dispose(); + } } @Override @@ -255,7 +261,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr } private void registerForMediaInteractorChanges() { - JavaAdapterKt.collectFlow( + mJavaAdapterDisposableHandle = JavaAdapterKt.collectFlow( mView, getMediaVisibleFlow(), mMediaOrRecommendationVisibleConsumer diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java index d38f8492c883..88f0318c2fbf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java @@ -87,6 +87,7 @@ import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig; import com.android.systemui.res.R; import com.android.systemui.security.data.model.SecurityModel; import com.android.systemui.settings.UserTracker; +import com.android.systemui.shade.ShadeDisplayAware; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.SecurityController; @@ -177,7 +178,7 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener { @Inject QSSecurityFooterUtils( - @Application Context context, DevicePolicyManager devicePolicyManager, + @ShadeDisplayAware Context context, DevicePolicyManager devicePolicyManager, UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter, SecurityController securityController, @Background Looper bgLooper, DialogTransitionAnimator dialogTransitionAnimator) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt index 676f6a426264..2ec729223a8d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt @@ -20,6 +20,7 @@ import android.content.Context import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.qs.flags.QSComposeFragment +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.util.Utils import dagger.Module import dagger.Provides @@ -34,7 +35,7 @@ interface QSFragmentComposeModule { @Provides @SysUISingleton @Named(QS_USING_MEDIA_PLAYER) - fun providesUsingMedia(@Application context: Context): Boolean { + fun providesUsingMedia(@ShadeDisplayAware context: Context): Boolean { return QSComposeFragment.isEnabled && Utils.useQsMediaPlayer(context) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 89f85ab14dd6..15e34990e111 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -43,6 +43,7 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; +import com.android.systemui.shade.ShadeDisplayAware; import java.util.ArrayList; import java.util.Arrays; @@ -69,7 +70,7 @@ public class TileQueryHelper { @Inject public TileQueryHelper( - Context context, + @ShadeDisplayAware Context context, UserTracker userTracker, @Main Executor mainExecutor, @Background Executor bgExecutor diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 564bc78a3f98..8ef637545e69 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -37,6 +37,7 @@ import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.util.icuMessageFormat import javax.inject.Inject import javax.inject.Named @@ -112,7 +113,7 @@ class FooterActionsViewModel( class Factory @Inject constructor( - @Application private val context: Context, + @ShadeDisplayAware private val context: Context, private val falsingManager: FalsingManager, private val footerActionsInteractor: FooterActionsInteractor, private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, @@ -175,7 +176,7 @@ class FooterActionsViewModel( } fun FooterActionsViewModel( - @Application appContext: Context, + @ShadeDisplayAware appContext: Context, footerActionsInteractor: FooterActionsInteractor, falsingManager: FalsingManager, globalActionsDialogLite: GlobalActionsDialogLite, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 4e094cc77eae..789fdebc36eb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -16,12 +16,17 @@ package com.android.systemui.qs.panels.ui.compose -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape @@ -39,16 +44,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.development.ui.compose.BuildNumber +import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType -import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight -import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing +import com.android.systemui.qs.panels.ui.compose.Dimensions.FooterHeight +import com.android.systemui.qs.panels.ui.compose.Dimensions.InterPageSpacing import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.ui.compose.borderOnFocus @@ -121,38 +127,78 @@ constructor( TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) } } - // Use requiredHeight so it won't be squished if the view doesn't quite fit. As this is - // expected to be inside a scrollable container, this should not be an issue. - Box(modifier = Modifier.requiredHeight(FooterHeight).fillMaxWidth()) { - PagerDots( - pagerState = pagerState, - activeColor = MaterialTheme.colorScheme.primary, - nonActiveColor = MaterialTheme.colorScheme.surfaceVariant, - modifier = Modifier.align(Alignment.Center), - ) - CompositionLocalProvider(value = LocalContentColor provides Color.White) { - IconButton( - onClick = editModeStart, - shape = RoundedCornerShape(CornerSize(28.dp)), - modifier = - Modifier.align(Alignment.CenterEnd) - .borderOnFocus( - color = MaterialTheme.colorScheme.secondary, - cornerSize = CornerSize(FooterHeight / 2), - ), - ) { - Icon( - imageVector = Icons.Default.Edit, - contentDescription = stringResource(id = R.string.qs_edit), + FooterBar( + buildNumberViewModelFactory = viewModel.buildNumberViewModelFactory, + pagerState = pagerState, + editModeStart = editModeStart, + ) + } + } +} + +private object Dimensions { + val FooterHeight = 48.dp + val InterPageSpacing = 16.dp +} + +@Composable +private fun FooterBar( + buildNumberViewModelFactory: BuildNumberViewModel.Factory, + pagerState: PagerState, + editModeStart: () -> Unit, +) { + // Use requiredHeight so it won't be squished if the view doesn't quite fit. As this is + // expected to be inside a scrollable container, this should not be an issue. + // Also, we construct the layout this way to do the following: + // * PagerDots is centered in the row, taking as much space as it needs. + // * On the start side, we place the BuildNumber, taking as much space as it needs, but + // constrained by the available space left over after PagerDots + // * On the end side, we place the edit mode button, with the same constraints as for + // BuildNumber (but it will usually fit, as it's just a square button). + Row( + modifier = Modifier.requiredHeight(FooterHeight).fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = spacedBy(8.dp), + ) { + Row(Modifier.weight(1f)) { + BuildNumber( + viewModelFactory = buildNumberViewModelFactory, + textColor = MaterialTheme.colorScheme.onSurface, + modifier = + Modifier.borderOnFocus( + color = MaterialTheme.colorScheme.secondary, + cornerSize = CornerSize(1.dp), ) - } + .wrapContentSize(), + ) + Spacer(modifier = Modifier.weight(1f)) + } + PagerDots( + pagerState = pagerState, + activeColor = MaterialTheme.colorScheme.primary, + nonActiveColor = MaterialTheme.colorScheme.surfaceVariant, + modifier = Modifier.wrapContentWidth(), + ) + Row(Modifier.weight(1f)) { + Spacer(modifier = Modifier.weight(1f)) + CompositionLocalProvider( + value = LocalContentColor provides MaterialTheme.colorScheme.onSurface + ) { + IconButton( + onClick = editModeStart, + shape = RoundedCornerShape(CornerSize(28.dp)), + modifier = + Modifier.borderOnFocus( + color = MaterialTheme.colorScheme.secondary, + cornerSize = CornerSize(FooterHeight / 2), + ), + ) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = stringResource(id = R.string.qs_edit), + ) } } } } - - private object Dimensions { - val FooterHeight = 48.dp - val InterPageSpacing = 16.dp - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt index 4e34e73654fc..faab6960a99c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt @@ -56,7 +56,7 @@ constructor( private val tilesAvailabilityInteractor: TilesAvailabilityInteractor, private val minTilesInteractor: MinimumTilesInteractor, @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, - @Application private val applicationContext: Context, + @ShadeDisplayAware private val context: Context, @Named("Default") private val defaultGridLayout: GridLayout, @Application private val applicationScope: CoroutineScope, gridLayoutTypeInteractor: GridLayoutTypeInteractor, @@ -140,7 +140,7 @@ constructor( .combine(configurationInteractor.onAnyConfigurationChange.emitOnStart()) { tiles, _ -> - tiles.fastMap { it.load(applicationContext) } + tiles.fastMap { it.load(context) } } } else { emptyFlow() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt index e5607eb6e620..bff330b98fda 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.panels.ui.viewmodel import androidx.compose.runtime.getValue +import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS @@ -34,6 +35,7 @@ constructor( columnsWithMediaViewModelFactory: QSColumnsViewModel.Factory, paginatedGridInteractor: PaginatedGridInteractor, inFirstPageViewModel: InFirstPageViewModel, + val buildNumberViewModelFactory: BuildNumberViewModel.Factory, ) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() { private val hydrator = Hydrator("PaginatedGridViewModel") diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt index c5b27376a82a..41cdefdb2396 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt @@ -30,8 +30,8 @@ import androidx.annotation.GuardedBy import com.android.systemui.common.data.repository.PackageChangeRepository import com.android.systemui.common.shared.model.PackageChangeModel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.util.kotlin.isComponentActuallyEnabled import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -54,7 +54,7 @@ interface InstalledTilesComponentRepository { class InstalledTilesComponentRepositoryImpl @Inject constructor( - @Application private val applicationContext: Context, + @ShadeDisplayAware private val context: Context, @Background private val backgroundScope: CoroutineScope, private val packageChangeRepository: PackageChangeRepository ) : InstalledTilesComponentRepository { @@ -77,10 +77,10 @@ constructor( * context. */ val packageManager = - if (applicationContext.userId == userId) { - applicationContext.packageManager + if (context.userId == userId) { + context.packageManager } else { - applicationContext + context .createContextAsUser( UserHandle.of(userId), /* flags */ 0, diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt index 31ea734fb842..e9c91ca0db12 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt @@ -27,6 +27,7 @@ import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking import com.android.systemui.qs.pipeline.domain.model.AutoAddable import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.NightDisplayTile +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -42,7 +43,7 @@ class NightDisplayAutoAddable @Inject constructor( private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder, - context: Context, + @ShadeDisplayAware context: Context, ) : AutoAddable { private val enabled = ColorDisplayManager.isNightDisplayAvailable(context) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index 6b654beea149..fed8b60a653f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -39,6 +39,7 @@ import com.android.systemui.qs.PseudoGridView; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.qs.user.UserSwitchDialogController; import com.android.systemui.res.R; +import com.android.systemui.shade.ShadeDisplayAware; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -95,7 +96,7 @@ public class UserDetailView extends PseudoGridView { } @Inject - public Adapter(Context context, UserSwitcherController controller, + public Adapter(@ShadeDisplayAware Context context, UserSwitcherController controller, UiEventLogger uiEventLogger, FalsingManager falsingManager) { super(controller); mContext = context; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt index 87b89ea6810a..45775272e01f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt @@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor.PolicyResult +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext @@ -70,7 +71,7 @@ interface DisabledByPolicyInteractor { class DisabledByPolicyInteractorImpl @Inject constructor( - private val context: Context, + @ShadeDisplayAware private val context: Context, private val activityStarter: ActivityStarter, private val restrictedLockProxy: RestrictedLockProxy, @Background private val backgroundDispatcher: CoroutineDispatcher, @@ -105,7 +106,7 @@ constructor( /** Mockable proxy for [RestrictedLockUtilsInternal] static methods. */ @VisibleForTesting -class RestrictedLockProxy @Inject constructor(private val context: Context) { +class RestrictedLockProxy @Inject constructor(@ShadeDisplayAware private val context: Context) { @WorkerThread fun hasBaseUserRestriction(userId: Int, userRestriction: String?): Boolean = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt index b766ee0d4333..222fa3efbe94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt @@ -28,6 +28,7 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProviderImpl import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.shade.ShadeDisplayAware import dagger.Binds import dagger.Module import dagger.Provides @@ -66,6 +67,6 @@ interface QSTilesModule { companion object { - @Provides fun provideTilesTheme(context: Context): Theme = context.theme + @Provides fun provideTilesTheme(@ShadeDisplayAware context: Context): Theme = context.theme } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 8f6c4e743269..244f024625db 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -87,6 +87,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; +import com.android.systemui.shade.ShadeDisplayAware; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.LocationController; @@ -240,7 +241,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi } @Inject - public InternetDialogController(@NonNull Context context, UiEventLogger uiEventLogger, + public InternetDialogController(@ShadeDisplayAware Context context, UiEventLogger uiEventLogger, ActivityStarter starter, AccessPointController accessPointController, SubscriptionManager subscriptionManager, TelephonyManager telephonyManager, @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java index 89b9eee52f2a..0ab533bb9838 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java @@ -71,6 +71,7 @@ import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; +import com.android.systemui.shade.ShadeDisplayAware; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wifitrackerlib.WifiEntry; @@ -190,7 +191,7 @@ public class InternetDialogDelegate implements @AssistedInject public InternetDialogDelegate( - Context context, + @ShadeDisplayAware Context context, InternetDialogManager internetDialogManager, InternetDialogController internetDialogController, @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigMobileData, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileDefaultsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileDefaultsRepository.kt index 1546ec2c54bd..32fb1d18724d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileDefaultsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileDefaultsRepository.kt @@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults import com.android.systemui.qs.tiles.impl.di.QSTileScope +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -68,7 +69,7 @@ interface CustomTileDefaultsRepository { class CustomTileDefaultsRepositoryImpl @Inject constructor( - private val context: Context, + @ShadeDisplayAware private val context: Context, @Application applicationScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : CustomTileDefaultsRepository { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt index 0ebd6f2b4ac3..cd4938f01b63 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt @@ -28,6 +28,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.impl.di.QSTileScope +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope @@ -51,7 +52,7 @@ class CustomTilePackageUpdatesRepositoryImpl @Inject constructor( private val tileSpec: TileSpec.CustomTileSpec, - @Application private val context: Context, + @ShadeDisplayAware private val context: Context, @QSTileScope private val tileScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, ) : CustomTilePackageUpdatesRepository { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt index 60aa4ea4759f..c446865f31af 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt @@ -30,12 +30,16 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject @SysUISingleton class CustomTileMapper @Inject -constructor(private val context: Context, private val uriGrantsManager: IUriGrantsManager) : +constructor( + @ShadeDisplayAware private val context: Context, + private val uriGrantsManager: IUriGrantsManager +) : QSTileDataToStateMapper<CustomTileDataModel> { override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt index af2bb9d0d2f7..1153b5c67261 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt @@ -42,6 +42,7 @@ import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataMod import com.android.systemui.qs.tiles.impl.di.QSTileScope import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.settings.DisplayTracker +import com.android.systemui.shade.ShadeDisplayAware import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject import kotlin.coroutines.CoroutineContext @@ -51,7 +52,7 @@ import kotlinx.coroutines.withContext class CustomTileUserActionInteractor @Inject constructor( - private val context: Context, + @ShadeDisplayAware private val context: Context, private val tileSpec: TileSpec, private val qsTileLogger: QSTileLogger, private val windowManager: IWindowManager, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt index fc945851cdad..1a6876d0b765 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt @@ -30,6 +30,7 @@ import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileMode import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel import javax.inject.Inject @@ -37,9 +38,9 @@ import javax.inject.Inject class InternetTileMapper @Inject constructor( - @Main private val resources: Resources, + @ShadeDisplayAware private val resources: Resources, private val theme: Resources.Theme, - private val context: Context, + @ShadeDisplayAware private val context: Context, @Main private val handler: Handler, ) : QSTileDataToStateMapper<InternetTileModel> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt index 6fe3979fa446..6d10843decc0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt @@ -28,6 +28,7 @@ import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor @@ -54,7 +55,7 @@ import kotlinx.coroutines.flow.stateIn class InternetTileDataInteractor @Inject constructor( - private val context: Context, + @ShadeDisplayAware private val context: Context, @Application private val scope: CoroutineScope, airplaneModeRepository: AirplaneModeRepository, private val connectivityRepository: ConnectivityRepository, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt index 3e44258229f9..9b2880b6d47f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt @@ -28,6 +28,7 @@ import com.android.systemui.qs.tiles.ModesTile import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo @@ -42,7 +43,7 @@ import kotlinx.coroutines.flow.map class ModesTileDataInteractor @Inject constructor( - val context: Context, + @ShadeDisplayAware val context: Context, val zenModeInteractor: ZenModeInteractor, @Background val bgDispatcher: CoroutineDispatcher, ) : QSTileDataInteractor<ModesTileModel> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt index 252e3f84df6c..05bdf0a92679 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverDialogDelegate import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.settings.UserFileManager +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.DataSaverController import javax.inject.Inject @@ -42,7 +43,7 @@ import kotlinx.coroutines.withContext class DataSaverTileUserActionInteractor @Inject constructor( - @Application private val context: Context, + @ShadeDisplayAware private val context: Context, @Main private val coroutineContext: CoroutineContext, @Background private val backgroundContext: CoroutineContext, private val dataSaverController: DataSaverController, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt index c928e8af17fc..7af3576d8cd9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt @@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.LocationController @@ -38,7 +39,7 @@ import kotlinx.coroutines.flow.flowOf class UiModeNightTileDataInteractor @Inject constructor( - @Application private val context: Context, + @ShadeDisplayAware private val context: Context, private val configurationController: ConfigurationController, private val uiModeManager: UiModeManager, private val batteryController: BatteryController, diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt new file mode 100644 index 000000000000..bc15bbb5e57d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt @@ -0,0 +1,89 @@ +/* + * 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.systemui.reardisplay + +import android.content.Context +import android.hardware.devicestate.DeviceStateManager +import android.hardware.devicestate.feature.flags.Flags +import androidx.annotation.VisibleForTesting +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor +import com.android.systemui.statusbar.phone.SystemUIDialog +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map + +/** + * Provides a {@link com.android.systemui.statusbar.phone.SystemUIDialog} to be shown on the inner + * display when the device enters Rear Display Mode, containing an UI affordance to let the user + * know that the main content has moved to the outer display, as well as an UI affordance to cancel + * the Rear Display Mode. + */ +@SysUISingleton +class RearDisplayCoreStartable +@Inject +internal constructor( + private val context: Context, + private val deviceStateManager: DeviceStateManager, + private val rearDisplayStateInteractor: RearDisplayStateInteractor, + private val rearDisplayInnerDialogDelegateFactory: RearDisplayInnerDialogDelegate.Factory, + @Application private val scope: CoroutineScope, +) : CoreStartable, AutoCloseable { + + companion object { + private const val TAG: String = "RearDisplayCoreStartable" + } + + @VisibleForTesting var stateChangeListener: Job? = null + + override fun close() { + stateChangeListener?.cancel() + } + + override fun start() { + if (Flags.deviceStateRdmV2()) { + var dialog: SystemUIDialog? = null + + stateChangeListener = + rearDisplayStateInteractor.state + .map { + when (it) { + is RearDisplayStateInteractor.State.Enabled -> { + val rearDisplayContext = + context.createDisplayContext(it.innerDisplay) + val delegate = + rearDisplayInnerDialogDelegateFactory.create( + rearDisplayContext, + deviceStateManager::cancelStateRequest, + ) + dialog = delegate.createDialog().apply { show() } + } + + is RearDisplayStateInteractor.State.Disabled -> { + dialog?.dismiss() + dialog = null + } + } + } + .launchIn(scope) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt new file mode 100644 index 000000000000..2d6181aa04af --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt @@ -0,0 +1,62 @@ +/* + * 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.systemui.reardisplay + +import android.content.Context +import android.os.Bundle +import android.view.View +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIDialog +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** + * A {@link com.android.systemui.statusbar.phone.SystemUIDialog.Delegate} providing a dialog which + * lets the user know that the Rear Display Mode is active, and that the content has moved to the + * outer display. + */ +class RearDisplayInnerDialogDelegate +@AssistedInject +internal constructor( + private val systemUIDialogFactory: SystemUIDialog.Factory, + @Assisted private val rearDisplayContext: Context, + @Assisted private val onCanceledRunnable: Runnable, +) : SystemUIDialog.Delegate { + + @AssistedFactory + interface Factory { + fun create( + rearDisplayContext: Context, + onCanceledRunnable: Runnable, + ): RearDisplayInnerDialogDelegate + } + + override fun createDialog(): SystemUIDialog { + return systemUIDialogFactory.create(this, rearDisplayContext) + } + + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + dialog.apply { + setContentView(R.layout.activity_rear_display_front_screen_on) + setCanceledOnTouchOutside(false) + requireViewById<View>(R.id.button_cancel).setOnClickListener { + onCanceledRunnable.run() + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt index 6ab294dd9818..5fb9cb27f90f 100644 --- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt @@ -41,4 +41,10 @@ interface RearDisplayModule { fun bindRearDisplayDialogControllerConfigChanges( impl: RearDisplayDialogController ): ConfigurationListener + + /** Start RearDisplayCoreStartable. */ + @Binds + @IntoMap + @ClassKey(RearDisplayCoreStartable::class) + abstract fun bindRearDisplayCoreStartable(impl: RearDisplayCoreStartable): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java index 0738a03f8237..1ff0d9262476 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Flags; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.Icon; @@ -93,12 +94,22 @@ public class HybridConversationNotificationView extends HybridNotificationView { } mConversationSenderName = requireViewById(R.id.conversation_notification_sender); applyTextColor(mConversationSenderName, mSecondaryTextColor); - mFacePileSize = getResources() - .getDimensionPixelSize(R.dimen.conversation_single_line_face_pile_size); - mFacePileAvatarSize = getResources() - .getDimensionPixelSize(R.dimen.conversation_single_line_face_pile_avatar_size); - mSingleAvatarSize = getResources() - .getDimensionPixelSize(R.dimen.conversation_single_line_avatar_size); + if (Flags.notificationsRedesignTemplates()) { + mFacePileSize = getResources() + .getDimensionPixelSize(R.dimen.notification_2025_single_line_face_pile_size); + mFacePileAvatarSize = getResources() + .getDimensionPixelSize( + R.dimen.notification_2025_single_line_face_pile_avatar_size); + mSingleAvatarSize = getResources() + .getDimensionPixelSize(R.dimen.notification_2025_single_line_avatar_size); + } else { + mFacePileSize = getResources() + .getDimensionPixelSize(R.dimen.conversation_single_line_face_pile_size); + mFacePileAvatarSize = getResources() + .getDimensionPixelSize(R.dimen.conversation_single_line_face_pile_avatar_size); + mSingleAvatarSize = getResources() + .getDimensionPixelSize(R.dimen.conversation_single_line_avatar_size); + } mFacePileProtectionWidth = getResources().getDimensionPixelSize( R.dimen.conversation_single_line_face_pile_protection_width); mTransformationHelper.setCustomTransformation( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java index 09c034978977..e5e559f1a5da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java @@ -63,9 +63,8 @@ public class HybridGroupManager { private HybridNotificationView inflateHybridView(View contentView, ViewGroup parent) { Trace.beginSection("HybridGroupManager#inflateHybridView"); LayoutInflater inflater = LayoutInflater.from(mContext); - int layout = contentView instanceof ConversationLayout - ? R.layout.hybrid_conversation_notification - : R.layout.hybrid_notification; + int layout = HybridNotificationView.getLayoutResource( + /* isConversation = */ contentView instanceof ConversationLayout); HybridNotificationView hybrid = (HybridNotificationView) inflater.inflate(layout, parent, false); parent.addView(hybrid); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java index da8c4dc08bf0..61f4e96bf99a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row; import static android.app.Notification.COLOR_INVALID; import android.annotation.Nullable; +import android.app.Flags; import android.content.Context; import android.content.res.TypedArray; import android.text.TextUtils; @@ -73,6 +74,25 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout return mTextView; } + /** + * Get layout resource for this view based on {@param isConversation}. + */ + public static int getLayoutResource(boolean isConversation) { + if (Flags.notificationsRedesignTemplates()) { + if (isConversation) { + return R.layout.notification_2025_hybrid_conversation; + } else { + return R.layout.notification_2025_hybrid; + } + } else { + if (isConversation) { + return R.layout.hybrid_conversation_notification; + } else { + return R.layout.hybrid_notification; + } + } + } + @Override protected void onFinishInflate() { super.onFinishInflate(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index 2dcb706234b8..d0c033bb10b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -834,6 +834,15 @@ constructor( public?.let { it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_PUBLIC) } + if (android.app.Flags.notificationsRedesignAppIcons()) { + normalGroupHeader?.let { + it.layoutInflaterFactory = provider.provide(row, FLAG_GROUP_SUMMARY_HEADER) + } + minimizedGroupHeader?.let { + it.layoutInflaterFactory = + provider.provide(row, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) + } + } return this } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt index 4e26ae85100c..e702f10d7f50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt @@ -412,10 +412,7 @@ internal object SingleLineViewInflater { traceSection("SingleLineViewInflater#inflateSingleLineView") { val inflater = LayoutInflater.from(context) - val layoutRes: Int = - if (isConversation) - com.android.systemui.res.R.layout.hybrid_conversation_notification - else com.android.systemui.res.R.layout.hybrid_notification + val layoutRes: Int = HybridNotificationView.getLayoutResource(isConversation) view = inflater.inflate(layoutRes, /* root= */ null) as HybridNotificationView if (view == null) { Log.wtf(TAG, "Single-line view inflation result is null for entry: ${entry.logKey}") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java index 7ef1e416aa19..5837a498e44f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; +import java.util.function.Consumer; import javax.inject.Inject; @@ -63,17 +64,21 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { @Override public void addCallback(@NonNull Callback callback) { - mCallbacks.add(callback); - if (mCallbacks.size() == 1) { - setListening(true); + synchronized (mCallbacks) { + mCallbacks.add(callback); + if (mCallbacks.size() == 1) { + setListening(true); + } + callback.onManagedProfileChanged(); } - callback.onManagedProfileChanged(); } @Override public void removeCallback(@NonNull Callback callback) { - if (mCallbacks.remove(callback) && mCallbacks.size() == 0) { - setListening(false); + synchronized (mCallbacks) { + if (mCallbacks.remove(callback) && mCallbacks.size() == 0) { + setListening(false); + } } } @@ -109,10 +114,7 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { } private void notifyManagedProfileRemoved() { - ArrayList<Callback> copy = new ArrayList<>(mCallbacks); - for (Callback callback : copy) { - callback.onManagedProfileRemoved(); - } + notifyCallbacks(Callback::onManagedProfileRemoved); } public boolean hasActiveProfile() { @@ -136,6 +138,16 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { } } + private void notifyCallbacks(Consumer<Callback> method) { + ArrayList<Callback> copy; + synchronized (mCallbacks) { + copy = new ArrayList<>(mCallbacks); + } + for (Callback callback : copy) { + method.accept(callback); + } + } + private void setListening(boolean listening) { if (mListening == listening) { return; @@ -154,19 +166,13 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { @Override public void onUserChanged(int newUser, @NonNull Context userContext) { reloadManagedProfiles(); - ArrayList<Callback> copy = new ArrayList<>(mCallbacks); - for (Callback callback : copy) { - callback.onManagedProfileChanged(); - } + notifyCallbacks(Callback::onManagedProfileChanged); } @Override public void onProfilesChanged(@NonNull List<UserInfo> profiles) { reloadManagedProfiles(); - ArrayList<Callback> copy = new ArrayList<>(mCallbacks); - for (Callback callback : copy) { - callback.onManagedProfileChanged(); - } + notifyCallbacks(Callback::onManagedProfileChanged); } } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index d371acf86a28..66a900bd72d8 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -18,6 +18,7 @@ package com.android.systemui.touchpad.tutorial.ui.composable import android.content.res.Configuration import androidx.compose.foundation.background +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -36,8 +37,12 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInteropFilter @@ -49,6 +54,7 @@ import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.gesture.isFourFingerTouchpadSwipe import com.android.systemui.touchpad.tutorial.ui.gesture.isThreeFingerTouchpadSwipe +import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen @Composable fun TutorialSelectionScreen( @@ -56,6 +62,7 @@ fun TutorialSelectionScreen( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, onDoneButtonClicked: () -> Unit, + lastSelectedScreen: Screen, ) { Column( verticalArrangement = Arrangement.Center, @@ -80,6 +87,7 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, modifier = Modifier.weight(1f).padding(60.dp), + lastSelectedScreen, ) } else -> { @@ -88,6 +96,7 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, modifier = Modifier.weight(1f).padding(60.dp), + lastSelectedScreen, ) } } @@ -105,6 +114,7 @@ private fun HorizontalSelectionButtons( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier, + lastSelectedScreen: Screen, ) { Row( horizontalArrangement = Arrangement.spacedBy(20.dp), @@ -116,6 +126,7 @@ private fun HorizontalSelectionButtons( onHomeTutorialClicked, onRecentAppsTutorialClicked, modifier = Modifier.weight(1f).fillMaxSize(), + lastSelectedScreen, ) } } @@ -126,6 +137,7 @@ private fun VerticalSelectionButtons( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier, + lastSelectedScreen: Screen, ) { Column( verticalArrangement = Arrangement.spacedBy(20.dp), @@ -137,6 +149,7 @@ private fun VerticalSelectionButtons( onHomeTutorialClicked, onRecentAppsTutorialClicked, modifier = Modifier.weight(1f).fillMaxSize(), + lastSelectedScreen, ) } } @@ -147,14 +160,26 @@ private fun ThreeTutorialButtons( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, modifier: Modifier = Modifier, + lastSelectedScreen: Screen, ) { + val homeFocusRequester = remember { FocusRequester() } + val backFocusRequester = remember { FocusRequester() } + val recentAppsFocusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + when (lastSelectedScreen) { + Screen.HOME_GESTURE -> homeFocusRequester.requestFocus() + Screen.BACK_GESTURE -> backFocusRequester.requestFocus() + Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus() + else -> {} // No-Op. + } + } TutorialButton( text = stringResource(R.string.touchpad_tutorial_home_gesture_button), icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon), iconColor = MaterialTheme.colorScheme.onPrimary, onClick = onHomeTutorialClicked, backgroundColor = MaterialTheme.colorScheme.primary, - modifier = modifier, + modifier = modifier.focusRequester(homeFocusRequester).focusable(), ) TutorialButton( text = stringResource(R.string.touchpad_tutorial_back_gesture_button), @@ -162,7 +187,7 @@ private fun ThreeTutorialButtons( iconColor = MaterialTheme.colorScheme.onTertiary, onClick = onBackTutorialClicked, backgroundColor = MaterialTheme.colorScheme.tertiary, - modifier = modifier, + modifier = modifier.focusRequester(backFocusRequester).focusable(), ) TutorialButton( text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button), @@ -170,7 +195,7 @@ private fun ThreeTutorialButtons( iconColor = MaterialTheme.colorScheme.onSecondary, onClick = onRecentAppsTutorialClicked, backgroundColor = MaterialTheme.colorScheme.secondary, - modifier = modifier, + modifier = modifier.focusRequester(recentAppsFocusRequester).focusable(), ) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt index e1f7bd59005c..6662fc5c127c 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt @@ -24,12 +24,16 @@ import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialMetricsLogger +import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen @@ -54,6 +58,7 @@ constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() + setTitle(getString(R.string.launch_touchpad_tutorial_notification_content)) setContent { PlatformTheme { TouchpadTutorialScreen(vm, closeTutorial = ::finishTutorial) } } @@ -82,13 +87,24 @@ constructor( @Composable fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> Unit) { val activeScreen by vm.screen.collectAsStateWithLifecycle(STARTED) + var lastSelectedScreen by remember { mutableStateOf(TUTORIAL_SELECTION) } when (activeScreen) { TUTORIAL_SELECTION -> TutorialSelectionScreen( - onBackTutorialClicked = { vm.goTo(BACK_GESTURE) }, - onHomeTutorialClicked = { vm.goTo(HOME_GESTURE) }, - onRecentAppsTutorialClicked = { vm.goTo(RECENT_APPS_GESTURE) }, + onBackTutorialClicked = { + lastSelectedScreen = BACK_GESTURE + vm.goTo(BACK_GESTURE) + }, + onHomeTutorialClicked = { + lastSelectedScreen = HOME_GESTURE + vm.goTo(HOME_GESTURE) + }, + onRecentAppsTutorialClicked = { + lastSelectedScreen = RECENT_APPS_GESTURE + vm.goTo(RECENT_APPS_GESTURE) + }, onDoneButtonClicked = closeTutorial, + lastSelectedScreen, ) BACK_GESTURE -> BackGestureTutorialScreen( diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt index 63a5b3f1e6f6..fb09ab4462ed 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt @@ -28,6 +28,7 @@ import java.util.function.Consumer import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext +import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job @@ -64,6 +65,28 @@ class JavaAdapter @Inject constructor(@Application private val scope: CoroutineS ): StateFlow<T> { return flow.stateIn(scope, started, initialValue) } + + /** Call suspend functions from Java */ + fun <T, R> callSuspend( + suspendFunction: suspend (T) -> R, + arg: T, + onSuccess: (R) -> Unit, + onCancel: (CancellationException) -> Unit, + onFailure: (Throwable) -> Unit, + ): Job = + scope.launch { + val result = + try { + suspendFunction(arg) + } catch (ex: CancellationException) { + onCancel(ex) + return@launch + } catch (ex: Throwable) { + onFailure(ex) + return@launch + } + onSuccess(result) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt index 2a9b1b97b48f..e5c1e7daa25a 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt @@ -17,14 +17,16 @@ package com.android.systemui.util.kotlin import android.os.Handler +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Tracing import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.util.settings.SettingsSingleThreadBackground import dagger.Module import dagger.Provides +import java.util.concurrent.Executor +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -33,8 +35,6 @@ import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.newFixedThreadPoolContext import kotlinx.coroutines.plus -import java.util.concurrent.Executor -import kotlin.coroutines.CoroutineContext private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true @@ -62,7 +62,7 @@ class SysUICoroutinesModule { @Background @Deprecated( "Use @Background CoroutineContext instead", - ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext") + ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext"), ) fun bgDispatcher(): CoroutineDispatcher { return if (LIMIT_BACKGROUND_DISPATCHER_THREADS) { @@ -73,7 +73,7 @@ class SysUICoroutinesModule { // code on those. newFixedThreadPoolContext( nThreads = Runtime.getRuntime().availableProcessors(), - name = "SystemUIBg" + name = "SystemUIBg", ) } else { Dispatchers.IO @@ -89,10 +89,17 @@ class SysUICoroutinesModule { } @Provides + @SysUISingleton + @SettingsSingleThreadBackground + fun settingsScope(@Background bgDispatcher: CoroutineDispatcher): CoroutineScope { + return CoroutineScope(bgDispatcher + newTracingContext("SettingsProxy")) + } + + @Provides @Background @SysUISingleton fun bgCoroutineContext( - @Background bgCoroutineDispatcher: CoroutineDispatcher, + @Background bgCoroutineDispatcher: CoroutineDispatcher ): CoroutineContext { return bgCoroutineDispatcher } @@ -103,7 +110,7 @@ class SysUICoroutinesModule { @UiBackground @Deprecated( "Use @UiBackground CoroutineContext instead", - ReplaceWith("uiBgCoroutineContext()", "kotlin.coroutines.CoroutineContext") + ReplaceWith("uiBgCoroutineContext()", "kotlin.coroutines.CoroutineContext"), ) fun uiBgDispatcher(@UiBackground uiBgExecutor: Executor): CoroutineDispatcher = uiBgExecutor.asCoroutineDispatcher() @@ -112,7 +119,7 @@ class SysUICoroutinesModule { @UiBackground @SysUISingleton fun uiBgCoroutineContext( - @UiBackground uiBgCoroutineDispatcher: CoroutineDispatcher, + @UiBackground uiBgCoroutineDispatcher: CoroutineDispatcher ): CoroutineContext { return uiBgCoroutineDispatcher } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index b7058722e2b3..68bffeefb0f0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -18,8 +18,6 @@ package com.android.systemui.volume; import static android.media.AudioManager.RINGER_MODE_NORMAL; -import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix; - import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.NotificationManager; @@ -79,9 +77,13 @@ import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.concurrency.ThreadFactory; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.volume.domain.interactor.AudioSharingInteractor; +import com.android.systemui.volume.shared.VolumeLogger; import dalvik.annotation.optimization.NeverCompile; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; + import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; @@ -155,6 +157,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private final VibratorHelper mVibrator; private final AudioSharingInteractor mAudioSharingInteractor; private final JavaAdapter mJavaAdapter; + private final VolumeLogger mVolumeLogger; private final boolean mHasVibrator; private boolean mShowA11yStream; private boolean mShowVolumeDialog; @@ -202,7 +205,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa UserTracker userTracker, DumpManager dumpManager, AudioSharingInteractor audioSharingInteractor, - JavaAdapter javaAdapter + JavaAdapter javaAdapter, + VolumeLogger volumeLogger ) { mContext = context.getApplicationContext(); mPackageManager = packageManager; @@ -216,6 +220,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW); mAudioSharingInteractor = audioSharingInteractor; mJavaAdapter = javaAdapter; + mVolumeLogger = volumeLogger; mAudio = audioManager; mNoMan = notificationManager; mObserver = new SettingObserver(mWorker); @@ -293,15 +298,28 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } catch (SecurityException e) { Log.w(TAG, "No access to media sessions", e); } - if (volumeDialogAudioSharingFix()) { - Slog.d(TAG, "Start collect volume changes in audio sharing"); - mJavaAdapter.alwaysCollectFlow( - mAudioSharingInteractor.getVolume(), - this::handleAudioSharingStreamVolumeChanges); - mJavaAdapter.alwaysCollectFlow( - mAudioSharingInteractor.isInAudioSharing(), - inSharing -> mInAudioSharing = inSharing); - } + Function1<Throwable, Unit> errorCallback = (ex) -> { + mVolumeLogger.onAudioSharingAvailabilityRequestedError("register()", + ex.getMessage()); + return null; + }; + var unused = + mJavaAdapter.<Context, Boolean>callSuspend( + mAudioSharingInteractor::audioSharingVolumeBarAvailable, mContext, + result -> { + if (result) { + Slog.d(TAG, "Start collect volume changes in audio sharing"); + mJavaAdapter.alwaysCollectFlow( + mAudioSharingInteractor.getVolume(), + volume -> handleAudioSharingStreamVolumeChanges(volume)); + mJavaAdapter.alwaysCollectFlow( + mAudioSharingInteractor.isInAudioSharing(), + inSharing -> mInAudioSharing = inSharing); + } + return null; + }, + errorCallback, + errorCallback); } public void setVolumePolicy(VolumePolicy policy) { @@ -588,18 +606,34 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mState.activeStream = activeStream; Events.writeEvent(Events.EVENT_ACTIVE_STREAM_CHANGED, activeStream); if (D.BUG) Log.d(TAG, "updateActiveStreamW " + activeStream); - final int s = - activeStream - < (volumeDialogAudioSharingFix() - ? DYNAMIC_STREAM_BROADCAST - : DYNAMIC_STREAM_REMOTE_START_INDEX) - ? activeStream - : -1; - if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s); - mAudio.forceVolumeControlStream(s); + Function1<Throwable, Unit> errorCallback = (ex) -> { + mVolumeLogger.onAudioSharingAvailabilityRequestedError( + "updateActiveStreamW", + ex.getMessage()); + forceVolumeControlStreamW(activeStream, false); + return null; + }; + var unused = + mJavaAdapter.<Context, Boolean>callSuspend( + mAudioSharingInteractor::audioSharingVolumeBarAvailable, mContext, + result -> { + forceVolumeControlStreamW(activeStream, result); + return null; + }, + errorCallback, + errorCallback); return true; } + private void forceVolumeControlStreamW(int activeStream, + boolean audioSharingVolumeBarAvailable) { + final int dynamicStartIdx = audioSharingVolumeBarAvailable ? DYNAMIC_STREAM_BROADCAST + : DYNAMIC_STREAM_REMOTE_START_INDEX; + final int s = activeStream < dynamicStartIdx ? activeStream : -1; + if (D.BUG) Log.d(TAG, "forceVolumeControlStream " + s); + mWorker.post(() -> mAudio.forceVolumeControlStream(s)); + } + private StreamState streamStateW(int stream) { StreamState ss = mState.states.get(stream); if (ss == null) { @@ -773,10 +807,26 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } private void onSetStreamVolumeW(int stream, int level) { - if (D.BUG) Log.d(TAG, "onSetStreamVolume " + stream + " level=" + level); - if (volumeDialogAudioSharingFix() && stream == DYNAMIC_STREAM_BROADCAST) { - Slog.d(TAG, "onSetStreamVolumeW set broadcast stream level = " + level); - mAudioSharingInteractor.setStreamVolume(level); + if (D.BUG) Log.d(TAG, "onSetStreamVolumeW " + stream + " level=" + level); + + if (stream == DYNAMIC_STREAM_BROADCAST) { + Function1<Throwable, Unit> errorCallback = (ex) -> { + mVolumeLogger.onAudioSharingAvailabilityRequestedError( + "onSetStreamVolumeW", + ex.getMessage()); + return null; + }; + var unused = + mJavaAdapter.<Context, Boolean>callSuspend( + mAudioSharingInteractor::audioSharingVolumeBarAvailable, mContext, + result -> { + if (result) { + mAudioSharingInteractor.setStreamVolume(level); + } + return null; + }, + errorCallback, + errorCallback); return; } if (stream >= DYNAMIC_STREAM_REMOTE_START_INDEX) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index bd4c4639e57a..a4e46f667329 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; +import static com.android.settingslib.flags.Flags.audioSharingDeveloperOption; import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix; import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -121,9 +122,9 @@ import com.android.systemui.Flags; import com.android.systemui.Prefs; import com.android.systemui.dump.DumpManager; import com.android.systemui.haptics.slider.HapticSlider; +import com.android.systemui.haptics.slider.HapticSliderPlugin; import com.android.systemui.haptics.slider.HapticSliderViewBinder; import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig; -import com.android.systemui.haptics.slider.HapticSliderPlugin; import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig; import com.android.systemui.media.dialog.MediaOutputDialogManager; import com.android.systemui.plugins.VolumeDialog; @@ -1685,10 +1686,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } // Always show the stream for audio sharing if it exists. - if (volumeDialogAudioSharingFix() + if ((volumeDialogAudioSharingFix() || audioSharingDeveloperOption()) && row.ss != null && mContext.getString(R.string.audio_sharing_description) - .equals(row.ss.remoteLabel)) { + .equals(row.ss.remoteLabel)) { return true; } @@ -1894,9 +1895,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, if (!ss.dynamic) continue; mDynamic.put(stream, true); if (findRow(stream) == null) { - if (volumeDialogAudioSharingFix() - && mContext.getString(R.string.audio_sharing_description) - .equals(ss.remoteLabel)) { + if ((volumeDialogAudioSharingFix() || audioSharingDeveloperOption()) + && (mContext.getString(R.string.audio_sharing_description) + .equals(ss.remoteLabel))) { addRow( stream, R.drawable.ic_volume_media_bt, diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 536403ca970e..3ce1bde16afd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -16,8 +16,6 @@ package com.android.systemui.volume; -import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix; - import android.content.Context; import android.content.res.Configuration; import android.util.Log; @@ -28,7 +26,12 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.volume.domain.interactor.AudioSharingInteractor; +import com.android.systemui.volume.shared.VolumeLogger; + +import kotlin.Unit; +import kotlin.jvm.functions.Function1; import java.io.PrintWriter; @@ -44,16 +47,22 @@ public class VolumeUI implements CoreStartable, ConfigurationController.Configur private VolumeDialogComponent mVolumeComponent; private AudioSharingInteractor mAudioSharingInteractor; private AudioRepository mAudioRepository; + private JavaAdapter mJavaAdapter; + private VolumeLogger mVolumeLogger; @Inject public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent, AudioRepository audioRepository, - AudioSharingInteractor audioSharingInteractor) { + AudioSharingInteractor audioSharingInteractor, + JavaAdapter javaAdapter, + VolumeLogger volumeLogger) { mContext = context; mVolumeComponent = volumeDialogComponent; mAudioRepository = audioRepository; mAudioSharingInteractor = audioSharingInteractor; + mJavaAdapter = javaAdapter; + mVolumeLogger = volumeLogger; } @Override @@ -67,9 +76,22 @@ public class VolumeUI implements CoreStartable, ConfigurationController.Configur mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning); setDefaultVolumeController(); - if (volumeDialogAudioSharingFix()) { - mAudioSharingInteractor.handlePrimaryGroupChange(); - } + Function1<Throwable, Unit> errorCallback = (ex) -> { + mVolumeLogger.onAudioSharingAvailabilityRequestedError("start()", + ex.getMessage()); + return null; + }; + var unused = + mJavaAdapter.<Context, Boolean>callSuspend( + mAudioSharingInteractor::audioSharingVolumeBarAvailable, mContext, + result -> { + if (result) { + mAudioSharingInteractor.handlePrimaryGroupChange(); + } + return null; + }, + errorCallback, + errorCallback); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt index d5b8597e36ed..d71ddb380843 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt @@ -86,7 +86,10 @@ interface AudioModule { @Background coroutineContext: CoroutineContext, volumeLogger: VolumeLogger, ): AudioSharingRepository = - if (Flags.enableLeAudioSharing() && localBluetoothManager != null) { + if ( + (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) && + localBluetoothManager != null + ) { AudioSharingRepositoryImpl( contentResolver, localBluetoothManager, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt index 1c80887dd3e8..f6f1e8d22133 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt @@ -36,7 +36,7 @@ interface AudioSharingModule { impl: Lazy<AudioSharingInteractorImpl>, emptyImpl: Lazy<AudioSharingInteractorEmptyImpl>, ): AudioSharingInteractor = - if (Flags.volumeDialogAudioSharingFix()) { + if (Flags.volumeDialogAudioSharingFix() || Flags.audioSharingDeveloperOption()) { impl.get() } else { emptyImpl.get() diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt index 7af4258930cb..c904ac5e3ae1 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.runningReduce import kotlinx.coroutines.flow.stateIn private const val DEFAULT_STREAM = AudioManager.STREAM_MUSIC @@ -54,13 +55,17 @@ constructor( volumeDialogStateInteractor.volumeDialogState .filter { it.streamModels.isNotEmpty() } .map { stateModel -> - stateModel.streamModels.values - .filter { streamModel -> shouldShowSliders(stateModel, streamModel) } - .sortedWith(streamsSorter) + val sliderTypes = + stateModel.streamModels.values + .filter { streamModel -> shouldShowSliders(stateModel, streamModel) } + .sortedWith(streamsSorter) + .map { model -> model.toType() } + LinkedHashSet(sliderTypes) } - .map { models -> - val sliderTypes: List<VolumeDialogSliderType> = - models.map { model -> model.toType() } + .runningReduce { sliderTypes, newSliderTypes -> + newSliderTypes.apply { addAll(sliderTypes) } + } + .map { sliderTypes -> VolumeDialogSlidersModel( slider = sliderTypes.first(), floatingSliders = sliderTypes.drop(1), @@ -84,7 +89,7 @@ constructor( // Always show the stream for audio sharing if it exists. if ( - Flags.volumeDialogAudioSharingFix() && + (Flags.volumeDialogAudioSharingFix() || Flags.audioSharingDeveloperOption()) && streamModel.stream == VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST ) { return true diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt index 293be94638db..7da041e7ef19 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt @@ -17,8 +17,12 @@ package com.android.systemui.volume.domain.interactor import android.bluetooth.BluetoothCsipSetCoordinator +import android.content.Context import android.media.AudioManager.STREAM_MUSIC import androidx.annotation.IntRange +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.settingslib.bluetooth.BluetoothUtils +import com.android.settingslib.flags.Flags import com.android.settingslib.volume.data.repository.AudioSharingRepository import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN @@ -38,7 +42,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext interface AudioSharingInteractor { @@ -54,6 +57,8 @@ interface AudioSharingInteractor { /** Audio sharing secondary headset max volume. */ val volumeMax: Int + suspend fun audioSharingVolumeBarAvailable(@Application context: Context): Boolean + /** Set the volume of the secondary headset in audio sharing. */ fun setStreamVolume( @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong()) @@ -78,7 +83,7 @@ constructor( @Application private val coroutineScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, private val audioVolumeInteractor: AudioVolumeInteractor, - private val audioSharingRepository: AudioSharingRepository + private val audioSharingRepository: AudioSharingRepository, ) : AudioSharingInteractor { override val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing @@ -95,6 +100,12 @@ constructor( override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX + override suspend fun audioSharingVolumeBarAvailable(@Application context: Context): Boolean = + withContext(backgroundCoroutineContext) { + (Flags.volumeDialogAudioSharingFix() && BluetoothUtils.isAudioSharingEnabled()) || + BluetoothUtils.isAudioSharingPreviewEnabled(context.contentResolver) + } + override fun setStreamVolume( @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong()) level: Int @@ -141,6 +152,9 @@ class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingIntera override val volumeMin: Int = EMPTY_VOLUME override val volumeMax: Int = EMPTY_VOLUME + override suspend fun audioSharingVolumeBarAvailable(@Application context: Context): Boolean = + false + override fun setStreamVolume( @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong()) level: Int diff --git a/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt index d6b159e9b13a..3847df66c7f7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt @@ -41,7 +41,7 @@ class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuff str1 = audioStream.toString() int1 = volume }, - { "Set volume: stream=$str1 volume=$int1" } + { "Set volume: stream=$str1 volume=$int1" }, ) } @@ -53,7 +53,7 @@ class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuff str1 = audioStream.toString() int1 = model.volume }, - { "Volume update received: stream=$str1 volume=$int1" } + { "Volume update received: stream=$str1 volume=$int1" }, ) } @@ -62,7 +62,7 @@ class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuff TAG, LogLevel.DEBUG, { bool1 = state }, - { "Audio sharing state update: state=$bool1" } + { "Audio sharing state update: state=$bool1" }, ) } @@ -71,7 +71,7 @@ class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuff TAG, LogLevel.DEBUG, { int1 = groupId }, - { "Secondary group id in audio sharing update: groupId=$int1" } + { "Secondary group id in audio sharing update: groupId=$int1" }, ) } @@ -80,11 +80,23 @@ class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuff TAG, LogLevel.DEBUG, { str1 = map.toString() }, - { "Volume map update: map=$str1" } + { "Volume map update: map=$str1" }, ) } override fun onSetDeviceVolumeRequested(volume: Int) { logBuffer.log(TAG, LogLevel.DEBUG, { int1 = volume }, { "Set device volume: volume=$int1" }) } + + override fun onAudioSharingAvailabilityRequestedError(requestFrom: String, e: String) { + logBuffer.log( + TAG, + LogLevel.WARNING, + { + str1 = requestFrom + str1 = e + }, + { "$str1, fail to check audio sharing availability: e=$str2" }, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt index 6e9b24f1433e..4ca84c58f6d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt @@ -37,7 +37,6 @@ import com.google.common.truth.Truth.assertThat import java.io.File import java.io.FileInputStream import java.io.FileOutputStream -import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test @@ -63,22 +62,18 @@ class CommunalBackupHelperTest : SysuiTestCase() { Room.inMemoryDatabaseBuilder(context, CommunalDatabase::class.java) .allowMainThreadQueries() .build() + onTeardown { database.close() } CommunalDatabase.setInstance(database) dao = database.communalWidgetDao() backupUtils = CommunalBackupUtils(context) backupDataFile = File(context.cacheDir, "backup_data_file") + onTeardown { backupDataFile.delete() } underTest = CommunalBackupHelper(UserHandle.SYSTEM, backupUtils) } - @After - fun teardown() { - backupDataFile.delete() - database.close() - } - @Test @EnableFlags(Flags.FLAG_COMMUNAL_HUB) fun backupAndRestoreCommunalHub() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt index f8d848139039..a9b6dd16cd95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat @@ -37,7 +38,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ShortcutCustomizationViewModelTest : SysuiTestCase() { - private val kosmos = Kosmos() + private val kosmos = Kosmos().also { it.testCase = this } private val testScope = kosmos.testScope private val viewModel = kosmos.shortcutCustomizationViewModelFactory.create() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt index 7383faf2fd78..feae901114e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt @@ -278,7 +278,11 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { testScope.runTest { fakeSystemSource.setGroups( groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"), - groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"), + groupWithShortcutLabels( + "second foO shortcut2", + "second bar shortcut2", + groupLabel = SECOND_SIMPLE_GROUP_LABEL, + ), ) fakeMultiTaskingSource.setGroups( groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1") @@ -298,7 +302,10 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { ShortcutCategory( System, subCategoryWithShortcutLabels("first Foo shortcut1"), - subCategoryWithShortcutLabels("second foO shortcut2"), + subCategoryWithShortcutLabels( + "second foO shortcut2", + subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL, + ), ), ), ShortcutCategoryUi( @@ -380,16 +387,23 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { assertThat(activeUiState.defaultSelectedCategory).isInstanceOf(CurrentApp::class.java) } - private fun groupWithShortcutLabels(vararg shortcutLabels: String) = - KeyboardShortcutGroup(SIMPLE_GROUP_LABEL, shortcutLabels.map { simpleShortcutInfo(it) }) - .apply { packageName = "test.package.name" } + private fun groupWithShortcutLabels( + vararg shortcutLabels: String, + groupLabel: String = FIRST_SIMPLE_GROUP_LABEL, + ) = + KeyboardShortcutGroup(groupLabel, shortcutLabels.map { simpleShortcutInfo(it) }).apply { + packageName = "test.package.name" + } private fun simpleShortcutInfo(label: String) = KeyboardShortcutInfo(label, KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON) - private fun subCategoryWithShortcutLabels(vararg shortcutLabels: String) = + private fun subCategoryWithShortcutLabels( + vararg shortcutLabels: String, + subCategoryLabel: String = FIRST_SIMPLE_GROUP_LABEL, + ) = ShortcutSubCategory( - label = SIMPLE_GROUP_LABEL, + label = subCategoryLabel, shortcuts = shortcutLabels.map { simpleShortcut(it) }, ) @@ -402,6 +416,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { } companion object { - private const val SIMPLE_GROUP_LABEL = "simple group" + private const val FIRST_SIMPLE_GROUP_LABEL = "simple group 1" + private const val SECOND_SIMPLE_GROUP_LABEL = "simple group 2" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt new file mode 100644 index 000000000000..c8faa81adffa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt @@ -0,0 +1,112 @@ +/* + * 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.systemui.reardisplay + +import android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_RDM_V2 +import android.hardware.display.rearDisplay +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.view.Display +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.deviceStateManager +import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.rearDisplayInnerDialogDelegateFactory +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** atest SystemUITests:com.android.systemui.reardisplay.RearDisplayCoreStartableTest */ +@SmallTest +@kotlinx.coroutines.ExperimentalCoroutinesApi +class RearDisplayCoreStartableTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val mockDelegate: RearDisplayInnerDialogDelegate = mock() + private val mockDialog: SystemUIDialog = mock() + + private val fakeRearDisplayStateInteractor = FakeRearDisplayStateInteractor(kosmos) + private val impl = + RearDisplayCoreStartable( + mContext, + kosmos.deviceStateManager, + fakeRearDisplayStateInteractor, + kosmos.rearDisplayInnerDialogDelegateFactory, + kosmos.testScope, + ) + + @Before + fun setup() { + whenever(kosmos.rearDisplay.flags).thenReturn(Display.FLAG_REAR) + whenever(kosmos.rearDisplay.displayAdjustments) + .thenReturn(mContext.display.displayAdjustments) + whenever(kosmos.rearDisplayInnerDialogDelegateFactory.create(any(), any())) + .thenReturn(mockDelegate) + whenever(mockDelegate.createDialog()).thenReturn(mockDialog) + } + + @Test + @DisableFlags(FLAG_DEVICE_STATE_RDM_V2) + fun testWhenFlagDisabled() = + kosmos.runTest { + impl.use { + it.start() + assertThat(impl.stateChangeListener).isNull() + } + } + + @Test + @EnableFlags(FLAG_DEVICE_STATE_RDM_V2) + fun testShowAndDismissDialog() = + kosmos.runTest { + impl.use { + it.start() + fakeRearDisplayStateInteractor.emitRearDisplay() + verify(mockDialog).show() + verify(mockDialog, never()).dismiss() + + fakeRearDisplayStateInteractor.emitDisabled() + verify(mockDialog).dismiss() + } + } + + private class FakeRearDisplayStateInteractor(private val kosmos: Kosmos) : + RearDisplayStateInteractor { + private val stateFlow = MutableSharedFlow<RearDisplayStateInteractor.State>() + + suspend fun emitRearDisplay() = + stateFlow.emit(RearDisplayStateInteractor.State.Enabled(kosmos.rearDisplay)) + + suspend fun emitDisabled() = stateFlow.emit(RearDisplayStateInteractor.State.Disabled) + + override val state: Flow<RearDisplayStateInteractor.State> + get() = stateFlow + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt new file mode 100644 index 000000000000..60588802ffa9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt @@ -0,0 +1,65 @@ +/* + * 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.systemui.reardisplay + +import android.testing.TestableLooper +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.systemUIDialogDotFactory +import com.android.systemui.testKosmos +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Test +import org.mockito.Mockito.verify +import org.mockito.kotlin.mock + +/** atest SystemUITests:com.android.systemui.reardisplay.RearDisplayInnerDialogDelegateTest */ +@SmallTest +@TestableLooper.RunWithLooper +class RearDisplayInnerDialogDelegateTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + @Test + fun testShowAndDismissDialog() { + val dialogDelegate = + RearDisplayInnerDialogDelegate(kosmos.systemUIDialogDotFactory, mContext) {} + + val dialog = dialogDelegate.createDialog() + dialog.show() + assertTrue(dialog.isShowing) + + dialog.dismiss() + assertFalse(dialog.isShowing) + } + + @Test + fun testCancel() { + val mockCallback = mock<Runnable>() + RearDisplayInnerDialogDelegate(kosmos.systemUIDialogDotFactory, mContext) { + mockCallback.run() + } + .createDialog() + .apply { + show() + findViewById<View>(R.id.button_cancel).performClick() + verify(mockCallback).run() + } + } +} diff --git a/packages/SystemUI/tests/utils/src/android/content/ClipboardManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ClipboardManagerKosmos.kt new file mode 100644 index 000000000000..379c00842b62 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/content/ClipboardManagerKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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 android.content + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +val Kosmos.clipboardManager by Kosmos.Fixture { mock<ClipboardManager>() } diff --git a/packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt index 796ec9400249..45dcb28b23ee 100644 --- a/packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/android/hardware/display/DisplayManagerKosmos.kt @@ -16,7 +16,12 @@ package android.hardware.display +import android.view.Display import com.android.systemui.kosmos.Kosmos -import com.android.systemui.util.mockito.mock +import org.mockito.kotlin.mock val Kosmos.displayManager by Kosmos.Fixture { mock<DisplayManager>() } + +val Kosmos.defaultDisplay: Display by Kosmos.Fixture { mock<Display>() } + +val Kosmos.rearDisplay: Display by Kosmos.Fixture { mock<Display>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/DeviceStateManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/DeviceStateManagerKosmos.kt index 9c55820b797c..b8a095eae23e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/DeviceStateManagerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/DeviceStateManagerKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui import android.hardware.devicestate.DeviceState import android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY +import android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED @@ -44,7 +45,7 @@ val Kosmos.foldedDeviceStateList by .setSystemProperties( setOf( PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY, - PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP + PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP, ) ) .setPhysicalProperties( @@ -57,7 +58,7 @@ val Kosmos.foldedDeviceStateList by .setSystemProperties( setOf( PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY, - PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP + PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP, ) ) .setPhysicalProperties( @@ -70,14 +71,14 @@ val Kosmos.foldedDeviceStateList by .setSystemProperties( setOf( PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY, - PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP + PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP, ) ) .setPhysicalProperties( setOf(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED) ) .build() - ) + ), ) } @@ -88,7 +89,7 @@ val Kosmos.halfFoldedDeviceState by .setSystemProperties( setOf( PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY, - PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE + PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE, ) ) .setPhysicalProperties( @@ -105,7 +106,7 @@ val Kosmos.unfoldedDeviceState by .setSystemProperties( setOf( PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY, - PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE + PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE, ) ) .setPhysicalProperties(setOf(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN)) @@ -120,7 +121,22 @@ val Kosmos.rearDisplayDeviceState by .setSystemProperties( setOf( PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY, - PROPERTY_FEATURE_REAR_DISPLAY + PROPERTY_FEATURE_REAR_DISPLAY, + ) + ) + .build() + ) + } + +val Kosmos.rearDisplayOuterDefaultDeviceState by + Kosmos.Fixture { + DeviceState( + DeviceState.Configuration.Builder(5 /* identifier */, "REAR_DISPLAY") + .setSystemProperties( + setOf( + PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY, + PROPERTY_FEATURE_REAR_DISPLAY, + PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT, ) ) .build() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/RearDisplayInnerDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/RearDisplayInnerDialogDelegateKosmos.kt new file mode 100644 index 000000000000..6f5985536fe7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/RearDisplayInnerDialogDelegateKosmos.kt @@ -0,0 +1,24 @@ +/* + * 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.systemui + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.reardisplay.RearDisplayInnerDialogDelegate +import org.mockito.kotlin.mock + +val Kosmos.rearDisplayInnerDialogDelegateFactory by + Kosmos.Fixture { mock<RearDisplayInnerDialogDelegate.Factory>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt index 2198e04eaf8a..e36ad427b43e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt @@ -21,7 +21,7 @@ import com.android.systemui.brightness.domain.interactor.screenBrightnessInterac import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor -import com.android.systemui.kosmos.brightnessWarningToast +import com.android.systemui.settings.brightness.ui.brightnessWarningToast val Kosmos.brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory by Kosmos.Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt new file mode 100644 index 000000000000..3ce119576096 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt @@ -0,0 +1,25 @@ +/* + * 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.systemui.development.data.repository + +import android.os.userManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.util.settings.fakeGlobalSettings + +val Kosmos.developmentSettingRepository by + Kosmos.Fixture { DevelopmentSettingRepository(fakeGlobalSettings, userManager, testDispatcher) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt new file mode 100644 index 000000000000..aa4dd18a6cba --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt @@ -0,0 +1,35 @@ +/* + * 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.systemui.development.domain.interactor + +import android.content.clipboardManager +import android.content.res.mainResources +import com.android.systemui.development.data.repository.developmentSettingRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.buildNumberInteractor by + Kosmos.Fixture { + BuildNumberInteractor( + developmentSettingRepository, + mainResources, + userRepository, + { clipboardManager }, + testDispatcher, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.kt new file mode 100644 index 000000000000..c827311a6ac3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.kt @@ -0,0 +1,29 @@ +/* + * 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.systemui.development.ui.viewmodel + +import com.android.systemui.development.domain.interactor.buildNumberInteractor +import com.android.systemui.kosmos.Kosmos + +val Kosmos.buildNumberViewModelFactory by + Kosmos.Fixture { + object : BuildNumberViewModel.Factory { + override fun create(): BuildNumberViewModel { + return BuildNumberViewModel(buildNumberInteractor) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 903bc8ebf42d..9cb15c5b816d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -136,7 +136,11 @@ val Kosmos.shortcutHelperStateInteractor by } val Kosmos.shortcutHelperCategoriesInteractor by - Kosmos.Fixture { ShortcutHelperCategoriesInteractor(defaultShortcutCategoriesRepository) } + Kosmos.Fixture { + ShortcutHelperCategoriesInteractor(defaultShortcutCategoriesRepository) { + customShortcutCategoriesRepository + } + } val Kosmos.shortcutHelperViewModel by Kosmos.Fixture { @@ -162,7 +166,8 @@ val Kosmos.shortcutCustomizationDialogStarterFactory by } } -val Kosmos.shortcutCustomizationInteractor by Kosmos.Fixture { ShortcutCustomizationInteractor() } +val Kosmos.shortcutCustomizationInteractor by + Kosmos.Fixture { ShortcutCustomizationInteractor(customShortcutCategoriesRepository) } val Kosmos.shortcutCustomizationViewModelFactory by Kosmos.Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt index d33d594d9e8a..76e2cc8b7bd0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.domain.interactor.keyguardSmartspaceInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.util.mockito.mock val Kosmos.keyguardSmartspaceViewModel by @@ -28,5 +29,6 @@ val Kosmos.keyguardSmartspaceViewModel by smartspaceController = mock(), keyguardClockViewModel = keyguardClockViewModel, smartspaceInteractor = keyguardSmartspaceInteractor, + shadeInteractor = shadeInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt index f43841b31c2e..72cb1dfe38db 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt @@ -3,9 +3,6 @@ package com.android.systemui.kosmos import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.settings.brightness.ui.BrightnessWarningToast - -import com.android.systemui.util.mockito.mock import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.test.StandardTestDispatcher @@ -41,9 +38,6 @@ var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture { testScope.backgroundScope.coroutineContext } var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext } -var Kosmos.brightnessWarningToast: BrightnessWarningToast by Kosmos.Fixture { - mock<BrightnessWarningToast>() -} /** * Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt index 0e5edb75846d..2e80293eafff 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.panels.ui.viewmodel +import com.android.systemui.development.ui.viewmodel.buildNumberViewModelFactory import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor @@ -26,5 +27,6 @@ val Kosmos.paginatedGridViewModel by qsColumnsViewModelFactory, paginatedGridInteractor, inFirstPageViewModel, + buildNumberViewModelFactory, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt index aac122c6610c..5d146fbffaca 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt @@ -21,9 +21,9 @@ import com.android.systemui.classifier.falsingManager import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.haptics.vibratorHelper import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.brightnessWarningToast import com.android.systemui.plugins.activityStarter import com.android.systemui.settings.brightness.BrightnessSliderController +import com.android.systemui.settings.brightness.ui.brightnessWarningToast import com.android.systemui.util.time.systemClock /** This factory creates empty mocks. */ diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToastKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToastKosmos.kt new file mode 100644 index 000000000000..d9acb527522a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToastKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.systemui.settings.brightness.ui + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.brightnessWarningToast: BrightnessWarningToast by + Kosmos.Fixture { mock<BrightnessWarningToast>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java index 65f4122f773e..21a910b67dff 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java @@ -16,6 +16,8 @@ package com.android.systemui.util.settings; +import static com.android.systemui.util.settings.JavaAdapter.newCoroutineScope; + import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; import android.annotation.NonNull; @@ -25,6 +27,7 @@ import android.database.ContentObserver; import android.net.Uri; import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; import java.util.ArrayList; import java.util.HashMap; @@ -34,7 +37,7 @@ import java.util.Map; public class FakeGlobalSettings implements GlobalSettings { private final Map<String, String> mValues = new HashMap<>(); private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>(); - private final CoroutineDispatcher mDispatcher; + private final CoroutineScope mSettingsScope; public static final Uri CONTENT_URI = Uri.parse("content://settings/fake_global"); @@ -44,11 +47,15 @@ public class FakeGlobalSettings implements GlobalSettings { */ @Deprecated public FakeGlobalSettings() { - mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null); + CoroutineDispatcher dispatcher = StandardTestDispatcher( + /* scheduler = */ null, + /* name = */ null + ); + mSettingsScope = newCoroutineScope(dispatcher); } public FakeGlobalSettings(CoroutineDispatcher dispatcher) { - mDispatcher = dispatcher; + mSettingsScope = newCoroutineScope(dispatcher); } @NonNull @@ -61,8 +68,8 @@ public class FakeGlobalSettings implements GlobalSettings { @NonNull @Override - public CoroutineDispatcher getBackgroundDispatcher() { - return mDispatcher; + public CoroutineScope getSettingsScope() { + return mSettingsScope; } @Override diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt index 35fa2af7639f..78b78ca86a8d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt @@ -19,5 +19,14 @@ package com.android.systemui.util.settings import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings(testDispatcher) } + +object JavaAdapter { + @JvmStatic + fun newCoroutineScope(context: CoroutineContext): CoroutineScope { + return CoroutineScope(context) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt index e5d113be7ca2..a3572754ab19 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt @@ -24,6 +24,7 @@ import android.util.Pair import androidx.annotation.VisibleForTesting import com.android.systemui.util.settings.SettingsProxy.CurrentUserIdProvider import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestDispatcher @@ -33,7 +34,7 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { private val contentObservers = mutableMapOf<SettingsKey, MutableList<ContentObserver>>() private val contentObserversAllUsers = mutableMapOf<String, MutableList<ContentObserver>>() - override val backgroundDispatcher: CoroutineDispatcher + override val settingsScope: CoroutineScope @UserIdInt override var userId = UserHandle.USER_CURRENT override val currentUserProvider: CurrentUserIdProvider @@ -43,17 +44,17 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { by main test scope.""" ) constructor() { - backgroundDispatcher = StandardTestDispatcher(scheduler = null, name = null) + settingsScope = CoroutineScope(StandardTestDispatcher(scheduler = null, name = null)) currentUserProvider = CurrentUserIdProvider { userId } } constructor(dispatcher: CoroutineDispatcher) { - backgroundDispatcher = dispatcher + settingsScope = CoroutineScope(dispatcher) currentUserProvider = CurrentUserIdProvider { userId } } constructor(dispatcher: CoroutineDispatcher, currentUserProvider: CurrentUserIdProvider) { - backgroundDispatcher = dispatcher + settingsScope = CoroutineScope(dispatcher) this.currentUserProvider = currentUserProvider } @@ -77,7 +78,7 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { if (userHandle == UserHandle.USER_ALL) { contentObserversAllUsers @@ -107,31 +108,31 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { override suspend fun registerContentObserver( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) = suspendAdvanceDispatcher { super<UserSettingsProxy>.registerContentObserver( uri, notifyForDescendants, - settingsObserver + settingsObserver, ) } override fun registerContentObserverAsync( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ): Job = advanceDispatcher { super<UserSettingsProxy>.registerContentObserverAsync( uri, notifyForDescendants, - settingsObserver + settingsObserver, ) } override suspend fun registerContentObserverForUser( name: String, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = suspendAdvanceDispatcher { super<UserSettingsProxy>.registerContentObserverForUser(name, settingsObserver, userHandle) } @@ -139,12 +140,12 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { override fun registerContentObserverForUserAsync( name: String, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ): Job = advanceDispatcher { super<UserSettingsProxy>.registerContentObserverForUserAsync( name, settingsObserver, - userHandle + userHandle, ) } @@ -156,7 +157,7 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { override suspend fun registerContentObserverForUser( uri: Uri, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = suspendAdvanceDispatcher { super<UserSettingsProxy>.registerContentObserverForUser(uri, settingsObserver, userHandle) } @@ -164,12 +165,12 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { override fun registerContentObserverForUserAsync( uri: Uri, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ): Job = advanceDispatcher { super<UserSettingsProxy>.registerContentObserverForUserAsync( uri, settingsObserver, - userHandle + userHandle, ) } @@ -177,13 +178,13 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { uri: Uri, settingsObserver: ContentObserver, userHandle: Int, - registered: Runnable + registered: Runnable, ): Job = advanceDispatcher { super<UserSettingsProxy>.registerContentObserverForUserAsync( uri, settingsObserver, userHandle, - registered + registered, ) } @@ -191,13 +192,13 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = suspendAdvanceDispatcher { super<UserSettingsProxy>.registerContentObserverForUser( name, notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } @@ -205,13 +206,13 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = advanceDispatcher { super<UserSettingsProxy>.registerContentObserverForUserAsync( name, notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } @@ -219,13 +220,13 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ): Job = advanceDispatcher { super<UserSettingsProxy>.registerContentObserverForUserAsync( uri, notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } @@ -259,7 +260,7 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { tag: String?, makeDefault: Boolean, userHandle: Int, - overrideableByRestore: Boolean + overrideableByRestore: Boolean, ): Boolean { val key = SettingsKey(userHandle, getUriFor(name).toString()) values[key] = value @@ -275,7 +276,7 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { name: String, value: String?, tag: String?, - makeDefault: Boolean + makeDefault: Boolean, ): Boolean { return putString(name, value) } @@ -293,8 +294,9 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { return result } + @OptIn(ExperimentalStdlibApi::class) private fun testDispatcherRunCurrent() { - val testDispatcher = backgroundDispatcher as? TestDispatcher + val testDispatcher = settingsScope.coroutineContext[CoroutineDispatcher] as? TestDispatcher testDispatcher?.scheduler?.runCurrent() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/FakeAudioSharingInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/FakeAudioSharingInteractor.kt new file mode 100644 index 000000000000..1fb5e77a3210 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/FakeAudioSharingInteractor.kt @@ -0,0 +1,61 @@ +/* + * 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.systemui.volume.domain.interactor + +import android.content.Context +import androidx.annotation.IntRange +import com.android.dream.lowlight.dagger.qualifiers.Application +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeAudioSharingInteractor : AudioSharingInteractor { + private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false) + private val mutableVolume: MutableStateFlow<Int?> = MutableStateFlow(null) + private var audioSharingVolumeBarAvailable = false + + override val isInAudioSharing: Flow<Boolean> = mutableInAudioSharing + override val volume: Flow<Int?> = mutableVolume + override val volumeMin: Int = AUDIO_SHARING_VOLUME_MIN + override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX + + override suspend fun audioSharingVolumeBarAvailable(@Application context: Context): Boolean = + audioSharingVolumeBarAvailable + + override fun setStreamVolume( + @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong()) + level: Int + ) {} + + override fun handlePrimaryGroupChange() {} + + fun setInAudioSharing(state: Boolean) { + mutableInAudioSharing.value = state + } + + fun setVolume(volume: Int?) { + mutableVolume.value = volume + } + + fun setAudioSharingVolumeBarAvailable(available: Boolean) { + audioSharingVolumeBarAvailable = available + } + + companion object { + const val AUDIO_SHARING_VOLUME_MIN = 0 + const val AUDIO_SHARING_VOLUME_MAX = 255 + } +} diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java index 869d854f7b23..9b71f8050c80 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -30,6 +30,8 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.ravenwood.common.RavenwoodCommonUtils; + import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runner.Runner; @@ -229,7 +231,9 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase s.evaluate(); onAfter(description, scope, order, null); } catch (Throwable t) { - if (onAfter(description, scope, order, t)) { + var shouldReportFailure = RavenwoodCommonUtils.runIgnoringException( + () -> onAfter(description, scope, order, t)); + if (shouldReportFailure == null || shouldReportFailure) { throw t; } } diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index 678a97be60a2..1c1f15761329 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -22,6 +22,8 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_R import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP; +import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt; +import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +41,7 @@ import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.os.Binder; import android.os.Build; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.HandlerThread; import android.os.Looper; @@ -154,6 +157,13 @@ public class RavenwoodRuntimeEnvironmentController { private static RavenwoodAwareTestRunner sRunner; private static RavenwoodSystemProperties sProps; + private static final int DEFAULT_TARGET_SDK_LEVEL = VERSION_CODES.CUR_DEVELOPMENT; + private static final String DEFAULT_PACKAGE_NAME = "com.android.ravenwoodtests.defaultname"; + + private static int sTargetSdkLevel; + private static String sTestPackageName; + private static String sTargetPackageName; + /** * Initialize the global environment. */ @@ -235,9 +245,22 @@ public class RavenwoodRuntimeEnvironmentController { System.setProperty("android.junit.runner", "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner"); + loadRavenwoodProperties(); + assertMockitoVersion(); } + private static void loadRavenwoodProperties() { + var props = RavenwoodSystemProperties.readProperties("ravenwood.properties"); + + sTargetSdkLevel = withDefault( + parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL); + sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME); + sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName); + + // TODO(b/377765941) Read them from the manifest too? + } + /** * Initialize the environment. */ @@ -256,7 +279,9 @@ public class RavenwoodRuntimeEnvironmentController { initInner(runner.mState.getConfig()); } catch (Exception th) { Log.e(TAG, "init() failed", th); - reset(); + + RavenwoodCommonUtils.runIgnoringException(()-> reset()); + SneakyThrow.sneakyThrow(th); } } @@ -267,6 +292,14 @@ public class RavenwoodRuntimeEnvironmentController { Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler); } + config.mTargetPackageName = sTargetPackageName; + config.mTestPackageName = sTestPackageName; + config.mTargetSdkLevel = sTargetSdkLevel; + + Log.i(TAG, "TargetPackageName=" + sTargetPackageName); + Log.i(TAG, "TestPackageName=" + sTestPackageName); + Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel); + RavenwoodRuntimeState.sUid = config.mUid; RavenwoodRuntimeState.sPid = config.mPid; RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel; @@ -349,8 +382,11 @@ public class RavenwoodRuntimeEnvironmentController { * Partially re-initialize after each test method invocation */ public static void reinit() { - var config = sRunner.mState.getConfig(); - Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid)); + // sRunner could be null, if there was a failure in the initialization. + if (sRunner != null) { + var config = sRunner.mState.getConfig(); + Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid)); + } } private static void initializeCompatIds(RavenwoodConfig config) { @@ -380,6 +416,9 @@ public class RavenwoodRuntimeEnvironmentController { /** * De-initialize. + * + * Note, we call this method when init() fails too, so this method should deal with + * any partially-initialized states. */ public static void reset() { if (RAVENWOOD_VERBOSE_LOGGING) { @@ -411,7 +450,9 @@ public class RavenwoodRuntimeEnvironmentController { config.mState.mSystemServerContext.cleanUp(); } - Looper.getMainLooper().quit(); + if (Looper.getMainLooper() != null) { + Looper.getMainLooper().quit(); + } Looper.clearMainLooperForTest(); ActivityManager.reset$ravenwood(); diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java index 3ed8b0a748e1..619c8e30c78e 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -22,7 +22,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Instrumentation; import android.content.Context; -import android.os.Build; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -30,16 +29,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; /** - * Represents how to configure the ravenwood environment for a test class. - * - * If a ravenwood test class has a public static field with the {@link Config} annotation, - * Ravenwood will extract the config from it and initializes the environment. The type of the - * field must be of {@link RavenwoodConfig}. + * @deprecated This class will be removed. Reach out to g/ravenwood if you need any features in it. */ +@Deprecated public final class RavenwoodConfig { /** * Use this to mark a field as the configuration. @@ -66,7 +61,7 @@ public final class RavenwoodConfig { String mTestPackageName; String mTargetPackageName; - int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; + int mTargetSdkLevel; final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties(); @@ -91,12 +86,6 @@ public final class RavenwoodConfig { return RavenwoodRule.isOnRavenwood(); } - private void setDefaults() { - if (mTargetPackageName == null) { - mTargetPackageName = mTestPackageName; - } - } - public static class Builder { private final RavenwoodConfig mConfig = new RavenwoodConfig(); @@ -120,28 +109,27 @@ public final class RavenwoodConfig { } /** - * Configure the package name of the test, which corresponds to - * {@link Instrumentation#getContext()}. + * @deprecated no longer used. Package name is set in the build file. (for now) */ + @Deprecated public Builder setPackageName(@NonNull String packageName) { - mConfig.mTestPackageName = Objects.requireNonNull(packageName); return this; } /** - * Configure the package name of the target app, which corresponds to - * {@link Instrumentation#getTargetContext()}. Defaults to {@link #setPackageName}. + * @deprecated no longer used. Package name is set in the build file. (for now) */ + @Deprecated public Builder setTargetPackageName(@NonNull String packageName) { - mConfig.mTargetPackageName = Objects.requireNonNull(packageName); return this; } + /** - * Configure the target SDK level of the test. + * @deprecated no longer used. Target SDK level is set in the build file. (for now) */ + @Deprecated public Builder setTargetSdkLevel(int sdkLevel) { - mConfig.mTargetSdkLevel = sdkLevel; return this; } @@ -205,7 +193,6 @@ public final class RavenwoodConfig { } public RavenwoodConfig build() { - mConfig.setDefaults(); return mConfig; } } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index bfa3802ce583..f7acd9022300 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -36,10 +36,8 @@ import java.util.Objects; import java.util.regex.Pattern; /** - * @deprecated Use {@link RavenwoodConfig} to configure the ravenwood environment instead. - * A {@link RavenwoodRule} is no longer needed for {@link DisabledOnRavenwood}. To get the - * {@link Context} and {@link Instrumentation}, use - * {@link androidx.test.platform.app.InstrumentationRegistry} instead. + * @deprecated This class is undergoing a major change. Reach out to g/ravenwood if you need + * any featues in it. */ @Deprecated public final class RavenwoodRule implements TestRule { @@ -128,11 +126,10 @@ public final class RavenwoodRule implements TestRule { } /** - * Configure the identity of this process to be the given package name for the duration - * of the test. Has no effect on non-Ravenwood environments. + * @deprecated no longer used. */ + @Deprecated public Builder setPackageName(@NonNull String packageName) { - mBuilder.setPackageName(packageName); return this; } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java index 3e4619f55c6d..9bd376a76f77 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java @@ -52,7 +52,7 @@ public class RavenwoodSystemProperties { "vendor_dlkm", }; - private static Map<String, String> readProperties(String propFile) { + static Map<String, String> readProperties(String propFile) { // Use an ordered map just for cleaner dump log. final Map<String, String> ret = new LinkedHashMap<>(); try { @@ -60,7 +60,7 @@ public class RavenwoodSystemProperties { .map(String::trim) .filter(s -> !s.startsWith("#")) .map(s -> s.split("\\s*=\\s*", 2)) - .filter(a -> a.length == 2) + .filter(a -> a.length == 2 && a[1].length() > 0) .forEach(a -> ret.put(a[0], a[1])); } catch (IOException e) { throw new RuntimeException(e); diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java index 520f050f0655..2a04d4469ef4 100644 --- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java +++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java @@ -30,6 +30,7 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.function.Supplier; public class RavenwoodCommonUtils { private static final String TAG = "RavenwoodCommonUtils"; @@ -277,11 +278,55 @@ public class RavenwoodCommonUtils { (isStatic ? "static" : ""))); } + /** + * Run a supplier and swallow the exception, if any. + * + * It's a dangerous function. Only use it in an exception handler where we don't want to crash. + */ + @Nullable + public static <T> T runIgnoringException(@NonNull Supplier<T> s) { + try { + return s.get(); + } catch (Throwable th) { + log(TAG, "Warning: Exception detected! " + getStackTraceString(th)); + } + return null; + } + + /** + * Run a runnable and swallow the exception, if any. + * + * It's a dangerous function. Only use it in an exception handler where we don't want to crash. + */ + public static void runIgnoringException(@NonNull Runnable r) { + runIgnoringException(() -> { + r.run(); + return null; + }); + } + @NonNull - public static String getStackTraceString(@Nullable Throwable th) { + public static String getStackTraceString(@NonNull Throwable th) { StringWriter stringWriter = new StringWriter(); PrintWriter writer = new PrintWriter(stringWriter); th.printStackTrace(writer); return stringWriter.toString(); } + + /** Same as {@link Integer#parseInt(String)} but accepts null and returns null. */ + @Nullable + public static Integer parseNullableInt(@Nullable String value) { + if (value == null) { + return null; + } + return Integer.parseInt(value); + } + + /** + * @return {@code value} if it's non-null. Otherwise, returns {@code def}. + */ + @Nullable + public static <T> T withDefault(@Nullable T value, @Nullable T def) { + return value != null ? value : def; + } } diff --git a/ravenwood/tests/bivalentinst/Android.bp b/ravenwood/tests/bivalentinst/Android.bp index 41e45e5a6d95..31e3bcc3634f 100644 --- a/ravenwood/tests/bivalentinst/Android.bp +++ b/ravenwood/tests/bivalentinst/Android.bp @@ -27,6 +27,9 @@ android_ravenwood_test { "junit", "truth", ], + + package_name: "com.android.ravenwood.bivalentinsttest_self_inst", + resource_apk: "RavenwoodBivalentInstTest_self_inst_device", auto_gen_config: true, } @@ -53,6 +56,10 @@ android_ravenwood_test { "truth", ], resource_apk: "RavenwoodBivalentInstTestTarget", + + package_name: "com.android.ravenwood.bivalentinst_target_app", + inst_package_name: "com.android.ravenwood.bivalentinsttest_nonself_inst", + inst_resource_apk: "RavenwoodBivalentInstTest_nonself_inst_device", auto_gen_config: true, } diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java index 92d43d714e14..db252d87c784 100644 --- a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java +++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java @@ -19,8 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import android.app.Instrumentation; import android.content.Context; -import android.platform.test.ravenwood.RavenwoodConfig; -import android.platform.test.ravenwood.RavenwoodConfig.Config; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; @@ -39,11 +37,6 @@ public class RavenwoodInstrumentationTest_nonself { private static final String TEST_PACKAGE_NAME = "com.android.ravenwood.bivalentinsttest_nonself_inst"; - @Config - public static final RavenwoodConfig sConfig = new RavenwoodConfig.Builder() - .setPackageName(TEST_PACKAGE_NAME) - .setTargetPackageName(TARGET_PACKAGE_NAME) - .build(); private static Instrumentation sInstrumentation; private static Context sTestContext; diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java index 2f35923dead2..94b18619efcd 100644 --- a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java +++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java @@ -19,8 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import android.app.Instrumentation; import android.content.Context; -import android.platform.test.ravenwood.RavenwoodConfig; -import android.platform.test.ravenwood.RavenwoodConfig.Config; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; @@ -40,13 +38,6 @@ public class RavenwoodInstrumentationTest_self { private static final String TEST_PACKAGE_NAME = "com.android.ravenwood.bivalentinsttest_self_inst"; - @Config - public static final RavenwoodConfig sConfig = new RavenwoodConfig.Builder() - .setPackageName(TEST_PACKAGE_NAME) - .setTargetPackageName(TARGET_PACKAGE_NAME) - .build(); - - private static Instrumentation sInstrumentation; private static Context sTestContext; private static Context sTargetContext; diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp index 40e6672a3c63..ac545dfb06cc 100644 --- a/ravenwood/tests/bivalenttest/Android.bp +++ b/ravenwood/tests/bivalenttest/Android.bp @@ -84,6 +84,8 @@ java_defaults { android_ravenwood_test { name: "RavenwoodBivalentTest", defaults: ["ravenwood-bivalent-defaults"], + target_sdk_version: "34", + package_name: "com.android.ravenwoodtest.bivalenttest", auto_gen_config: true, } diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java index a5a16c14600b..306c2b39c70d 100644 --- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java +++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java @@ -20,8 +20,6 @@ import static android.platform.test.ravenwood.RavenwoodConfig.isOnRavenwood; import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; -import android.platform.test.ravenwood.RavenwoodConfig; - import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; @@ -33,13 +31,7 @@ import org.junit.runner.RunWith; */ @RunWith(AndroidJUnit4.class) public class RavenwoodConfigTest { - private static final String PACKAGE_NAME = "com.test"; - - @RavenwoodConfig.Config - public static RavenwoodConfig sConfig = - new RavenwoodConfig.Builder() - .setPackageName(PACKAGE_NAME) - .build(); + private static final String PACKAGE_NAME = "com.android.ravenwoodtest.bivalenttest"; @Test public void testConfig() { diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt index a95760db1a61..882c91c43ee9 100644 --- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt +++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt @@ -16,8 +16,6 @@ package com.android.ravenwoodtest.bivalenttest.compat import android.app.compat.CompatChanges -import android.os.Build -import android.platform.test.ravenwood.RavenwoodConfig import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.internal.ravenwood.RavenwoodEnvironment.CompatIdsForTest import org.junit.Assert @@ -26,14 +24,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RavenwoodCompatFrameworkTest { - companion object { - @JvmField // Expose as a raw field, not as a property. - @RavenwoodConfig.Config - val config = RavenwoodConfig.Builder() - .setTargetSdkLevel(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - .build() - } - @Test fun testEnabled() { Assert.assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_1)) @@ -53,4 +43,4 @@ class RavenwoodCompatFrameworkTest { fun testEnabledAfterUForUApps() { Assert.assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_4)) } -}
\ No newline at end of file +} diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java index 02d10732245d..f94b98bc1fb8 100644 --- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java +++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java @@ -24,6 +24,7 @@ import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -32,6 +33,10 @@ import org.junit.runner.RunWith; /** * Test for @Config field extraction and validation. + * + * TODO(b/377765941) Most of the tests here will be obsolete and deleted with b/377765941, but + * some of the tests may need to be re-implemented one way or another. (e.g. the package name + * test.) Until that happens, we'll keep all tests here but add an {@code @Ignore} instead. */ @NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner. public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase { @@ -59,6 +64,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testRunFinished: 1,0,0,0 """) // CHECKSTYLE:ON + @Ignore // Package name is no longer set via config. public static class ConfigInBaseClassTest extends ConfigInBaseClass { @Test public void test() { @@ -83,6 +89,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testRunFinished: 1,0,0,0 """) // CHECKSTYLE:ON + @Ignore // Package name is no longer set via config. public static class ConfigOverridingTest extends ConfigInBaseClass { static String PACKAGE_NAME_OVERRIDE = "com.ConfigOverridingTest"; @@ -376,6 +383,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testRunFinished: 1,0,0,0 """) // CHECKSTYLE:ON + @Ignore // Package name is no longer set via config. public static class RuleInBaseClassSuccessTest extends RuleInBaseClass { @Test @@ -437,6 +445,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase testRunFinished: 1,1,0,0 """) // CHECKSTYLE:ON + @Ignore // Package name is no longer set via config. public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass { @Test diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java index f7a2198a9bc4..0e3d053e90b1 100644 --- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java +++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java @@ -25,6 +25,8 @@ import android.util.Log; import junitparams.JUnitParamsRunner; import junitparams.Parameters; + +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.Description; import org.junit.runner.JUnitCore; @@ -103,6 +105,7 @@ public abstract class RavenwoodRunnerTestBase { var thisClass = this.getClass(); var ret = Arrays.stream(thisClass.getNestMembers()) .filter((c) -> c.getAnnotation(Expected.class) != null) + .filter((c) -> c.getAnnotation(Ignore.class) == null) .toArray(Class[]::new); assertThat(ret.length).isGreaterThan(0); diff --git a/ravenwood/tests/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp index 0c0df1f993aa..c3520031ea7d 100644 --- a/ravenwood/tests/runtime-test/Android.bp +++ b/ravenwood/tests/runtime-test/Android.bp @@ -9,7 +9,7 @@ package { android_ravenwood_test { name: "RavenwoodRuntimeTest", - + target_sdk_version: "34", libs: [ "ravenwood-helper-runtime", ], diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index e59bb42fd666..4f632c9e28a1 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -353,7 +353,10 @@ public final class Helper { private static void addAutofillableIds(@NonNull ViewNode node, @NonNull ArrayList<AutofillId> ids, boolean autofillableOnly) { if (!autofillableOnly || node.getAutofillType() != View.AUTOFILL_TYPE_NONE) { - ids.add(node.getAutofillId()); + AutofillId id = node.getAutofillId(); + if (id != null) { + ids.add(id); + } } final int size = node.getChildCount(); for (int i = 0; i < size; i++) { diff --git a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java new file mode 100644 index 000000000000..5e4bab15952d --- /dev/null +++ b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2017 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.backup; + +import static com.android.server.backup.BackupManagerService.MORE_DEBUG; +import static com.android.server.backup.BackupManagerService.TAG; + +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.ApplicationThreadConstants; +import android.app.IActivityManager; +import android.app.IBackupAgent; +import android.app.backup.BackupAnnotations; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Binder; +import android.os.Build; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; +import com.android.server.backup.internal.LifecycleOperationStorage; + +import java.util.Set; + +/** + * Handles the lifecycle of {@link IBackupAgent}s that the {@link UserBackupManagerService} + * communicates with. + * + * <p>There can only be one agent that's connected to at a time. + * + * <p>There should be only one instance of this class per {@link UserBackupManagerService}. + */ +public class BackupAgentConnectionManager { + + /** + * Enables the OS making a decision on whether backup restricted mode should be used for apps + * that haven't explicitly opted in or out. See + * {@link android.content.pm.PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA) + public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510; + + // The thread performing the sequence of queued backups binds to each app's agent + // in succession. Bind notifications are asynchronously delivered through the + // Activity Manager; use this lock object to signal when a requested binding has + // completed. + private final Object mAgentConnectLock = new Object(); + private IBackupAgent mConnectedAgent; + private volatile boolean mConnecting; + private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>(); + private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>(); + + private final IActivityManager mActivityManager; + private final ActivityManagerInternal mActivityManagerInternal; + private final LifecycleOperationStorage mOperationStorage; + private final PackageManager mPackageManager; + private final UserBackupManagerService mUserBackupManagerService; + private final int mUserId; + private final String mUserIdMsg; + + BackupAgentConnectionManager(LifecycleOperationStorage operationStorage, + PackageManager packageManager, UserBackupManagerService userBackupManagerService, + int userId) { + mActivityManager = ActivityManager.getService(); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + mOperationStorage = operationStorage; + mPackageManager = packageManager; + mUserBackupManagerService = userBackupManagerService; + mUserId = userId; + mUserIdMsg = "[UserID:" + userId + "] "; + } + + /** + * Fires off a backup agent, blocking until it attaches (and ActivityManager will call + * {@link #agentConnected(String, IBinder)}) or until this operation times out. + * + * @param mode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}. + */ + @Nullable + public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode, + @BackupAnnotations.BackupDestination int backupDestination) { + IBackupAgent agent = null; + synchronized (mAgentConnectLock) { + mConnecting = true; + mConnectedAgent = null; + boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode, + app.packageName); + try { + if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId, + backupDestination, useRestrictedMode)) { + Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app); + + // success; wait for the agent to arrive + // only wait 10 seconds for the bind to happen + long timeoutMark = System.currentTimeMillis() + 10 * 1000; + while (mConnecting && mConnectedAgent == null && (System.currentTimeMillis() + < timeoutMark)) { + try { + mAgentConnectLock.wait(5000); + } catch (InterruptedException e) { + // just bail + Slog.w(TAG, mUserIdMsg + "Interrupted: " + e); + mConnecting = false; + mConnectedAgent = null; + } + } + + // if we timed out with no connect, abort and move on + if (mConnecting) { + Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app); + mConnectedAgent = null; + } + Slog.i(TAG, mUserIdMsg + "got agent " + mConnectedAgent); + agent = mConnectedAgent; + } + } catch (RemoteException e) { + // can't happen - ActivityManager is local + } + } + if (agent == null) { + mActivityManagerInternal.clearPendingBackup(mUserId); + } + return agent; + } + + /** + * Tell the ActivityManager that we are done with the {@link IBackupAgent} of this {@code app}. + * It will tell the app to destroy the agent. + */ + public void unbindAgent(ApplicationInfo app) { + try { + mActivityManager.unbindBackupAgent(app); + } catch (RemoteException e) { + // Can't happen - activity manager is local + } + } + + /** + * Callback: a requested backup agent has been instantiated. This should only be called from + * the + * {@link ActivityManager} when it's telling us that an agent is ready after a call to + * {@link #bindToAgentSynchronous(ApplicationInfo, int, int)}. + */ + public void agentConnected(String packageName, IBinder agentBinder) { + synchronized (mAgentConnectLock) { + if (getCallingUid() == android.os.Process.SYSTEM_UID) { + Slog.d(TAG, + mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder); + mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder); + mConnecting = false; + } else { + Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid() + + " claiming agent connected"); + } + mAgentConnectLock.notifyAll(); + } + } + + /** + * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed + * to come up in the first place, the agentBinder argument will be {@code null}. This should + * only be called from the {@link ActivityManager}. + */ + public void agentDisconnected(String packageName) { + synchronized (mAgentConnectLock) { + if (getCallingUid() == Process.SYSTEM_UID) { + mConnectedAgent = null; + mConnecting = false; + } else { + Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid() + + " claiming agent disconnected"); + } + Slog.w(TAG, mUserIdMsg + "agentDisconnected: the backup agent for " + packageName + + " died: cancel current operations"); + + // Offload operation cancellation off the main thread as the cancellation callbacks + // might call out to BackupTransport. Other operations started on the same package + // before the cancellation callback has executed will also be cancelled by the callback. + Runnable cancellationRunnable = () -> { + // handleCancel() causes the PerformFullTransportBackupTask to go on to + // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so + // that the package being backed up doesn't get stuck in restricted mode until the + // backup time-out elapses. + for (int token : mOperationStorage.operationTokensForPackage(packageName)) { + if (MORE_DEBUG) { + Slog.d(TAG, + mUserIdMsg + "agentDisconnected: will handleCancel(all) for token:" + + Integer.toHexString(token)); + } + mUserBackupManagerService.handleCancel(token, true /* cancelAll */); + } + }; + getThreadForCancellation(cancellationRunnable).start(); + + mAgentConnectLock.notifyAll(); + } + } + + /** + * Marks the given set of packages as packages that should not be put into restricted mode if + * they are started for the given {@link BackupAnnotations.OperationType}. + */ + public void setNoRestrictedModePackages(Set<String> packageNames, + @BackupAnnotations.OperationType int opType) { + if (opType == BackupAnnotations.OperationType.BACKUP) { + mBackupNoRestrictedModePackages.clear(); + mBackupNoRestrictedModePackages.addAll(packageNames); + } else if (opType == BackupAnnotations.OperationType.RESTORE) { + mRestoreNoRestrictedModePackages.clear(); + mRestoreNoRestrictedModePackages.addAll(packageNames); + } else { + throw new IllegalArgumentException("opType must be BACKUP or RESTORE"); + } + } + + /** + * Clears the list of packages that should not be put into restricted mode for either backup or + * restore. + */ + public void clearNoRestrictedModePackages() { + mBackupNoRestrictedModePackages.clear(); + mRestoreNoRestrictedModePackages.clear(); + } + + /** + * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then + * its value is returned. If it hasn't and it targets an SDK below + * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then + * returns the decision made by the {@link android.app.backup.BackupTransport}. + * + * <p>When this method is called, we should have already asked the transport and cached its + * response in {@link #mBackupNoRestrictedModePackages} or + * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without + * any IPC to the transport. + */ + private boolean shouldUseRestrictedBackupModeForPackage( + @BackupAnnotations.OperationType int mode, String packageName) { + if (!Flags.enableRestrictedModeChanges()) { + return true; + } + + // Key/Value apps are never put in restricted mode. + if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL + || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) { + return false; + } + + try { + PackageManager.Property property = mPackageManager.getPropertyAsUser( + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, + packageName, /* className= */ null, mUserId); + if (property.isBoolean()) { + // If the package has explicitly specified, we won't ask the transport. + return property.getBoolean(); + } else { + Slog.w(TAG, + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE + "must be a boolean."); + } + } catch (NameNotFoundException e) { + // This is expected when the package has not defined the property in its manifest. + } + + // The package has not specified the property. The behavior depends on the package's + // targetSdk. + // <36 gets the old behavior of always using restricted mode. + if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName, + UserHandle.of(mUserId))) { + return true; + } + + // Apps targeting >=36 get the behavior decided by the transport. + // By this point, we should have asked the transport and cached its decision. + if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL + && mBackupNoRestrictedModePackages.contains(packageName)) || ( + mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL + && mRestoreNoRestrictedModePackages.contains(packageName))) { + Slog.d(TAG, "Transport requested no restricted mode for: " + packageName); + return false; + } + return true; + } + + @VisibleForTesting + Thread getThreadForCancellation(Runnable operation) { + return new Thread(operation, /* operationName */ "agent-disconnected"); + } + + @VisibleForTesting + int getCallingUid() { + return Binder.getCallingUid(); + } +} diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 5f0071d47c89..3f6ede95eaf9 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -658,7 +658,8 @@ public class BackupManagerService extends IBackupManager.Stub { getServiceForUserIfCallerHasPermission(userId, "agentConnected()"); if (userBackupManagerService != null) { - userBackupManagerService.agentConnected(packageName, agentBinder); + userBackupManagerService.getBackupAgentConnectionManager().agentConnected(packageName, + agentBinder); } } @@ -683,7 +684,8 @@ public class BackupManagerService extends IBackupManager.Stub { getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()"); if (userBackupManagerService != null) { - userBackupManagerService.agentDisconnected(packageName); + userBackupManagerService.getBackupAgentConnectionManager().agentDisconnected( + packageName); } } diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java index f5d68362c70a..136bacdd6399 100644 --- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java +++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java @@ -146,7 +146,8 @@ public class KeyValueAdbBackupEngine { private IBackupAgent bindToAgent(ApplicationInfo targetApp) { try { - return mBackupManagerService.bindToAgentSynchronous(targetApp, + return mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous( + targetApp, ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupAnnotations.BackupDestination.CLOUD); } catch (SecurityException e) { diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 5de2fb30ac78..e085f6e63067 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -43,9 +43,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.AppGlobals; -import android.app.ApplicationThreadConstants; import android.app.IActivityManager; -import android.app.IBackupAgent; import android.app.PendingIntent; import android.app.backup.BackupAgent; import android.app.backup.BackupAnnotations; @@ -60,9 +58,6 @@ import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreSession; import android.app.backup.ISelectBackupTransportCallback; -import android.app.compat.CompatChanges; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledSince; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -83,7 +78,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; -import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PowerManager; @@ -302,21 +296,10 @@ public class UserBackupManagerService { private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED"; private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName"; - /** - * Enables the OS making a decision on whether backup restricted mode should be used for apps - * that haven't explicitly opted in or out. See - * {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details. - */ - @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA) - public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510; - // Time delay for initialization operations that can be delayed so as not to consume too much // CPU on bring-up and increase time-to-UI. private static final long INITIALIZATION_DELAY_MILLIS = 3000; - // Timeout interval for deciding that a bind has taken too long. - private static final long BIND_TIMEOUT_INTERVAL = 10 * 1000; // Timeout interval for deciding that a clear-data has taken too long. private static final long CLEAR_DATA_TIMEOUT_INTERVAL = 30 * 1000; @@ -365,22 +348,9 @@ public class UserBackupManagerService { // Backups that we haven't started yet. Keys are package names. private final HashMap<String, BackupRequest> mPendingBackups = new HashMap<>(); - private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>(); - private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>(); - // locking around the pending-backup management private final Object mQueueLock = new Object(); - private final UserBackupPreferences mBackupPreferences; - - // The thread performing the sequence of queued backups binds to each app's agent - // in succession. Bind notifications are asynchronously delivered through the - // Activity Manager; use this lock object to signal when a requested binding has - // completed. - private final Object mAgentConnectLock = new Object(); - private IBackupAgent mConnectedAgent; - private volatile boolean mConnecting; - private volatile boolean mBackupRunning; private volatile long mLastBackupPass; @@ -410,6 +380,7 @@ public class UserBackupManagerService { private ActiveRestoreSession mActiveRestoreSession; + private final BackupAgentConnectionManager mBackupAgentConnectionManager; private final LifecycleOperationStorage mOperationStorage; private final Random mTokenGenerator = new Random(); @@ -547,6 +518,8 @@ public class UserBackupManagerService { mRegisterTransportsRequestedTime = 0; mPackageManager = packageManager; mOperationStorage = operationStorage; + mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage, + mPackageManager, this, mUserId); mTransportManager = transportManager; mFullBackupQueue = new ArrayList<>(); mBackupHandler = backupHandler; @@ -599,6 +572,8 @@ public class UserBackupManagerService { mAgentTimeoutParameters.start(); mOperationStorage = new LifecycleOperationStorage(mUserId); + mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage, + mPackageManager, this, mUserId); Objects.requireNonNull(userBackupThread, "userBackupThread cannot be null"); mBackupHandler = new BackupHandler(this, mOperationStorage, userBackupThread); @@ -1660,67 +1635,6 @@ public class UserBackupManagerService { } } - /** Fires off a backup agent, blocking until it attaches or times out. */ - @Nullable - public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode, - @BackupDestination int backupDestination) { - IBackupAgent agent = null; - synchronized (mAgentConnectLock) { - mConnecting = true; - mConnectedAgent = null; - boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode, - app.packageName); - try { - if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId, - backupDestination, useRestrictedMode)) { - Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app)); - - // success; wait for the agent to arrive - // only wait 10 seconds for the bind to happen - long timeoutMark = System.currentTimeMillis() + BIND_TIMEOUT_INTERVAL; - while (mConnecting && mConnectedAgent == null - && (System.currentTimeMillis() < timeoutMark)) { - try { - mAgentConnectLock.wait(5000); - } catch (InterruptedException e) { - // just bail - Slog.w(TAG, addUserIdToLogMessage(mUserId, "Interrupted: " + e)); - mConnecting = false; - mConnectedAgent = null; - } - } - - // if we timed out with no connect, abort and move on - if (mConnecting) { - Slog.w( - TAG, - addUserIdToLogMessage(mUserId, "Timeout waiting for agent " + app)); - mConnectedAgent = null; - } - if (DEBUG) { - Slog.i(TAG, addUserIdToLogMessage(mUserId, "got agent " + mConnectedAgent)); - } - agent = mConnectedAgent; - } - } catch (RemoteException e) { - // can't happen - ActivityManager is local - } - } - if (agent == null) { - mActivityManagerInternal.clearPendingBackup(mUserId); - } - return agent; - } - - /** Unbind from a backup agent. */ - public void unbindAgent(ApplicationInfo app) { - try { - mActivityManager.unbindBackupAgent(app); - } catch (RemoteException e) { - // Can't happen - activity manager is local - } - } - /** * Clear an application's data after a failed restore, blocking until the operation completes or * times out. @@ -2493,10 +2407,6 @@ public class UserBackupManagerService { AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, mUserId); } - // ***************************** - // NEW UNIFIED RESTORE IMPLEMENTATION - // ***************************** - /** Schedule a backup pass for {@code packageName}. */ public void dataChangedImpl(String packageName) { HashSet<String> targets = dataChangedTargets(packageName); @@ -3122,91 +3032,6 @@ public class UserBackupManagerService { } } - /** - * Marks the given set of packages as packages that should not be put into restricted mode if - * they are started for the given {@link BackupAnnotations.OperationType}. - */ - public void setNoRestrictedModePackages(Set<String> packageNames, - @BackupAnnotations.OperationType int opType) { - if (opType == BackupAnnotations.OperationType.BACKUP) { - mBackupNoRestrictedModePackages.clear(); - mBackupNoRestrictedModePackages.addAll(packageNames); - } else if (opType == BackupAnnotations.OperationType.RESTORE) { - mRestoreNoRestrictedModePackages.clear(); - mRestoreNoRestrictedModePackages.addAll(packageNames); - } else { - throw new IllegalArgumentException("opType must be BACKUP or RESTORE"); - } - } - - /** - * Clears the list of packages that should not be put into restricted mode for either backup or - * restore. - */ - public void clearNoRestrictedModePackages() { - mBackupNoRestrictedModePackages.clear(); - mRestoreNoRestrictedModePackages.clear(); - } - - /** - * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then - * its value is returned. If it hasn't and it targets an SDK below - * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then - * returns the decision made by the {@link android.app.backup.BackupTransport}. - * - * <p>When this method is called, we should have already asked the transport and cached its - * response in {@link #mBackupNoRestrictedModePackages} or - * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without - * any IPC to the transport. - */ - private boolean shouldUseRestrictedBackupModeForPackage( - @BackupAnnotations.OperationType int mode, String packageName) { - if (!Flags.enableRestrictedModeChanges()) { - return true; - } - - // Key/Value apps are never put in restricted mode. - if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL - || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) { - return false; - } - - try { - PackageManager.Property property = mPackageManager.getPropertyAsUser( - PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, - packageName, /* className= */ null, - mUserId); - if (property.isBoolean()) { - // If the package has explicitly specified, we won't ask the transport. - return property.getBoolean(); - } else { - Slog.w(TAG, PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE - + "must be a boolean."); - } - } catch (NameNotFoundException e) { - // This is expected when the package has not defined the property in its manifest. - } - - // The package has not specified the property. The behavior depends on the package's - // targetSdk. - // <36 gets the old behavior of always using restricted mode. - if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName, - UserHandle.of(mUserId))) { - return true; - } - - // Apps targeting >=36 get the behavior decided by the transport. - // By this point, we should have asked the transport and cached its decision. - if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL - && mBackupNoRestrictedModePackages.contains(packageName)) - || (mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL - && mRestoreNoRestrictedModePackages.contains(packageName))) { - Slog.d(TAG, "Transport requested no restricted mode for: " + packageName); - return false; - } - return true; - } - private boolean startConfirmationUi(int token, String action) { try { Intent confIntent = new Intent(action); @@ -3898,83 +3723,6 @@ public class UserBackupManagerService { } /** - * Callback: a requested backup agent has been instantiated. This should only be called from the - * {@link ActivityManager}. - */ - public void agentConnected(String packageName, IBinder agentBinder) { - synchronized (mAgentConnectLock) { - if (Binder.getCallingUid() == Process.SYSTEM_UID) { - Slog.d( - TAG, - addUserIdToLogMessage( - mUserId, - "agentConnected pkg=" + packageName + " agent=" + agentBinder)); - mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder); - mConnecting = false; - } else { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, - "Non-system process uid=" - + Binder.getCallingUid() - + " claiming agent connected")); - } - mAgentConnectLock.notifyAll(); - } - } - - /** - * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed - * to come up in the first place, the agentBinder argument will be {@code null}. This should - * only be called from the {@link ActivityManager}. - */ - public void agentDisconnected(String packageName) { - synchronized (mAgentConnectLock) { - if (Binder.getCallingUid() == Process.SYSTEM_UID) { - mConnectedAgent = null; - mConnecting = false; - } else { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, - "Non-system process uid=" - + Binder.getCallingUid() - + " claiming agent disconnected")); - } - Slog.w(TAG, "agentDisconnected: the backup agent for " + packageName - + " died: cancel current operations"); - - // Offload operation cancellation off the main thread as the cancellation callbacks - // might call out to BackupTransport. Other operations started on the same package - // before the cancellation callback has executed will also be cancelled by the callback. - Runnable cancellationRunnable = () -> { - // handleCancel() causes the PerformFullTransportBackupTask to go on to - // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so - // that the package being backed up doesn't get stuck in restricted mode until the - // backup time-out elapses. - for (int token : mOperationStorage.operationTokensForPackage(packageName)) { - if (MORE_DEBUG) { - Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:" - + Integer.toHexString(token)); - } - handleCancel(token, true /* cancelAll */); - } - }; - getThreadForAsyncOperation(/* operationName */ "agent-disconnected", - cancellationRunnable).start(); - - mAgentConnectLock.notifyAll(); - } - } - - @VisibleForTesting - Thread getThreadForAsyncOperation(String operationName, Runnable operation) { - return new Thread(operation, operationName); - } - - /** * An application being installed will need a restore pass, then the {@link PackageManager} will * need to be told when the restore is finished. */ @@ -4521,4 +4269,8 @@ public class UserBackupManagerService { public IBackupManager getBackupManagerBinder() { return mBackupManagerBinder; } + + public BackupAgentConnectionManager getBackupAgentConnectionManager() { + return mBackupAgentConnectionManager; + } } diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java index 12712063e344..b98cb1086680 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java +++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java @@ -314,7 +314,7 @@ public class FullBackupEngine { Slog.d(TAG, "Binding to full backup agent : " + mPkg.packageName); } mAgent = - backupManagerService.bindToAgentSynchronous( + backupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous( mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL, mBackupEligibilityRules.getBackupDestination()); } diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index be9cdc8692cb..65730c9591a8 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -702,7 +702,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } // Clear this to avoid using the memory until reboot. - mUserBackupManagerService.clearNoRestrictedModePackages(); + mUserBackupManagerService + .getBackupAgentConnectionManager().clearNoRestrictedModePackages(); Slog.i(TAG, "Full data backup pass finished."); mUserBackupManagerService.getWakelock().release(); @@ -741,7 +742,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames, BACKUP); - mUserBackupManagerService.setNoRestrictedModePackages(packageNames, BACKUP); + mUserBackupManagerService.getBackupAgentConnectionManager().setNoRestrictedModePackages( + packageNames, BACKUP); } catch (RemoteException e) { Slog.i(TAG, "Failed to retrieve no restricted mode packages from transport"); } diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index 3a6e1cafa505..82232a653858 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -741,7 +741,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { final IBackupAgent agent; try { agent = - mBackupManagerService.bindToAgentSynchronous( + mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous( packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL, mBackupEligibilityRules.getBackupDestination()); if (agent == null) { @@ -1302,7 +1302,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { // For PM metadata (for which applicationInfo is null) there is no agent-bound state. if (mCurrentPackage.applicationInfo != null) { - mBackupManagerService.unbindAgent(mCurrentPackage.applicationInfo); + mBackupManagerService.getBackupAgentConnectionManager().unbindAgent( + mCurrentPackage.applicationInfo); } mAgent = null; } diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java index 2d99c96452da..b59e860f81fe 100644 --- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java +++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java @@ -410,11 +410,7 @@ public class FullRestoreEngine extends RestoreEngine { // All set; now set up the IPC and launch the agent setUpPipes(); - mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp, - FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain) - ? ApplicationThreadConstants.BACKUP_MODE_RESTORE - : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, - mBackupEligibilityRules.getBackupDestination()); + mAgent = bindToAgent(info); mAgentPackage = pkg; } catch (IOException | NameNotFoundException e) { // fall through to error handling @@ -805,15 +801,12 @@ public class FullRestoreEngine extends RestoreEngine { return packages.contains(packageName); } - void sendOnRestorePackage(String name) { - if (mObserver != null) { - try { - // TODO: use a more user-friendly name string - mObserver.onRestorePackage(name); - } catch (RemoteException e) { - Slog.w(TAG, "full restore observer went away: restorePackage"); - mObserver = null; - } - } + private IBackupAgent bindToAgent(FileMetadata info) { + return mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous( + mTargetApp, + FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain) + ? ApplicationThreadConstants.BACKUP_MODE_RESTORE + : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, + mBackupEligibilityRules.getBackupDestination()); } } diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index 5ee51a5aa189..e5c7e5cce757 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -814,7 +814,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // Good to go! Set up and bind the agent... mAgent = - backupManagerService.bindToAgentSynchronous( + backupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous( mCurrentPackage.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_RESTORE, mBackupEligibilityRules.getBackupDestination()); @@ -1364,7 +1364,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT); // Clear this to avoid using the memory until reboot. - backupManagerService.clearNoRestrictedModePackages(); + backupManagerService.getBackupAgentConnectionManager().clearNoRestrictedModePackages(); // If we have a PM token, we must under all circumstances be sure to // handshake when we've finished. @@ -1838,7 +1838,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { } packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames, RESTORE); - backupManagerService.setNoRestrictedModePackages(packageNames, RESTORE); + backupManagerService.getBackupAgentConnectionManager().setNoRestrictedModePackages( + packageNames, RESTORE); } catch (RemoteException e) { Slog.i(TAG, "Failed to retrieve restricted mode packages from transport"); } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index fa228627c255..e57b00944f7c 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -65,6 +65,7 @@ import android.telephony.CellSignalStrengthLte; import android.telephony.CellSignalStrengthNr; import android.telephony.CellSignalStrengthTdscdma; import android.telephony.CellSignalStrengthWcdma; +import android.telephony.CellularIdentifierDisclosure; import android.telephony.DisconnectCause; import android.telephony.LinkCapacityEstimate; import android.telephony.LocationAccessPolicy; @@ -76,6 +77,7 @@ import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; import android.telephony.PreciseDisconnectCause; import android.telephony.Rlog; +import android.telephony.SecurityAlgorithmUpdate; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; @@ -590,7 +592,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { || events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED) || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED) || events.contains(TelephonyCallback - .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED); + .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED) + || events.contains(TelephonyCallback.EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED) + || events.contains(TelephonyCallback.EVENT_SECURITY_ALGORITHMS_CHANGED); } private static final int MSG_USER_SWITCHED = 1; @@ -897,7 +901,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mIsSatelliteEnabled = new AtomicBoolean(); mWasSatelliteEnabledNotified = new AtomicBoolean(); - for (int i = 0; i < numPhones; i++) { mCallState[i] = TelephonyManager.CALL_STATE_IDLE; mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE; @@ -3825,7 +3828,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - /** * Notify external listeners that carrier roaming non-terrestrial network * signal strength changed. @@ -3835,7 +3837,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { public void notifyCarrierRoamingNtnSignalStrengthChanged(int subId, @NonNull NtnSignalStrength ntnSignalStrength) { if (!checkNotifyPermission("notifyCarrierRoamingNtnSignalStrengthChanged")) { - log("nnotifyCarrierRoamingNtnSignalStrengthChanged: caller does not have required " + log("notifyCarrierRoamingNtnSignalStrengthChanged: caller does not have required " + "permissions."); return; } @@ -3863,6 +3865,98 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + /** + * Notify that the radio security algorithms have changed. + * + * @param phoneId the phone id. + * @param subId the subId. + * @param update the security algorithm update. + */ + public void notifySecurityAlgorithmsChanged(int phoneId, int subId, + SecurityAlgorithmUpdate update) { + if (!Flags.securityAlgorithmsUpdateIndications()) { + log("Not available due to securityAlgorithmsUpdateIndications() flag"); + return; + } + if (!checkNotifyPermission("notifySecurityAlgorithmChanged()")) { + return; + } + + synchronized (mRecords) { + if (validatePhoneId(phoneId)) { + if (update == null) { + loge("SecurityAlgorithmUpdate is null, subId=" + subId + + ", phoneId=" + phoneId); + // Listeners shouldn't be updated for null updates. + return; + } + + for (Record r : mRecords) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_SECURITY_ALGORITHMS_CHANGED) + && idMatch(r, subId, phoneId)) { + try { + if (VDBG) { + log("notifySecurityAlgorithmsChanged: securityAlgorithmUpdate= " + + update); + } + r.callback.onSecurityAlgorithmsChanged(update); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + } + handleRemoveListLocked(); + } + } + + /** + * Notify of a cellular identifier disclosure. + * + * @param phoneId the phone id. + * @param subId the subId. + * @param disclosure the cellular identifier disclosure. + */ + public void notifyCellularIdentifierDisclosedChanged(int phoneId, int subId, + @NonNull CellularIdentifierDisclosure disclosure) { + if (!Flags.cellularIdentifierDisclosureIndications()) { + log("Not available due to cellularIdentifierDisclosureIndications() flag"); + return; + } + if (!checkNotifyPermission("notifyCellularIdentifierDisclosedChanged()")) { + return; + } + + synchronized (mRecords) { + if (validatePhoneId(phoneId)) { + if (disclosure == null) { + loge("CellularIdentifierDisclosure is null, subId=" + subId + + ", phoneId=" + phoneId); + // Listeners shouldn't be updated for null disclosures. + return; + } + + for (Record r : mRecords) { + if (r.matchTelephonyCallbackEvent( + TelephonyCallback.EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED) + && idMatch(r, subId, phoneId)) { + try { + if (VDBG) { + log("notifyCellularIdentifierDisclosedChanged: disclosure= " + + disclosure); + } + r.callback.onCellularIdentifierDisclosedChanged(disclosure); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + } + handleRemoveListLocked(); + } + } + @NeverCompile // Avoid size overhead of debugging code. @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index d880bce921aa..e166807083ff 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19070,8 +19070,13 @@ public class ActivityManagerService extends IActivityManager.Stub */ @Override public boolean enableFgsNotificationRateLimit(boolean enable) { - enforceCallingPermission(permission.WRITE_DEVICE_CONFIG, - "enableFgsNotificationRateLimit"); + if (android.security.Flags.protectDeviceConfigFlags()) { + enforceCallingHasAtLeastOnePermission("enableFgsNotificationRateLimit", + permission.WRITE_DEVICE_CONFIG, permission.WRITE_ALLOWLISTED_DEVICE_CONFIG); + } else { + enforceCallingPermission(permission.WRITE_DEVICE_CONFIG, + "enableFgsNotificationRateLimit"); + } synchronized (this) { return mServices.enableFgsNotificationRateLimitLocked(enable); } diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 3913d2f2ea92..961022b7231b 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -634,12 +634,20 @@ public final class AppStartInfoTracker { } final ApplicationStartInfo info = new ApplicationStartInfo(raw); + int uid = raw.getRealUid(); - AppStartInfoContainer container = mData.get(raw.getPackageName(), raw.getRealUid()); + // Isolated process starts won't be reasonably accessible if stored by their uid, don't + // store them. + if (com.android.server.am.Flags.appStartInfoIsolatedProcess() + && UserHandle.isIsolated(uid)) { + return null; + } + + AppStartInfoContainer container = mData.get(raw.getPackageName(), uid); if (container == null) { container = new AppStartInfoContainer(mAppStartInfoHistoryListSize); - container.mUid = info.getRealUid(); - mData.put(raw.getPackageName(), raw.getRealUid(), container); + container.mUid = uid; + mData.put(raw.getPackageName(), uid, container); } container.addStartInfoLocked(info); @@ -1010,6 +1018,17 @@ public final class AppStartInfoTracker { new AppStartInfoContainer(mAppStartInfoHistoryListSize); int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS, pkgName); + + // If the isolated process flag is enabled and the uid is that of an isolated + // process, then break early so that the container will not be added to mData. + // This is expected only as a one time mitigation, records added after this flag + // is enabled should always return false for isIsolated and thereby always + // continue on. + if (com.android.server.am.Flags.appStartInfoIsolatedProcess() + && UserHandle.isIsolated(uid)) { + break; + } + synchronized (mLock) { mData.put(pkgName, uid, container); } diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index c59c40fc9cd8..6d247d227774 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -270,3 +270,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "app_start_info_isolated_process" + namespace: "system_performance" + description: "Adjust handling of isolated process records to be discarded." + bug: "374032823" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 5e74d67905a6..06c586f5e9c2 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2876,7 +2876,8 @@ public class AppOpsService extends IAppOpsService.Stub { } @Override - public int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId) { + public int checkOperationForDevice(int code, int uid, String packageName, + @Nullable String attributionTag, int virtualDeviceId) { if (Binder.getCallingPid() != Process.myPid() && Flags.appopAccessTrackingLoggingEnabled()) { FrameworkStatsLog.write( @@ -2884,7 +2885,7 @@ public class AppOpsService extends IAppOpsService.Stub { APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION, false); } - return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null, + return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag, virtualDeviceId, false /*raw*/); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 5f716605e8cd..6e6bf80e8c09 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -14166,10 +14166,10 @@ public class AudioService extends IAudioService.Stub * Update player event * @param piid Player id to update * @param event The new player event - * @param eventValue The value associated with this event + * @param eventValues The values associated with this event */ - public void playerEvent(int piid, int event, int eventValue) { - mPlaybackMonitor.playerEvent(piid, event, eventValue, Binder.getCallingUid()); + public void playerEvent(int piid, int event, int[] eventValues) { + mPlaybackMonitor.playerEvent(piid, event, eventValues, Binder.getCallingUid()); } /** diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index e92b5188652b..a62ac82f27eb 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -365,10 +365,11 @@ public final class PlaybackActivityMonitor * @param eventValue The value associated with this event * @param binderUid Calling binder uid */ - public void playerEvent(int piid, int event, int eventValue, int binderUid) { + public void playerEvent(int piid, int event, int[] eventValues, int binderUid) { if (DEBUG) { - Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValue=%d)", - piid, AudioPlaybackConfiguration.playerStateToString(event), eventValue)); + Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValues=%d)", + piid, AudioPlaybackConfiguration.playerStateToString(event), + Arrays.toString(eventValues))); } boolean change; synchronized(mPlayerLock) { @@ -382,13 +383,13 @@ public final class PlaybackActivityMonitor // do not log nor dispatch events for "ignored" players other than the release return; } - sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue)); + sEventLogger.enqueue(new PlayerEvent(piid, event, eventValues)); if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) { if (portToPiidSimplification()) { - mPiidToPortId.put(piid, eventValue); + mPiidToPortId.put(piid, eventValues[0]); } else { - mPortIdToPiid.put(eventValue, piid); + mPortIdToPiid.put(eventValues[0], piid); } return; } else if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { @@ -409,7 +410,7 @@ public final class PlaybackActivityMonitor if (checkConfigurationCaller(piid, apc, binderUid)) { //TODO add generation counter to only update to the latest state checkVolumeForPrivilegedAlarm(apc, event); - change = apc.handleStateEvent(event, eventValue); + change = apc.handleStateEvent(event, eventValues); } else { Log.e(TAG, "Error handling event " + event); change = false; @@ -517,7 +518,7 @@ public final class PlaybackActivityMonitor mMutedPlayersAwaitingConnection.remove(Integer.valueOf(piid)); checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED); change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED, - AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID); + AudioPlaybackConfiguration.PLAYER_DEVICEIDS_INVALID); if (portToPiidSimplification()) { mPiidToPortId.delete(piid); @@ -1336,12 +1337,12 @@ public final class PlaybackActivityMonitor // only keeping the player interface ID as it uniquely identifies the player in the event final int mPlayerIId; final int mEvent; - final int mEventValue; + final int[] mEventValues; - PlayerEvent(int piid, int event, int eventValue) { + PlayerEvent(int piid, int event, int[] eventValues) { mPlayerIId = piid; mEvent = event; - mEventValue = eventValue; + mEventValues = eventValues; } @Override @@ -1353,36 +1354,38 @@ public final class PlaybackActivityMonitor switch (mEvent) { case AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID: return AudioPlaybackConfiguration.toLogFriendlyPlayerState(mEvent) + " portId:" - + mEventValue + " mapped to player piid:" + mPlayerIId; + + Arrays.toString(mEventValues) + " mapped to player piid:" + + mPlayerIId; case AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID: - if (mEventValue != 0) { - builder.append(" deviceId:").append(mEventValue); + if ((mEventValues.length > 0) && (mEventValues[0] != 0)) { + builder.append(" deviceIds:").append(Arrays.toString(mEventValues)); } return builder.toString(); case AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED: builder.append(" source:"); - if (mEventValue <= 0) { + int eventValue = mEventValues[0]; + if (eventValue <= 0) { builder.append("none "); } else { - if ((mEventValue & MUTED_BY_MASTER) != 0) { + if ((eventValue & MUTED_BY_MASTER) != 0) { builder.append("masterMute "); } - if ((mEventValue & MUTED_BY_STREAM_VOLUME) != 0) { + if ((eventValue & MUTED_BY_STREAM_VOLUME) != 0) { builder.append("streamVolume "); } - if ((mEventValue & MUTED_BY_STREAM_MUTED) != 0) { + if ((eventValue & MUTED_BY_STREAM_MUTED) != 0) { builder.append("streamMute "); } - if ((mEventValue & MUTED_BY_APP_OPS) != 0) { + if ((eventValue & MUTED_BY_APP_OPS) != 0) { builder.append("appOps "); } - if ((mEventValue & MUTED_BY_CLIENT_VOLUME) != 0) { + if ((eventValue & MUTED_BY_CLIENT_VOLUME) != 0) { builder.append("clientVolume "); } - if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) { + if ((eventValue & MUTED_BY_VOLUME_SHAPER) != 0) { builder.append("volumeShaper "); } - if ((mEventValue & MUTED_BY_PORT_VOLUME) != 0) { + if ((eventValue & MUTED_BY_PORT_VOLUME) != 0) { builder.append("portVolume "); } } @@ -1732,8 +1735,11 @@ public final class PlaybackActivityMonitor synchronized (mPlayerLock) { int piid = msg.arg1; + + int[] eventValues = new int[1]; + eventValues[0] = eventValue; sEventLogger.enqueue( - new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue)); + new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValues)); final AudioPlaybackConfiguration apc = mPlayers.get(piid); if (apc == null || !apc.handleMutedEvent(eventValue)) { diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 82d5d4d8141d..e8786be4d8e6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -677,10 +677,11 @@ public class BiometricScheduler<T, U> { * Start the timeout for the watchdog. */ public void startWatchdog() { - if (mCurrentOperation == null) { + final BiometricSchedulerOperation operation = mCurrentOperation; + if (operation == null) { + Slog.e(TAG, "Current operation is null,no need to start watchdog"); return; } - final BiometricSchedulerOperation operation = mCurrentOperation; mHandler.postDelayed(() -> { if (operation == mCurrentOperation && !operation.isFinished()) { Counter.logIncrement("biometric.value_scheduler_watchdog_triggered_count"); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index abb756b294a3..62fcccf13da9 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -5283,6 +5283,23 @@ public final class DisplayManagerService extends SystemService { } @Override + public void setScreenBrightnessOverrideFromWindowManager( + SparseArray<DisplayBrightnessOverrideRequest> brightnessOverrides) { + SparseArray<DisplayPowerController> dpcs = new SparseArray<>(); + synchronized (mSyncRoot) { + for (int i = 0; i < mDisplayPowerControllers.size(); i++) { + dpcs.put(mDisplayPowerControllers.keyAt(i), + mDisplayPowerControllers.valueAt(i)); + } + } + for (int i = 0; i < dpcs.size(); ++i) { + final int displayId = dpcs.keyAt(i); + final DisplayPowerController dpc = dpcs.valueAt(i); + dpc.setBrightnessOverrideRequest(brightnessOverrides.get(displayId)); + } + } + + @Override public boolean requestPowerState(int groupId, DisplayPowerRequest request, boolean waitForNegativeProximity) { synchronized (mSyncRoot) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index c90dfbf5456e..2948ae4d83fa 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -42,6 +42,7 @@ import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; @@ -170,6 +171,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18; private static final int MSG_SET_STYLUS_BEING_USED = 19; private static final int MSG_SET_STYLUS_USE_ENDED = 20; + private static final int MSG_SET_WINDOW_MANAGER_BRIGHTNESS_OVERRIDE = 21; private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500; @@ -850,6 +852,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + public void setBrightnessOverrideRequest( + DisplayManagerInternal.DisplayBrightnessOverrideRequest request) { + Message msg = mHandler.obtainMessage(MSG_SET_WINDOW_MANAGER_BRIGHTNESS_OVERRIDE, request); + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); + } + public void setDisplayOffloadSession(DisplayOffloadSession session) { if (session == mDisplayOffloadSession) { return; @@ -3100,6 +3108,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call updatePowerState(); break; + case MSG_SET_WINDOW_MANAGER_BRIGHTNESS_OVERRIDE: + if (mDisplayBrightnessController.updateWindowManagerBrightnessOverride( + (DisplayManagerInternal.DisplayBrightnessOverrideRequest) msg.obj)) { + updatePowerState(); + } + break; + case MSG_STOP: cleanupHandlerThreadAfterStop(); break; diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index 5b12dfb951d5..35f6fd047ec6 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -180,6 +180,20 @@ public final class DisplayBrightnessController { } /** + * Updates the brightness override from WindowManager. + * + * @param request The request to override the brightness + * @return whether this request will result in a change of the brightness + */ + public boolean updateWindowManagerBrightnessOverride( + DisplayManagerInternal.DisplayBrightnessOverrideRequest request) { + synchronized (mLock) { + return mDisplayBrightnessStrategySelector.getOverrideBrightnessStrategy() + .updateWindowManagerBrightnessOverride(request); + } + } + + /** * Sets the brightness to follow */ public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) { diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index 60c1b8ff4db9..a0ad49dd9f24 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -180,8 +180,10 @@ public class DisplayBrightnessStrategySelector { displayBrightnessStrategy = mFollowerBrightnessStrategy; } else if (displayPowerRequest.boostScreenBrightness) { displayBrightnessStrategy = mBoostBrightnessStrategy; - } else if (BrightnessUtils - .isValidBrightnessValue(displayPowerRequest.screenBrightnessOverride)) { + } else if (BrightnessUtils.isValidBrightnessValue( + displayPowerRequest.screenBrightnessOverride) + || BrightnessUtils.isValidBrightnessValue( + mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride())) { displayBrightnessStrategy = mOverrideBrightnessStrategy; } else if (BrightnessUtils.isValidBrightnessValue( mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) { @@ -256,6 +258,10 @@ public class DisplayBrightnessStrategySelector { return mAutoBrightnessFallbackStrategy; } + public OverrideBrightnessStrategy getOverrideBrightnessStrategy() { + return mOverrideBrightnessStrategy; + } + /** * Dumps the state of this class. */ diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java index 3fc15d120434..649f9dacf8ab 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java @@ -16,10 +16,13 @@ package com.android.server.display.brightness.strategy; +import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.os.PowerManager; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.brightness.StrategyExecutionRequest; import com.android.server.display.brightness.StrategySelectionNotifyRequest; @@ -29,6 +32,10 @@ import java.io.PrintWriter; * Manages the brightness of the display when the system brightness is overridden */ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy { + + private float mWindowManagerBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; + private CharSequence mWindowManagerBrightnessOverrideTag = null; + @Override public DisplayBrightnessState updateBrightness( StrategyExecutionRequest strategyExecutionRequest) { @@ -36,9 +43,18 @@ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy { // the brightness DisplayPowerRequest dpr = strategyExecutionRequest.getDisplayPowerRequest(); BrightnessReason reason = new BrightnessReason(BrightnessReason.REASON_OVERRIDE); - reason.setTag(dpr.screenBrightnessOverrideTag); + + float brightness = dpr.screenBrightnessOverride; + if (BrightnessUtils.isValidBrightnessValue(dpr.screenBrightnessOverride)) { + brightness = dpr.screenBrightnessOverride; + reason.setTag(dpr.screenBrightnessOverrideTag); + } else if (BrightnessUtils.isValidBrightnessValue(mWindowManagerBrightnessOverride)) { + brightness = mWindowManagerBrightnessOverride; + reason.setTag(mWindowManagerBrightnessOverrideTag); + } + return new DisplayBrightnessState.Builder() - .setBrightness(dpr.screenBrightnessOverride) + .setBrightness(brightness) .setBrightnessReason(reason) .setDisplayBrightnessStrategyName(getName()) .build(); @@ -50,7 +66,12 @@ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy { } @Override - public void dump(PrintWriter writer) {} + public void dump(PrintWriter writer) { + writer.println("OverrideBrightnessStrategy:"); + writer.println(" mWindowManagerBrightnessOverride=" + mWindowManagerBrightnessOverride); + writer.println(" mWindowManagerBrightnessOverrideTag=" + + mWindowManagerBrightnessOverrideTag); + } @Override public void strategySelectionPostProcessor( @@ -58,6 +79,37 @@ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy { // DO NOTHING } + /** + * Updates the brightness override from WindowManager. + * + * @param request The request to override the brightness + * @return whether this request will result in a change of the brightness + */ + public boolean updateWindowManagerBrightnessOverride( + DisplayManagerInternal.DisplayBrightnessOverrideRequest request) { + float newBrightness = request == null + ? PowerManager.BRIGHTNESS_INVALID_FLOAT : request.brightness; + mWindowManagerBrightnessOverrideTag = request == null ? null : request.tag; + + if (floatEquals(newBrightness, mWindowManagerBrightnessOverride)) { + return false; + } + + mWindowManagerBrightnessOverride = newBrightness; + return true; + } + + /** + * Returns the current brightness override from WindowManager. + */ + public float getWindowManagerBrightnessOverride() { + return mWindowManagerBrightnessOverride; + } + + private boolean floatEquals(float f1, float f2) { + return f1 == f2 || (Float.isNaN(f1) && Float.isNaN(f2)); + } + @Override public int getReason() { return BrightnessReason.REASON_OVERRIDE; diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index edad2473061c..e5b077d23bec 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -3031,6 +3031,11 @@ public class InputManagerService extends IInputManager.Stub return mKeyGestureController.getAppLaunchBookmarks(); } + @Override + public void resetLockedModifierState() { + mNative.resetLockedModifierState(); + } + private void handleCurrentUserChanged(@UserIdInt int userId) { mCurrentUserId = userId; mKeyGestureController.setCurrentUserId(userId); diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 8903c27b491a..728e44062e82 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -98,6 +98,8 @@ interface NativeInputManagerService { void toggleCapsLock(int deviceId); + void resetLockedModifierState(); + void displayRemoved(int displayId); void setInputDispatchMode(boolean enabled, boolean frozen); @@ -370,6 +372,9 @@ interface NativeInputManagerService { public native void toggleCapsLock(int deviceId); @Override + public native void resetLockedModifierState(); + + @Override public native void displayRemoved(int displayId); @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d8483f721306..b7af9a4b17bd 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1310,7 +1310,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Do not reset the default (current) IME when it is a 3rd-party IME String selectedMethodId = bindingController.getSelectedMethodId(); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); - if (selectedMethodId != null + if (selectedMethodId != null && settings.getMethodMap().get(selectedMethodId) != null && !settings.getMethodMap().get(selectedMethodId).isSystem()) { return; } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java index 0fdd0ae641df..5248a051404d 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java @@ -237,15 +237,16 @@ import java.util.function.Consumer; if (message.isBroadcastMessage()) { if (message.isReliable()) { - Log.e(TAG, "Received reliable broadcast message from " + message.getNanoAppId()); + Log.e(TAG, "Received reliable broadcast message from 0x" + + Long.toHexString(message.getNanoAppId())); return ErrorCode.PERMANENT_ERROR; } // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API // requirements. if (!messagePermissions.isEmpty()) { - Log.e(TAG, "Received broadcast message with permissions from " - + message.getNanoAppId()); + Log.e(TAG, "Received broadcast message with permissions from 0x" + + Long.toHexString(message.getNanoAppId())); return ErrorCode.PERMANENT_ERROR; } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 8cf0578523ad..7c9d9c57a8b0 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -326,8 +326,15 @@ public class ContextHubService extends IContextHubService.Stub { } if (Flags.offloadApi()) { - mHubInfoRegistry = new HubInfoRegistry(mContextHubWrapper); - Log.i(TAG, "Enabling generic offload API"); + HubInfoRegistry registry; + try { + registry = new HubInfoRegistry(mContextHubWrapper); + Log.i(TAG, "Enabling generic offload API"); + } catch (UnsupportedOperationException e) { + registry = null; + Log.w(TAG, "Generic offload API not supported, disabling"); + } + mHubInfoRegistry = registry; } else { mHubInfoRegistry = null; Log.i(TAG, "Disabling generic offload API"); @@ -768,6 +775,16 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override + public List<HubEndpointInfo> findEndpointsWithService(String serviceDescriptor) { + super.findEndpointsWithService_enforcePermission(); + if (mHubInfoRegistry == null) { + return Collections.emptyList(); + } + return mHubInfoRegistry.findEndpointsWithService(serviceDescriptor); + } + + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @Override public IContextHubEndpoint registerEndpoint( HubEndpointInfo pendingHubEndpointInfo, IContextHubEndpointCallback callback) throws RemoteException { diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java index 4d1000f3e0e5..d2b2331d54f3 100644 --- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java +++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java @@ -17,6 +17,7 @@ package com.android.server.location.contexthub; import android.hardware.contexthub.HubEndpointInfo; +import android.hardware.contexthub.HubServiceInfo; import android.hardware.location.HubInfo; import android.os.RemoteException; import android.util.ArrayMap; @@ -127,6 +128,23 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl return searchResult; } + /** + * Return a list of {@link HubEndpointInfo} that represents endpoints with the matching service. + */ + public List<HubEndpointInfo> findEndpointsWithService(String serviceDescriptor) { + List<HubEndpointInfo> searchResult = new ArrayList<>(); + synchronized (mLock) { + for (HubEndpointInfo endpointInfo : mHubEndpointInfos.values()) { + for (HubServiceInfo serviceInfo : endpointInfo.getServiceInfoCollection()) { + if (serviceDescriptor.equals(serviceInfo.getServiceDescriptor())) { + searchResult.add(endpointInfo); + } + } + } + } + return searchResult; + } + void dump(IndentingPrintWriter ipw) { synchronized (mLock) { dumpLocked(ipw); @@ -155,5 +173,4 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl ipw.println(); } - } diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index fce008c23350..df5ecf872df4 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -1006,9 +1006,9 @@ public final class MediaProjectionManagerService extends SystemService // Host app has 5 minutes to begin using the token before it is invalid. // Some apps show a dialog for the user to interact with (selecting recording resolution) // before starting capture, but after requesting consent. - final long mDefaultTimeoutMs = Duration.ofMinutes(5).toMillis(); + final long mDefaultTimeoutMillis = Duration.ofMinutes(5).toMillis(); // The creation timestamp in milliseconds, measured by {@link SystemClock#uptimeMillis}. - private final long mCreateTimeMs; + private final long mCreateTimeMillis; public final int uid; public final String packageName; public final UserHandle userHandle; @@ -1017,7 +1017,7 @@ public final class MediaProjectionManagerService extends SystemService private final int mType; // Values for tracking token validity. // Timeout value to compare creation time against. - private final long mTimeoutMs = mDefaultTimeoutMs; + private final long mTimeoutMillis = mDefaultTimeoutMillis; private final int mDisplayId; private IMediaProjectionCallback mCallback; @@ -1048,7 +1048,7 @@ public final class MediaProjectionManagerService extends SystemService userHandle = new UserHandle(UserHandle.getUserId(uid)); mTargetSdkVersion = targetSdkVersion; mIsPrivileged = isPrivileged; - mCreateTimeMs = mClock.uptimeMillis(); + mCreateTimeMillis = mClock.uptimeMillis(); mActivityManagerInternal.notifyMediaProjectionEvent(uid, asBinder(), MEDIA_PROJECTION_TOKEN_EVENT_CREATED); mDisplayId = displayId; @@ -1209,7 +1209,7 @@ public final class MediaProjectionManagerService extends SystemService } } Slog.d(TAG, "Content Recording: handling stopping this projection token" - + " createTime= " + mCreateTimeMs + + " createTime= " + mCreateTimeMillis + " countStarts= " + mCountStarts); stopProjectionLocked(this); mToken.unlinkToDeath(mDeathEater, 0); @@ -1273,7 +1273,7 @@ public final class MediaProjectionManagerService extends SystemService } long getCreateTimeMillis() { - return mCreateTimeMs; + return mCreateTimeMillis; } @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION) @@ -1281,8 +1281,8 @@ public final class MediaProjectionManagerService extends SystemService public boolean isValid() { isValid_enforcePermission(); synchronized (mLock) { - final long curMs = mClock.uptimeMillis(); - final boolean hasTimedOut = curMs - mCreateTimeMs > mTimeoutMs; + final long curMillis = mClock.uptimeMillis(); + final boolean hasTimedOut = curMillis - mCreateTimeMillis > mTimeoutMillis; final boolean virtualDisplayCreated = mVirtualDisplayId != INVALID_DISPLAY; final boolean isValid = !hasTimedOut && (mCountStarts <= 1) && !virtualDisplayCreated; diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index 1f8a2007a9ff..00bab8af44f3 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -25,11 +25,11 @@ import android.media.quality.IAmbientBacklightCallback; import android.media.quality.IMediaQualityManager; import android.media.quality.IPictureProfileCallback; import android.media.quality.ISoundProfileCallback; -import android.media.quality.MediaQualityContract.PictureQuality; +import android.media.quality.MediaQualityContract; import android.media.quality.ParamCapability; import android.media.quality.PictureProfile; import android.media.quality.SoundProfile; -import android.os.Bundle; +import android.os.PersistableBundle; import android.util.Log; import com.android.server.SystemService; @@ -74,10 +74,10 @@ public class MediaQualityService extends SystemService { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(PictureQuality.PARAMETER_TYPE, pp.getProfileType()); - values.put(PictureQuality.PARAMETER_NAME, pp.getName()); - values.put(PictureQuality.PARAMETER_PACKAGE, pp.getPackageName()); - values.put(PictureQuality.PARAMETER_INPUT_ID, pp.getInputId()); + values.put(MediaQualityContract.BaseParameters.PARAMETER_TYPE, pp.getProfileType()); + values.put(MediaQualityContract.BaseParameters.PARAMETER_NAME, pp.getName()); + values.put(MediaQualityContract.BaseParameters.PARAMETER_PACKAGE, pp.getPackageName()); + values.put(MediaQualityContract.BaseParameters.PARAMETER_INPUT_ID, pp.getInputId()); values.put(mMediaQualityDbHelper.SETTINGS, bundleToJson(pp.getParameters())); // id is auto-generated by SQLite upon successful insertion of row @@ -98,8 +98,8 @@ public class MediaQualityService extends SystemService { public PictureProfile getPictureProfile(int type, String name) { SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase(); - String selection = PictureQuality.PARAMETER_TYPE + " = ? AND " - + PictureQuality.PARAMETER_NAME + " = ?"; + String selection = MediaQualityContract.BaseParameters.PARAMETER_TYPE + " = ? AND " + + MediaQualityContract.BaseParameters.PARAMETER_NAME + " = ?"; String[] selectionArguments = {Integer.toString(type), name}; try ( @@ -127,7 +127,7 @@ public class MediaQualityService extends SystemService { } } - private String bundleToJson(Bundle bundle) { + private String bundleToJson(PersistableBundle bundle) { JSONObject jsonObject = new JSONObject(); if (bundle == null) { return jsonObject.toString(); @@ -142,9 +142,9 @@ public class MediaQualityService extends SystemService { return jsonObject.toString(); } - private Bundle jsonToBundle(String jsonString) { + private PersistableBundle jsonToBundle(String jsonString) { JSONObject jsonObject = null; - Bundle bundle = new Bundle(); + PersistableBundle bundle = new PersistableBundle(); try { jsonObject = new JSONObject(jsonString); @@ -175,26 +175,26 @@ public class MediaQualityService extends SystemService { private String[] getAllPictureProfileColumns() { return new String[]{ - PictureQuality.PARAMETER_ID, - PictureQuality.PARAMETER_TYPE, - PictureQuality.PARAMETER_NAME, - PictureQuality.PARAMETER_INPUT_ID, - PictureQuality.PARAMETER_PACKAGE, + MediaQualityContract.BaseParameters.PARAMETER_ID, + MediaQualityContract.BaseParameters.PARAMETER_TYPE, + MediaQualityContract.BaseParameters.PARAMETER_NAME, + MediaQualityContract.BaseParameters.PARAMETER_INPUT_ID, + MediaQualityContract.BaseParameters.PARAMETER_PACKAGE, mMediaQualityDbHelper.SETTINGS }; } private PictureProfile getPictureProfileFromCursor(Cursor cursor) { - String returnId = cursor.getString( - cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_ID)); - int type = cursor.getInt( - cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_TYPE)); - String name = cursor.getString( - cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_NAME)); - String inputId = cursor.getString( - cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_INPUT_ID)); - String packageName = cursor.getString( - cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_PACKAGE)); + String returnId = cursor.getString(cursor.getColumnIndexOrThrow( + MediaQualityContract.BaseParameters.PARAMETER_ID)); + int type = cursor.getInt(cursor.getColumnIndexOrThrow( + MediaQualityContract.BaseParameters.PARAMETER_TYPE)); + String name = cursor.getString(cursor.getColumnIndexOrThrow( + MediaQualityContract.BaseParameters.PARAMETER_NAME)); + String inputId = cursor.getString(cursor.getColumnIndexOrThrow( + MediaQualityContract.BaseParameters.PARAMETER_INPUT_ID)); + String packageName = cursor.getString(cursor.getColumnIndexOrThrow( + MediaQualityContract.BaseParameters.PARAMETER_PACKAGE)); String settings = cursor.getString( cursor.getColumnIndexOrThrow(mMediaQualityDbHelper.SETTINGS)); return new PictureProfile(returnId, type, name, inputId, @@ -203,7 +203,7 @@ public class MediaQualityService extends SystemService { @Override public List<PictureProfile> getPictureProfilesByPackage(String packageName) { - String selection = PictureQuality.PARAMETER_PACKAGE + " = ?"; + String selection = MediaQualityContract.BaseParameters.PARAMETER_PACKAGE + " = ?"; String[] selectionArguments = {packageName}; return getPictureProfilesBasedOnConditions(getAllPictureProfileColumns(), selection, selectionArguments); @@ -216,7 +216,7 @@ public class MediaQualityService extends SystemService { @Override public List<String> getPictureProfilePackageNames() { - String [] column = {PictureQuality.PARAMETER_NAME}; + String [] column = {MediaQualityContract.BaseParameters.PARAMETER_NAME}; List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column, null, null); List<String> packageNames = new ArrayList<>(); diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java index 925ba1752fe2..3e96afe9bee3 100644 --- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java +++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java @@ -39,9 +39,11 @@ import android.service.notification.ZenModeConfig.ConfigOrigin; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.Keep; /** Default implementation for {@link DeviceEffectsApplier}. */ -class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { +@Keep +public class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { private static final String TAG = "DeviceEffectsApplier"; private static final String SUPPRESS_AMBIENT_DISPLAY_TOKEN = "DefaultDeviceEffectsApplier:SuppressAmbientDisplay"; @@ -63,10 +65,10 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { @GuardedBy("mRegisterReceiverLock") private boolean mIsScreenOffReceiverRegistered; - private ZenDeviceEffects mLastAppliedEffects = new ZenDeviceEffects.Builder().build(); + protected ZenDeviceEffects mLastAppliedEffects = new ZenDeviceEffects.Builder().build(); private boolean mPendingNightMode; - DefaultDeviceEffectsApplier(Context context) { + public DefaultDeviceEffectsApplier(Context context) { mContext = context; mColorDisplayManager = context.getSystemService(ColorDisplayManager.class); mKeyguardManager = context.getSystemService(KeyguardManager.class); @@ -79,56 +81,69 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { @Override public void apply(ZenDeviceEffects effects, @ConfigOrigin int origin) { - Binder.withCleanCallingIdentity(() -> { - if (mLastAppliedEffects.shouldSuppressAmbientDisplay() - != effects.shouldSuppressAmbientDisplay()) { - try { - traceApplyDeviceEffect("suppressAmbientDisplay", - effects.shouldSuppressAmbientDisplay()); - mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN, - effects.shouldSuppressAmbientDisplay()); - } catch (Exception e) { - Slog.e(TAG, "Could not change AOD override", e); - } - } + Binder.withCleanCallingIdentity( + () -> { + maybeSuppressAmbientDisplay(effects.shouldSuppressAmbientDisplay()); + maybeDisplayGrayscale(effects.shouldDisplayGrayscale()); + maybeDimWallpaper(effects.shouldDimWallpaper()); + maybeUseNightMode(effects.shouldUseNightMode(), origin); + }); - if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) { - if (mColorDisplayManager != null) { - try { - traceApplyDeviceEffect("displayGrayscale", - effects.shouldDisplayGrayscale()); - mColorDisplayManager.setSaturationLevel( - effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE - : SATURATION_LEVEL_FULL_COLOR); - } catch (Exception e) { - Slog.e(TAG, "Could not change grayscale override", e); - } - } + mLastAppliedEffects = effects; + } + + protected void maybeSuppressAmbientDisplay(boolean shouldSuppressAmbientDisplay) { + if (mLastAppliedEffects.shouldSuppressAmbientDisplay() != shouldSuppressAmbientDisplay) { + try { + traceApplyDeviceEffect("suppressAmbientDisplay", shouldSuppressAmbientDisplay); + mPowerManager.suppressAmbientDisplay( + SUPPRESS_AMBIENT_DISPLAY_TOKEN, shouldSuppressAmbientDisplay); + } catch (Exception e) { + Slog.e(TAG, "Could not change AOD override", e); } + } + } - if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) { - if (mWallpaperManager != null) { - try { - traceApplyDeviceEffect("dimWallpaper", effects.shouldDimWallpaper()); - mWallpaperManager.setWallpaperDimAmount( - effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED - : WALLPAPER_DIM_AMOUNT_NORMAL); - } catch (Exception e) { - Slog.e(TAG, "Could not change wallpaper override", e); - } + protected void maybeDisplayGrayscale(boolean shouldDisplayGrayscale) { + if (mLastAppliedEffects.shouldDisplayGrayscale() != shouldDisplayGrayscale) { + if (mColorDisplayManager != null) { + try { + traceApplyDeviceEffect("displayGrayscale", shouldDisplayGrayscale); + mColorDisplayManager.setSaturationLevel( + shouldDisplayGrayscale + ? SATURATION_LEVEL_GRAYSCALE + : SATURATION_LEVEL_FULL_COLOR); + } catch (Exception e) { + Slog.e(TAG, "Could not change grayscale override", e); } } + } + } - if (mLastAppliedEffects.shouldUseNightMode() != effects.shouldUseNightMode()) { + protected void maybeDimWallpaper(boolean shouldDimWallpaper) { + if (mLastAppliedEffects.shouldDimWallpaper() != shouldDimWallpaper) { + if (mWallpaperManager != null) { try { - updateOrScheduleNightMode(effects.shouldUseNightMode(), origin); + traceApplyDeviceEffect("dimWallpaper", shouldDimWallpaper); + mWallpaperManager.setWallpaperDimAmount( + shouldDimWallpaper + ? WALLPAPER_DIM_AMOUNT_DIMMED + : WALLPAPER_DIM_AMOUNT_NORMAL); } catch (Exception e) { - Slog.e(TAG, "Could not change dark theme override", e); + Slog.e(TAG, "Could not change wallpaper override", e); } } - }); + } + } - mLastAppliedEffects = effects; + protected void maybeUseNightMode(boolean shouldUseNightMode, @ConfigOrigin int origin) { + if (mLastAppliedEffects.shouldUseNightMode() != shouldUseNightMode) { + try { + updateOrScheduleNightMode(shouldUseNightMode, origin); + } catch (Exception e) { + Slog.e(TAG, "Could not change dark theme override", e); + } + } } private void updateOrScheduleNightMode(boolean useNightMode, @ConfigOrigin int origin) { diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index d9e76966892c..8168c5493304 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -1029,12 +1029,14 @@ final class InstallPackageHelper { if (reconciledPackages == null) { return; } - if (Flags.improveInstallFreeze()) { - prepPerformDexoptIfNeeded(reconciledPackages); - } - if (renameAndUpdatePaths(requests) - && commitInstallPackages(reconciledPackages)) { - success = true; + if (renameAndUpdatePaths(requests)) { + // rename before dexopt because art will encoded the path in the odex/vdex file + if (Flags.improveInstallFreeze()) { + prepPerformDexoptIfNeeded(reconciledPackages); + } + if (commitInstallPackages(reconciledPackages)) { + success = true; + } } } } finally { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 37883f594227..c573293cbf48 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -625,14 +625,6 @@ public final class PowerManagerService extends SystemService boolean mIsFaceDown = false; private long mLastFlipTime = 0L; - // The screen brightness setting override from the window manager - // to allow the current foreground activity to override the brightness. - private float mScreenBrightnessOverrideFromWindowManager = - PowerManager.BRIGHTNESS_INVALID_FLOAT; - - // Tag identifying the window/activity that requested the brightness override. - private CharSequence mScreenBrightnessOverrideFromWmTag = null; - // The window manager has determined the user to be inactive via other means. // Set this to false to disable. private boolean mUserInactiveOverrideFromWindowManager; @@ -3663,9 +3655,7 @@ public final class PowerManagerService extends SystemService // Keep the brightness steady during boot. This requires the // bootloader brightness and the default brightness to be identical. screenBrightnessOverride = mScreenBrightnessDefault; - } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) { - screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager; - overrideTag = mScreenBrightnessOverrideFromWmTag; + overrideTag = "boot"; } else { screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; } @@ -4449,19 +4439,6 @@ public final class PowerManagerService extends SystemService } } - private void setScreenBrightnessOverrideFromWindowManagerInternal( - float brightness, CharSequence tag) { - synchronized (mLock) { - if (!BrightnessSynchronizer.floatEquals(mScreenBrightnessOverrideFromWindowManager, - brightness)) { - mScreenBrightnessOverrideFromWindowManager = brightness; - mScreenBrightnessOverrideFromWmTag = tag; - mDirty |= DIRTY_SETTINGS; - updatePowerStateLocked(); - } - } - } - private void setUserInactiveOverrideFromWindowManagerInternal() { synchronized (mLock) { mUserInactiveOverrideFromWindowManager = true; @@ -4800,10 +4777,6 @@ public final class PowerManagerService extends SystemService + mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced=" + isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")"); pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting); - pw.println(" mScreenBrightnessOverrideFromWindowManager=" - + mScreenBrightnessOverrideFromWindowManager); - pw.println(" mScreenBrightnessOverrideFromWmTag=" - + mScreenBrightnessOverrideFromWmTag); pw.println(" mUserActivityTimeoutOverrideFromWindowManager=" + mUserActivityTimeoutOverrideFromWindowManager); pw.println(" mUserInactiveOverrideFromWindowManager=" @@ -5193,10 +5166,6 @@ public final class PowerManagerService extends SystemService proto.write( PowerServiceSettingsAndConfigurationDumpProto - .SCREEN_BRIGHTNESS_OVERRIDE_FROM_WINDOW_MANAGER, - mScreenBrightnessOverrideFromWindowManager); - proto.write( - PowerServiceSettingsAndConfigurationDumpProto .USER_ACTIVITY_TIMEOUT_OVERRIDE_FROM_WINDOW_MANAGER_MS, mUserActivityTimeoutOverrideFromWindowManager); proto.write( @@ -7136,17 +7105,6 @@ public final class PowerManagerService extends SystemService @VisibleForTesting final class LocalService extends PowerManagerInternal { @Override - public void setScreenBrightnessOverrideFromWindowManager( - float screenBrightness, CharSequence tag) { - if (screenBrightness < PowerManager.BRIGHTNESS_MIN - || screenBrightness > PowerManager.BRIGHTNESS_MAX) { - screenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; - tag = null; - } - setScreenBrightnessOverrideFromWindowManagerInternal(screenBrightness, tag); - } - - @Override public void setDozeOverrideFromDreamManager( int screenState, int reason, float screenBrightnessFloat, int screenBrightnessInt, boolean useNormalBrightnessForDoze) { diff --git a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java index b8a4a9c26feb..6798a6146ae0 100644 --- a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java +++ b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java @@ -16,8 +16,11 @@ package com.android.server.security.authenticationpolicy; +import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE; + import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; +import android.annotation.EnforcePermission; import android.app.KeyguardManager; import android.content.Context; import android.content.pm.PackageManager; @@ -32,9 +35,14 @@ import android.hardware.biometrics.events.AuthenticationStoppedInfo; import android.hardware.biometrics.events.AuthenticationSucceededInfo; import android.os.Build; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.security.authenticationpolicy.AuthenticationPolicyManager; +import android.security.authenticationpolicy.DisableSecureLockDeviceParams; +import android.security.authenticationpolicy.EnableSecureLockDeviceParams; +import android.security.authenticationpolicy.IAuthenticationPolicyService; import android.util.Log; import android.util.Slog; import android.util.SparseIntArray; @@ -74,6 +82,7 @@ public class AuthenticationPolicyService extends SystemService { private final KeyguardManager mKeyguardManager; private final WindowManagerInternal mWindowManager; private final UserManagerInternal mUserManager; + private SecureLockDeviceServiceInternal mSecureLockDeviceService; @VisibleForTesting final SparseIntArray mFailedAttemptsForUser = new SparseIntArray(); private final SparseLongArray mLastLockedTimestamp = new SparseLongArray(); @@ -94,10 +103,16 @@ public class AuthenticationPolicyService extends SystemService { mWindowManager = Objects.requireNonNull( LocalServices.getService(WindowManagerInternal.class)); mUserManager = Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class)); + if (android.security.Flags.secureLockdown()) { + mSecureLockDeviceService = Objects.requireNonNull( + LocalServices.getService(SecureLockDeviceServiceInternal.class)); + } } @Override - public void onStart() {} + public void onStart() { + publishBinderService(Context.AUTHENTICATION_POLICY_SERVICE, mService); + } @Override public void onBootPhase(int phase) { @@ -294,4 +309,36 @@ public class AuthenticationPolicyService extends SystemService { // next successful primary or biometric auth happens mLastLockedTimestamp.put(userId, SystemClock.elapsedRealtime()); } + + private final IBinder mService = new IAuthenticationPolicyService.Stub() { + /** + * @see AuthenticationPolicyManager#enableSecureLockDevice(EnableSecureLockDeviceParams) + * @param params EnableSecureLockDeviceParams for caller to supply params related + * to the secure lock device request + * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the Secure + * Lock Device request + */ + @Override + @EnforcePermission(MANAGE_SECURE_LOCK_DEVICE) + @AuthenticationPolicyManager.EnableSecureLockDeviceRequestStatus + public int enableSecureLockDevice(EnableSecureLockDeviceParams params) { + enableSecureLockDevice_enforcePermission(); + return mSecureLockDeviceService.enableSecureLockDevice(params); + } + + /** + * @see AuthenticationPolicyManager#disableSecureLockDevice(DisableSecureLockDeviceParams) + * @param params @DisableSecureLockDeviceParams for caller to supply params related + * to the secure lock device request + * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the Secure + * Lock Device request + */ + @Override + @EnforcePermission(MANAGE_SECURE_LOCK_DEVICE) + @AuthenticationPolicyManager.DisableSecureLockDeviceRequestStatus + public int disableSecureLockDevice(DisableSecureLockDeviceParams params) { + disableSecureLockDevice_enforcePermission(); + return mSecureLockDeviceService.disableSecureLockDevice(params); + } + }; } diff --git a/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java new file mode 100644 index 000000000000..7b89723deb6c --- /dev/null +++ b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java @@ -0,0 +1,120 @@ +/* + * 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.security.authenticationpolicy; + +import android.annotation.NonNull; +import android.content.Context; +import android.security.authenticationpolicy.AuthenticationPolicyManager; +import android.security.authenticationpolicy.AuthenticationPolicyManager.DisableSecureLockDeviceRequestStatus; +import android.security.authenticationpolicy.AuthenticationPolicyManager.EnableSecureLockDeviceRequestStatus; +import android.security.authenticationpolicy.DisableSecureLockDeviceParams; +import android.security.authenticationpolicy.EnableSecureLockDeviceParams; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.SystemService; + +/** + * System service for remotely calling secure lock on the device. + * + * Callers will access this class via + * {@link com.android.server.security.authenticationpolicy.AuthenticationPolicyService}. + * + * @see AuthenticationPolicyService + * @see AuthenticationPolicyManager#enableSecureLockDevice + * @see AuthenticationPolicyManager#disableSecureLockDevice + * @hide + */ +public class SecureLockDeviceService extends SecureLockDeviceServiceInternal { + private static final String TAG = "SecureLockDeviceService"; + private final Context mContext; + + public SecureLockDeviceService(@NonNull Context context) { + mContext = context; + } + + private void start() { + // Expose private service for system components to use. + LocalServices.addService(SecureLockDeviceServiceInternal.class, this); + } + + /** + * @see AuthenticationPolicyManager#enableSecureLockDevice + * @param params EnableSecureLockDeviceParams for caller to supply params related + * to the secure lock device request + * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the + * secure lock device request + * + * @hide + */ + @Override + @EnableSecureLockDeviceRequestStatus + public int enableSecureLockDevice(EnableSecureLockDeviceParams params) { + // (1) Call into system_server to lock device, configure allowed auth types + // for secure lock + // TODO: lock device, configure allowed authentication types for device entry + // (2) Call into framework to configure secure lock 2FA lockscreen + // update, UI & string updates + // TODO: implement 2FA lockscreen when SceneContainerFlag.isEnabled() + // TODO: implement 2FA lockscreen when !SceneContainerFlag.isEnabled() + // (3) Call into framework to configure keyguard security updates + // TODO: implement security updates + return AuthenticationPolicyManager.ERROR_UNSUPPORTED; + } + + /** + * @see AuthenticationPolicyManager#disableSecureLockDevice + * @param params @DisableSecureLockDeviceParams for caller to supply params related + * to the secure lock device request + * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the + * secure lock device request + * + * @hide + */ + @Override + @DisableSecureLockDeviceRequestStatus + public int disableSecureLockDevice(DisableSecureLockDeviceParams params) { + // (1) Call into system_server to reset allowed auth types + // TODO: reset allowed authentication types for device entry; + // (2) Call into framework to disable secure lock 2FA lockscreen, reset UI + // & string updates + // TODO: implement reverting to normal lockscreen when SceneContainerFlag.isEnabled() + // TODO: implement reverting to normal lockscreen when !SceneContainerFlag.isEnabled() + // (3) Call into framework to revert keyguard security updates + // TODO: implement reverting security updates + return AuthenticationPolicyManager.ERROR_UNSUPPORTED; + } + + /** + * System service lifecycle. + */ + public static final class Lifecycle extends SystemService { + private final SecureLockDeviceService mService; + + public Lifecycle(@NonNull Context context) { + super(context); + mService = new SecureLockDeviceService(context); + } + + @Override + public void onStart() { + Slog.i(TAG, "Starting SecureLockDeviceService"); + mService.start(); + Slog.i(TAG, "Started SecureLockDeviceService"); + } + } +} diff --git a/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java new file mode 100644 index 000000000000..b90370956d8b --- /dev/null +++ b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java @@ -0,0 +1,55 @@ +/* + * 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.security.authenticationpolicy; + +import android.security.authenticationpolicy.AuthenticationPolicyManager; +import android.security.authenticationpolicy.AuthenticationPolicyManager.DisableSecureLockDeviceRequestStatus; +import android.security.authenticationpolicy.AuthenticationPolicyManager.EnableSecureLockDeviceRequestStatus; +import android.security.authenticationpolicy.DisableSecureLockDeviceParams; +import android.security.authenticationpolicy.EnableSecureLockDeviceParams; + +/** + * Local system service interface for {@link SecureLockDeviceService}. + * + * <p>No permission / argument checks will be performed inside. + * Callers must check the calling app permission and the calling package name. + * + * @hide + */ +public abstract class SecureLockDeviceServiceInternal { + private static final String TAG = "SecureLockDeviceServiceInternal"; + + /** + * @see AuthenticationPolicyManager#enableSecureLockDevice(EnableSecureLockDeviceParams) + * @param params EnableSecureLockDeviceParams for caller to supply params related + * to the secure lock request + * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the + * secure lock request + */ + @EnableSecureLockDeviceRequestStatus + public abstract int enableSecureLockDevice(EnableSecureLockDeviceParams params); + + /** + * @see AuthenticationPolicyManager#disableSecureLockDevice(DisableSecureLockDeviceParams) + * @param params @DisableSecureLockDeviceParams for caller to supply params related + * to the secure lock device request + * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the + * secure lock device request + */ + @DisableSecureLockDeviceRequestStatus + public abstract int disableSecureLockDevice(DisableSecureLockDeviceParams params); +} diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 465ac2f1731d..887e1861f789 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -61,6 +61,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -84,6 +85,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AndroidFuture; +import com.android.internal.policy.IDeviceLockedStateListener; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; @@ -105,6 +107,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.stream.IntStream; /** * Manages trust agents and trust listeners. @@ -253,6 +256,10 @@ public class TrustManagerService extends SystemService { new SparseArray<>(); private final SparseArray<TrustableTimeoutAlarmListener> mIdleTrustableTimeoutAlarmListenerForUser = new SparseArray<>(); + + private final RemoteCallbackList<IDeviceLockedStateListener> + mDeviceLockedStateListeners = new RemoteCallbackList<>(); + private AlarmManager mAlarmManager; private final Object mAlarmLock = new Object(); @@ -1090,6 +1097,7 @@ public class TrustManagerService extends SystemService { if (changed) { notifyTrustAgentsOfDeviceLockState(userId, locked); notifyKeystoreOfDeviceLockState(userId, locked); + notifyDeviceLockedListenersForUser(userId, locked); // Also update the user's profiles who have unified challenge, since they // share the same unlocked state (see {@link #isDeviceLocked(int)}) for (int profileHandle : mUserManager.getEnabledProfileIds(userId)) { @@ -1910,6 +1918,26 @@ public class TrustManagerService extends SystemService { return mIsInSignificantPlace; } + @EnforcePermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) + @Override + public void registerDeviceLockedStateListener(IDeviceLockedStateListener listener, + int deviceId) { + super.registerDeviceLockedStateListener_enforcePermission(); + if (deviceId != Context.DEVICE_ID_DEFAULT) { + // Virtual devices are considered insecure. + return; + } + mDeviceLockedStateListeners.register(listener, + Integer.valueOf(UserHandle.getUserId(Binder.getCallingUid()))); + } + + @EnforcePermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) + @Override + public void unregisterDeviceLockedStateListener(IDeviceLockedStateListener listener) { + super.unregisterDeviceLockedStateListener_enforcePermission(); + mDeviceLockedStateListeners.unregister(listener); + } + private void enforceReportPermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events"); @@ -2031,6 +2059,7 @@ public class TrustManagerService extends SystemService { } notifyKeystoreOfDeviceLockState(userId, locked); + notifyDeviceLockedListenersForUser(userId, locked); if (locked) { try { @@ -2497,4 +2526,24 @@ public class TrustManagerService extends SystemService { updateTrust(mUserId, 0 /* flags */); } } + + private void notifyDeviceLockedListenersForUser(int userId, boolean locked) { + int numListeners = mDeviceLockedStateListeners.beginBroadcast(); + try { + IntStream.range(0, numListeners).forEach(i -> { + try { + Integer uid = (Integer) mDeviceLockedStateListeners.getBroadcastCookie(i); + if (userId == uid.intValue()) { + mDeviceLockedStateListeners.getBroadcastItem(i) + .onDeviceLockedStateChanged(locked); + } + } catch (RemoteException re) { + Log.i(TAG, "Service died", re); + } + }); + + } finally { + mDeviceLockedStateListeners.finishBroadcast(); + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c6e6e761c0bc..f70dec175c06 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -630,8 +630,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The locusId associated with this activity, if set. private LocusId mLocusId; - // Whether the activity is requesting to limit the system's educational dialogs - public boolean mShouldLimitSystemEducationDialogs; + // The timestamp of the last request to show the "Open in browser" education + public long mRequestOpenInBrowserEducationTimestamp; // Whether the activity was launched from a bubble. private boolean mLaunchedFromBubble; @@ -1623,6 +1623,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A newParent.setResumedActivity(this, "onParentChanged"); } mAppCompatController.getTransparentPolicy().start(); + if (mState == INITIALIZING && isRestrictedFixedOrientation(info.screenOrientation)) { + Slog.i(TAG, "Ignoring manifest-declared fixed orientation " + + ActivityInfo.screenOrientationToString(info.screenOrientation) + + " of " + this + " since target sdk 36"); + } } if (rootTask != null && rootTask.topRunningActivity() == this) { @@ -3192,6 +3197,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * Returns {@code true} if the orientation will be ignored for {@link #isUniversalResizeable()}. + */ + private boolean isRestrictedFixedOrientation( + @ActivityInfo.ScreenOrientation int orientation) { + // Exclude "locked" because it is not explicit portrait or landscape. + return orientation != ActivityInfo.SCREEN_ORIENTATION_LOCKED + && ActivityInfo.isFixedOrientation(orientation) + && isUniversalResizeable(); + } + + /** * Returns {@code true} if the fixed orientation, aspect ratio, resizability of this activity * will be ignored. */ @@ -7326,9 +7342,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mLocusId; } - void setLimitSystemEducationDialogs(boolean limitSystemEducationDialogs) { - if (mShouldLimitSystemEducationDialogs == limitSystemEducationDialogs) return; - mShouldLimitSystemEducationDialogs = limitSystemEducationDialogs; + void requestOpenInBrowserEducation() { + mRequestOpenInBrowserEducationTimestamp = System.currentTimeMillis(); final Task task = getTask(); if (task != null) { final boolean force = isVisibleRequested() && this == task.getTopNonFinishingActivity(); @@ -8123,7 +8138,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_ORIENTATION, "Setting requested orientation %s for %s", ActivityInfo.screenOrientationToString(requestedOrientation), this); - setOrientation(requestedOrientation, this); + final int resolvedOrientation = setOrientation(requestedOrientation, this); + if (resolvedOrientation != requestedOrientation + && isRestrictedFixedOrientation(requestedOrientation)) { + Slog.i(TAG, "Ignoring requested fixed orientation " + + ActivityInfo.screenOrientationToString(requestedOrientation) + + " of " + this + " since target sdk 36"); + } // Push the new configuration to the requested app in case where it's not pushed, e.g. when // the request is handled at task level with letterbox. @@ -8214,9 +8235,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @ActivityInfo.ScreenOrientation protected int getOverrideOrientation() { int candidateOrientation = super.getOverrideOrientation(); - if (candidateOrientation != ActivityInfo.SCREEN_ORIENTATION_LOCKED - && ActivityInfo.isFixedOrientation(candidateOrientation) - && isUniversalResizeable()) { + if (isRestrictedFixedOrientation(candidateOrientation)) { candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } return mAppCompatController.getOrientationPolicy() diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 198e14a16500..8ff08187c698 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3915,12 +3915,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void setLimitSystemEducationDialogs( - IBinder appToken, boolean limitSystemEducationDialogs) { + public void requestOpenInBrowserEducation(IBinder appToken) { synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(appToken); if (r != null) { - r.setLimitSystemEducationDialogs(limitSystemEducationDialogs); + r.requestOpenInBrowserEducation(); } } } diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java index fbf9478b4fd9..9754595581a0 100644 --- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java @@ -16,10 +16,9 @@ package com.android.server.wm; -import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; -import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA; import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA; @@ -33,7 +32,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.AppCompatUtils.isChangeEnabled; import android.annotation.NonNull; -import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; import com.android.server.wm.utils.OptPropFactory; import com.android.window.flags.Flags; @@ -165,13 +163,13 @@ class AppCompatCameraOverrides { * * <p>The treatment is enabled when the following conditions are met: * <ul> - * <li>Property gating the camera compatibility free-form treatment is enabled. - * <li>Activity isn't opted out by the device manufacturer with override. + * <li>Feature flag gating the camera compatibility free-form treatment is enabled. + * <li>Activity is opted in by the device manufacturer with override. * </ul> */ boolean shouldApplyFreeformTreatmentForCameraCompat() { - return Flags.enableCameraCompatForDesktopWindowing() && !isChangeEnabled(mActivityRecord, - OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT); + return Flags.enableCameraCompatForDesktopWindowing() && isChangeEnabled(mActivityRecord, + OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT); } boolean isOverrideOrientationOnlyForCameraEnabled() { @@ -202,22 +200,10 @@ class AppCompatCameraOverrides { && !mActivityRecord.shouldCreateAppCompatDisplayInsets(); } - @FreeformCameraCompatMode - int getFreeformCameraCompatMode() { - return mAppCompatCameraOverridesState.mFreeformCameraCompatMode; - } - - void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) { - mAppCompatCameraOverridesState.mFreeformCameraCompatMode = freeformCameraCompatMode; - } - static class AppCompatCameraOverridesState { // Whether activity "refresh" was requested but not finished in // ActivityRecord#activityResumedLocked following the camera compat force rotation in // DisplayRotationCompatPolicy. private boolean mIsRefreshRequested; - - @FreeformCameraCompatMode - private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE; } } diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java index 2a0252ab06fd..506477f67bfc 100644 --- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -38,7 +38,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.CameraCompatTaskInfo; -import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.view.DisplayInfo; import android.view.Surface; @@ -46,7 +45,6 @@ import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.internal.protolog.WmProtoLogGroups; -import com.android.window.flags.Flags; /** * Policy for camera compatibility freeform treatment. @@ -124,26 +122,6 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa return appBoundsChanged || displayRotationChanged; } - /** - * Whether activity is eligible for camera compatibility free-form treatment. - * - * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing - * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and - * provides changes to the camera and display orientation signals to match those expected on a - * portrait device in that orientation (for example, on a standard phone). - * - * <p>The treatment is enabled when the following conditions are met: - * <ul> - * <li>Property gating the camera compatibility free-form treatment is enabled. - * <li>Activity isn't opted out by the device manufacturer with override. - * </ul> - */ - @VisibleForTesting - boolean isCameraCompatForFreeformEnabledForActivity(@NonNull ActivityRecord activity) { - return Flags.enableCameraCompatForDesktopWindowing() && !activity.info.isChangeEnabled( - ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT); - } - @Override public void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { @@ -159,16 +137,10 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa } cameraActivity.recomputeConfiguration(); - updateCameraCompatMode(cameraActivity); cameraActivity.getTask().dispatchTaskInfoChangedIfNeeded(/* force= */ true); cameraActivity.ensureActivityConfiguration(/* ignoreVisibility= */ false); } - private void updateCameraCompatMode(@NonNull ActivityRecord cameraActivity) { - cameraActivity.mAppCompatController.getAppCompatCameraOverrides() - .setFreeformCameraCompatMode(getCameraCompatMode(cameraActivity)); - } - @Override public boolean onCameraClosed(@NonNull String cameraId) { // Top activity in the same task as the camera activity, or `null` if the task is @@ -277,7 +249,8 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity, boolean checkOrientation) { int orientation = activity.getRequestedConfigurationOrientation(); - return isCameraCompatForFreeformEnabledForActivity(activity) + return activity.mAppCompatController.getAppCompatCameraOverrides() + .shouldApplyFreeformTreatmentForCameraCompat() && mCameraStateMonitor.isCameraRunningForActivity(activity) && (!checkOrientation || orientation != ORIENTATION_UNDEFINED) && activity.inFreeformWindowingMode() diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 3b2479885f57..f40d636b522a 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -101,8 +101,6 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { DisplayArea(WindowManagerService wms, Type type, String name, int featureId) { super(wms); - // TODO(display-area): move this up to ConfigurationContainer - setOverrideOrientation(SCREEN_ORIENTATION_UNSET); mType = type; mName = name; mFeatureId = featureId; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index f50417d6659e..c89feb41e723 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -108,6 +108,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.DisplayBrightnessOverrideRequest; import android.hardware.power.Mode; import android.net.Uri; import android.os.Binder; @@ -185,8 +186,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> private static final long SLEEP_TRANSITION_WAIT_MILLIS = 1000L; private Object mLastWindowFreezeSource = null; - private float mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; - private CharSequence mScreenBrightnessOverrideTag; + // Per-display WindowManager overrides that are passed on. + private final SparseArray<DisplayBrightnessOverrideRequest> mDisplayBrightnessOverrides = + new SparseArray<>(); private long mUserActivityTimeout = -1; private boolean mUpdateRotation = false; // Only set while traversing the default display based on its content. @@ -775,8 +777,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); } - mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; - mScreenBrightnessOverrideTag = null; + mDisplayBrightnessOverrides.clear(); mUserActivityTimeout = -1; mObscureApplicationContentOnSecondaryDisplays = false; mSustainedPerformanceModeCurrent = false; @@ -879,18 +880,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } if (!mWmService.mDisplayFrozen) { - final float brightnessOverride = mScreenBrightnessOverride < PowerManager.BRIGHTNESS_MIN - || mScreenBrightnessOverride > PowerManager.BRIGHTNESS_MAX - ? PowerManager.BRIGHTNESS_INVALID_FLOAT : mScreenBrightnessOverride; - CharSequence overrideTag = null; - if (brightnessOverride != PowerManager.BRIGHTNESS_INVALID_FLOAT) { - overrideTag = mScreenBrightnessOverrideTag; - } - int brightnessFloatAsIntBits = Float.floatToIntBits(brightnessOverride); // Post these on a handler such that we don't call into power manager service while // holding the window manager lock to avoid lock contention with power manager lock. - mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, brightnessFloatAsIntBits, - 0, overrideTag).sendToTarget(); + mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides) + .sendToTarget(); mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget(); } @@ -1043,10 +1036,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } if (w.isDrawn() || (w.mActivityRecord != null && w.mActivityRecord.firstWindowDrawn && w.mActivityRecord.isVisibleRequested())) { - if (!syswin && w.mAttrs.screenBrightness >= 0 - && Float.isNaN(mScreenBrightnessOverride)) { - mScreenBrightnessOverride = w.mAttrs.screenBrightness; - mScreenBrightnessOverrideTag = w.getWindowTag(); + if (!syswin && w.mAttrs.screenBrightness >= PowerManager.BRIGHTNESS_MIN + && w.mAttrs.screenBrightness <= PowerManager.BRIGHTNESS_MAX + && !mDisplayBrightnessOverrides.contains(w.getDisplayId())) { + var brightnessOverride = new DisplayBrightnessOverrideRequest(); + brightnessOverride.brightness = w.mAttrs.screenBrightness; + brightnessOverride.tag = w.getWindowTag(); + mDisplayBrightnessOverrides.put(w.getDisplayId(), brightnessOverride); } // This function assumes that the contents of the default display are processed first @@ -1118,8 +1114,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> public void handleMessage(Message msg) { switch (msg.what) { case SET_SCREEN_BRIGHTNESS_OVERRIDE: - mWmService.mPowerManagerInternal.setScreenBrightnessOverrideFromWindowManager( - Float.intBitsToFloat(msg.arg1), (CharSequence) msg.obj); + var brightnessOverrides = + (SparseArray<DisplayBrightnessOverrideRequest>) msg.obj; + mWmService.mDisplayManagerInternal.setScreenBrightnessOverrideFromWindowManager( + brightnessOverrides); break; case SET_USER_ACTIVITY_TIMEOUT: mWmService.mPowerManagerInternal. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index dbc3b76c22a1..e8ae28008c89 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3423,8 +3423,8 @@ class Task extends TaskFragment { ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID; info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays; info.mTopActivityLocusId = top != null ? top.getLocusId() : null; - info.isTopActivityLimitSystemEducationDialogs = top != null - ? top.mShouldLimitSystemEducationDialogs : false; + info.topActivityRequestOpenInBrowserEducationTimestamp = top != null + ? top.mRequestOpenInBrowserEducationTimestamp : 0; final Task parentTask = getParent() != null ? getParent().asTask() : null; info.parentTaskId = parentTask != null && parentTask.mCreatedByOrganizer ? parentTask.mTaskId @@ -3523,6 +3523,7 @@ class Task extends TaskFragment { info.capturedLink = null; info.capturedLinkTimestamp = 0; + info.topActivityRequestOpenInBrowserEducationTimestamp = 0; } @Nullable PictureInPictureParams getPictureInPictureParams() { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 143d1b72fff9..8562bb23b30f 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -658,8 +658,8 @@ class TransitionController { } // Always allow WindowState to assign layers since it won't affect transition. return wc.asWindowState() != null || (!isPlaying() - // Don't assign task while collecting. - && !(wc.asTask() != null && isCollecting())); + // Don't assign task or display area layers while collecting. + && !((wc.asTask() != null || wc.asDisplayArea() != null) && isCollecting())); } @WindowConfiguration.WindowingMode diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 5f92bb626154..2397e032fcc8 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1676,30 +1676,36 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}. * @param requestingContainer the container which orientation request has changed. Mostly used * to ensure it gets correct configuration. + * @return the resolved override orientation of this window container. */ - void setOrientation(@ScreenOrientation int orientation, + @ScreenOrientation + int setOrientation(@ScreenOrientation int orientation, @Nullable WindowContainer requestingContainer) { if (getOverrideOrientation() == orientation) { - return; + return orientation; } - setOverrideOrientation(orientation); final WindowContainer parent = getParent(); - if (parent != null) { - if (getConfiguration().orientation != getRequestedConfigurationOrientation() - // Update configuration directly only if the change won't be dispatched from - // ancestor. This prevents from computing intermediate configuration when the - // parent also needs to be updated from the ancestor. E.g. the app requests - // portrait but the task is still in landscape. While updating from display, - // the task can be updated to portrait first so the configuration can be - // computed in a consistent environment. - && (inMultiWindowMode() + if (parent == null) { + return orientation; + } + // The derived class can return a result that is different from the given orientation. + final int resolvedOrientation = getOverrideOrientation(); + if (getConfiguration().orientation != getRequestedConfigurationOrientation( + false /* forDisplay */, resolvedOrientation) + // Update configuration directly only if the change won't be dispatched from + // ancestor. This prevents from computing intermediate configuration when the + // parent also needs to be updated from the ancestor. E.g. the app requests + // portrait but the task is still in landscape. While updating from display, + // the task can be updated to portrait first so the configuration can be + // computed in a consistent environment. + && (inMultiWindowMode() || !handlesOrientationChangeFromDescendant(orientation))) { - // Resolve the requested orientation. - onConfigurationChanged(parent.getConfiguration()); - } - onDescendantOrientationChanged(requestingContainer); + // Resolve the requested orientation. + onConfigurationChanged(parent.getConfiguration()); } + onDescendantOrientationChanged(requestingContainer); + return resolvedOrientation; } @ScreenOrientation diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 81af78e5ce25..6009848f9308 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -182,7 +182,6 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY; import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES; -import static com.android.window.flags.Flags.secureWindowState; import static com.android.window.flags.Flags.surfaceTrustedOverlay; import android.annotation.CallSuper; @@ -1187,9 +1186,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) { getPendingTransaction().setTrustedOverlay(mSurfaceControl, true); } - if (secureWindowState()) { - getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked()); - } + getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked()); // All apps should be considered as occluding when computing TrustedPresentation Thresholds. final boolean canOccludePresentation = !mSession.mCanAddInternalSystemWindow; getPendingTransaction().setCanOccludePresentation(mSurfaceControl, canOccludePresentation); @@ -6174,18 +6171,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void setSecureLocked(boolean isSecure) { ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName()); - if (secureWindowState()) { - if (mSurfaceControl == null) { - return; - } - getPendingTransaction().setSecure(mSurfaceControl, isSecure); - } else { - if (mWinAnimator.mSurfaceControl == null) { - return; - } - getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl, - isSecure); + if (mSurfaceControl == null) { + return; } + getPendingTransaction().setSecure(mSurfaceControl, isSecure); if (mDisplayContent != null) { mDisplayContent.refreshImeSecureFlag(getSyncTransaction()); } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index a934eea690f7..0154d95d888d 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -49,7 +49,6 @@ import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE; import static com.android.server.wm.WindowStateAnimatorProto.SURFACE; import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT; import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN; -import static com.android.window.flags.Flags.secureWindowState; import static com.android.window.flags.Flags.setScPropertiesInClient; import android.content.Context; @@ -310,12 +309,6 @@ class WindowStateAnimator { int flags = SurfaceControl.HIDDEN; final WindowManager.LayoutParams attrs = w.mAttrs; - if (!secureWindowState()) { - if (w.isSecureLocked()) { - flags |= SurfaceControl.SECURE; - } - } - if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) { flags |= SurfaceControl.SKIP_SCREENSHOT; } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index e38337540ad9..dece612c9424 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -2330,6 +2330,12 @@ static void nativeToggleCapsLock(JNIEnv* env, jobject nativeImplObj, jint device im->getInputManager()->getReader().toggleCapsLockState(deviceId); } +static void resetLockedModifierState(JNIEnv* env, jobject nativeImplObj) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + + im->getInputManager()->getReader().resetLockedModifierState(); +} + static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); @@ -3134,6 +3140,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"verifyInputEvent", "(Landroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;", (void*)nativeVerifyInputEvent}, {"toggleCapsLock", "(I)V", (void*)nativeToggleCapsLock}, + {"resetLockedModifierState", "()V", (void*)resetLockedModifierState}, {"displayRemoved", "(I)V", (void*)nativeDisplayRemoved}, {"setFocusedApplication", "(ILandroid/view/InputApplicationHandle;)V", (void*)nativeSetFocusedApplication}, diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index da478f38498b..dc10dd9f47a2 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -251,6 +251,7 @@ import com.android.server.security.KeyAttestationApplicationIdProviderService; import com.android.server.security.KeyChainSystemService; import com.android.server.security.advancedprotection.AdvancedProtectionService; import com.android.server.security.authenticationpolicy.AuthenticationPolicyService; +import com.android.server.security.authenticationpolicy.SecureLockDeviceService; import com.android.server.security.forensic.ForensicService; import com.android.server.security.rkp.RemoteProvisioningService; import com.android.server.selinux.SelinuxAuditLogsService; @@ -2659,6 +2660,12 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(AuthService.class); t.traceEnd(); + if (android.security.Flags.secureLockdown()) { + t.traceBegin("StartSecureLockDeviceService.Lifecycle"); + mSystemServiceManager.startService(SecureLockDeviceService.Lifecycle.class); + t.traceEnd(); + } + if (android.adaptiveauth.Flags.enableAdaptiveAuth()) { t.traceBegin("StartAuthenticationPolicyService"); mSystemServiceManager.startService(AuthenticationPolicyService.class); @@ -3062,7 +3069,10 @@ public final class SystemServer implements Dumpable { if (com.android.ranging.flags.Flags.rangingStackEnabled()) { if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB) || context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_WIFI_RTT)) { + PackageManager.FEATURE_WIFI_RTT) + || (com.android.ranging.flags.Flags.rangingCsEnabled() + && context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING))) { t.traceBegin("RangingService"); // TODO: b/375264320 - Remove after RELEASE_RANGING_STACK is ramped to next. try { diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java index a547d0f94ea3..4e9fff230bac 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import static org.testng.Assert.expectThrows; @@ -96,6 +97,7 @@ public class BackupManagerServiceRoboTest { @UserIdInt private int mUserTwoId; @Mock private UserBackupManagerService mUserSystemService; @Mock private UserBackupManagerService mUserOneService; + @Mock private BackupAgentConnectionManager mUserOneBackupAgentConnectionManager; @Mock private UserBackupManagerService mUserTwoService; /** Setup */ @@ -116,6 +118,9 @@ public class BackupManagerServiceRoboTest { mShadowContext.grantPermissions(BACKUP); mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL); + when(mUserOneService.getBackupAgentConnectionManager()).thenReturn( + mUserOneBackupAgentConnectionManager); + ShadowBinder.setCallingUid(Process.SYSTEM_UID); } @@ -226,7 +231,7 @@ public class BackupManagerServiceRoboTest { backupManagerService.agentConnected(mUserOneId, TEST_PACKAGE, agentBinder); - verify(mUserOneService).agentConnected(TEST_PACKAGE, agentBinder); + verify(mUserOneBackupAgentConnectionManager).agentConnected(TEST_PACKAGE, agentBinder); } /** Test that the backup service does not route methods for non-registered users. */ @@ -239,7 +244,8 @@ public class BackupManagerServiceRoboTest { backupManagerService.agentConnected(mUserTwoId, TEST_PACKAGE, agentBinder); - verify(mUserOneService, never()).agentConnected(TEST_PACKAGE, agentBinder); + verify(mUserOneBackupAgentConnectionManager, never()).agentConnected(TEST_PACKAGE, + agentBinder); } /** Test that the backup service routes methods correctly to the user that requests it. */ diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 7349c14ef62b..aeb1ba93f049 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -107,6 +107,7 @@ import com.android.internal.backup.IBackupTransport; import com.android.internal.infra.AndroidFuture; import com.android.server.EventLogTags; import com.android.server.LocalServices; +import com.android.server.backup.BackupAgentConnectionManager; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.DataChangedJournal; import com.android.server.backup.KeyValueBackupJob; @@ -167,7 +168,6 @@ import java.util.List; import java.util.concurrent.TimeoutException; import java.util.stream.Stream; -// TODO: Test agents timing out @RunWith(RobolectricTestRunner.class) @Config( shadows = { @@ -195,6 +195,7 @@ public class KeyValueBackupTaskTest { @Mock private IBackupManagerMonitor mMonitor; @Mock private OnTaskFinishedListener mListener; @Mock private PackageManagerInternal mPackageManagerInternal; + @Mock private BackupAgentConnectionManager mBackupAgentConnectionManager; private UserBackupManagerService mBackupManagerService; private TransportData mTransport; @@ -257,6 +258,8 @@ public class KeyValueBackupTaskTest { when(mBackupManagerService.getBaseStateDir()).thenReturn(mBaseStateDir); when(mBackupManagerService.getDataDir()).thenReturn(mDataDir); when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager); + when(mBackupManagerService.getBackupAgentConnectionManager()).thenReturn( + mBackupAgentConnectionManager); mBackupHandler = mBackupManagerService.getBackupHandler(); mShadowBackupLooper = shadowOf(mBackupHandler.getLooper()); @@ -749,7 +752,8 @@ public class KeyValueBackupTaskTest { /** * Agent unavailable means {@link - * UserBackupManagerService#bindToAgentSynchronous(ApplicationInfo, int)} returns {@code null}. + * BackupAgentConnectionManager#bindToAgentSynchronous(ApplicationInfo, int, int)} returns + * {@code null}. * * @see #setUpAgent(PackageData) */ @@ -805,7 +809,7 @@ public class KeyValueBackupTaskTest { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgent(PACKAGE_1); doThrow(SecurityException.class) - .when(mBackupManagerService) + .when(mBackupAgentConnectionManager) .bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt(), anyInt()); KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); @@ -823,7 +827,7 @@ public class KeyValueBackupTaskTest { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgent(PACKAGE_1); doThrow(SecurityException.class) - .when(mBackupManagerService) + .when(mBackupAgentConnectionManager) .bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt(), anyInt()); KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1); @@ -861,7 +865,7 @@ public class KeyValueBackupTaskTest { runTask(task); verify(mBackupManagerService).setWorkSource(null); - verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1))); + verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1))); } @Test @@ -1097,7 +1101,7 @@ public class KeyValueBackupTaskTest { runTask(task); verify(agentMock.agentBinder).fail(any()); - verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1))); + verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1))); } @Test @@ -1418,7 +1422,8 @@ public class KeyValueBackupTaskTest { .isEqualTo("newState".getBytes()); assertCleansUpFiles(mTransport, PM_PACKAGE); // We don't unbind PM - verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE))); + verify(mBackupAgentConnectionManager, never()).unbindAgent( + argThat(applicationInfo(PM_PACKAGE))); } @Test @@ -1439,7 +1444,8 @@ public class KeyValueBackupTaskTest { runTask(task); - verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE))); + verify(mBackupAgentConnectionManager, never()).unbindAgent( + argThat(applicationInfo(PM_PACKAGE))); } @Test @@ -1642,9 +1648,10 @@ public class KeyValueBackupTaskTest { runTask(task); - InOrder inOrder = inOrder(agentMock.agent, mBackupManagerService); + InOrder inOrder = inOrder(agentMock.agent, mBackupAgentConnectionManager); inOrder.verify(agentMock.agent).onQuotaExceeded(anyLong(), eq(1234L)); - inOrder.verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1))); + inOrder.verify(mBackupAgentConnectionManager).unbindAgent( + argThat(applicationInfo(PACKAGE_1))); } @Test @@ -2634,12 +2641,12 @@ public class KeyValueBackupTaskTest { doNothing().when(backupAgentBinder).fail(any()); if (packageData.available) { doReturn(backupAgentBinder) - .when(mBackupManagerService) + .when(mBackupAgentConnectionManager) .bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt(), anyInt()); } else { doReturn(null) - .when(mBackupManagerService) + .when(mBackupAgentConnectionManager) .bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt(), anyInt()); } @@ -2976,7 +2983,7 @@ public class KeyValueBackupTaskTest { private void assertCleansUpFilesAndAgent(TransportData transport, PackageData packageData) { assertCleansUpFiles(transport, packageData); - verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(packageData))); + verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(packageData))); } private void assertCleansUpFiles(TransportData transport, PackageData packageData) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index e64d9855bc54..c9963391470e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -121,8 +121,8 @@ public final class DisplayPowerControllerTest { private static final float PROX_SENSOR_MAX_RANGE = 5; private static final float DOZE_SCALE_FACTOR = 0.34f; private static final float DEFAULT_DOZE_BRIGHTNESS = 0.121f; + private static final float OVERRIDE_BRIGHTNESS = 0.567f; - private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f; private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f; private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f; private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f; @@ -2438,6 +2438,36 @@ public final class DisplayPowerControllerTest { any(BrightnessEvent.class)); } + @Test + public void brightnessOverrideInPowerRequest_enablesOverrideBrightnessStrategy() { + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.screenBrightnessOverride = OVERRIDE_BRIGHTNESS; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.animator).animateTo(eq(OVERRIDE_BRIGHTNESS), anyFloat(), anyFloat(), + eq(false)); + } + + @Test + public void brightnessOverrideRequest_enablesOverrideBrightnessStrategy() { + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + var dbor = new DisplayManagerInternal.DisplayBrightnessOverrideRequest(); + dbor.brightness = OVERRIDE_BRIGHTNESS; + mHolder.dpc.setBrightnessOverrideRequest(dbor); + advanceTime(1); // Process the WM brightness override request + + verify(mHolder.animator).animateTo(eq(OVERRIDE_BRIGHTNESS), anyFloat(), anyFloat(), + eq(false)); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index 4f875c38b92f..49de80179683 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -52,6 +52,7 @@ import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStra import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; +import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy; import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy; import com.android.server.display.feature.DisplayManagerFlags; @@ -155,6 +156,27 @@ public final class DisplayBrightnessControllerTest { } @Test + public void updateWindowManagerBrightnessOverride() { + var request = new DisplayManagerInternal.DisplayBrightnessOverrideRequest(); + request.brightness = 0.4f; + request.tag = "cts"; + OverrideBrightnessStrategy overrideBrightnessStrategy = mock( + OverrideBrightnessStrategy.class); + when(mDisplayBrightnessStrategySelector.getOverrideBrightnessStrategy()).thenReturn( + overrideBrightnessStrategy); + + when(overrideBrightnessStrategy.updateWindowManagerBrightnessOverride(any())) + .thenReturn(false); + assertFalse(mDisplayBrightnessController.updateWindowManagerBrightnessOverride(request)); + verify(overrideBrightnessStrategy).updateWindowManagerBrightnessOverride(request); + + when(overrideBrightnessStrategy.updateWindowManagerBrightnessOverride(any())) + .thenReturn(true); + assertTrue(mDisplayBrightnessController.updateWindowManagerBrightnessOverride(request)); + verify(overrideBrightnessStrategy, times(2)).updateWindowManagerBrightnessOverride(request); + } + + @Test public void setCurrentScreenBrightness() { // Current Screen brightness is set as expected when a different value than the current // is set diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index b99a18c8e68c..a6476910a5de 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -183,6 +183,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mContext.getContentResolver()).thenReturn(contentResolver); when(mContext.getResources()).thenReturn(mResources); when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy"); + when(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride()) + .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT); mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, mInjector, DISPLAY_ID, mDisplayManagerFlags); @@ -284,6 +286,20 @@ public final class DisplayBrightnessStrategySelectorTest { } @Test + public void selectStrategySelectsOverrideStrategyWhenWindowManagerOverrideIsValid() { + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride()).thenReturn(0.4f); + assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( + new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED, /* isBedtimeModeWearEnabled= */ false)), + mOverrideBrightnessStrategy); + } + + @Test public void selectStrategySelectsTemporaryStrategyWhenValid() { DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( DisplayManagerInternal.DisplayPowerRequest.class); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java index cc21af19722b..414f274a9b58 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java @@ -16,8 +16,9 @@ package com.android.server.display.brightness.strategy; +import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertThat; import android.hardware.display.DisplayManagerInternal; @@ -34,7 +35,6 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) - public class OverrideBrightnessStrategyTest { private OverrideBrightnessStrategy mOverrideBrightnessStrategy; @@ -62,7 +62,56 @@ public class OverrideBrightnessStrategyTest { new StrategyExecutionRequest(displayPowerRequest, 0.2f, /* userSetBrightnessChanged= */ false, /* isStylusBeingUsed */ false)); - assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); + assertThat(updatedDisplayBrightnessState).isEqualTo(expectedDisplayBrightnessState); } + @Test + public void testUpdateBrightnessWhenWindowManagerOverrideIsRequested() { + var overrideRequest = new DisplayManagerInternal.DisplayBrightnessOverrideRequest(); + overrideRequest.brightness = 0.2f; + mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(overrideRequest); + DisplayManagerInternal.DisplayPowerRequest + displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest(); + displayPowerRequest.screenBrightnessOverride = BRIGHTNESS_INVALID_FLOAT; + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_OVERRIDE); + DisplayBrightnessState expectedDisplayBrightnessState = + new DisplayBrightnessState.Builder() + .setBrightness(overrideRequest.brightness) + .setBrightnessReason(brightnessReason) + .setDisplayBrightnessStrategyName(mOverrideBrightnessStrategy.getName()) + .build(); + DisplayBrightnessState updatedDisplayBrightnessState = + mOverrideBrightnessStrategy.updateBrightness( + new StrategyExecutionRequest(displayPowerRequest, 0.2f, + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); + assertThat(updatedDisplayBrightnessState).isEqualTo(expectedDisplayBrightnessState); + } + + @Test + public void testUpdateWindowManagerBrightnessOverride() { + var request = new DisplayManagerInternal.DisplayBrightnessOverrideRequest(); + assertThat(mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(request)) + .isFalse(); + assertThat(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride()) + .isEqualTo(BRIGHTNESS_INVALID_FLOAT); + + request.brightness = 0.2f; + assertThat(mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(request)) + .isTrue(); + assertThat(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride()) + .isEqualTo(0.2f); + + // Passing the same request doesn't result in an update. + assertThat(mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(request)) + .isFalse(); + assertThat(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride()) + .isEqualTo(0.2f); + + assertThat(mOverrideBrightnessStrategy.updateWindowManagerBrightnessOverride(null)) + .isTrue(); + assertThat(mOverrideBrightnessStrategy.getWindowManagerBrightnessOverride()) + .isEqualTo(BRIGHTNESS_INVALID_FLOAT); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java new file mode 100644 index 000000000000..19e43b6fa851 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java @@ -0,0 +1,381 @@ +/* + * 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.backup; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.ApplicationThreadConstants; +import android.app.IActivityManager; +import android.app.IBackupAgent; +import android.app.backup.BackupAnnotations.BackupDestination; +import android.app.backup.BackupAnnotations.OperationType; +import android.compat.testing.PlatformCompatChangeRule; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Process; +import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.backup.internal.LifecycleOperationStorage; + +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import com.google.common.collect.ImmutableSet; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.util.Set; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class BackupAgentConnectionManagerTest { + private static final String TEST_PACKAGE = "com.test.package"; + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Mock + IActivityManager mActivityManager; + @Mock + ActivityManagerInternal mActivityManagerInternal; + @Mock + LifecycleOperationStorage mOperationStorage; + @Mock + UserBackupManagerService mUserBackupManagerService; + @Mock + IBackupAgent.Stub mBackupAgentStub; + @Mock + PackageManager mPackageManager; + + private BackupAgentConnectionManager mConnectionManager; + private MockitoSession mSession; + private ApplicationInfo mTestApplicationInfo; + private IBackupAgent mBackupAgentResult; + private Thread mTestThread; + + @Before + public void setUp() throws Exception { + mSession = mockitoSession().initMocks(this).mockStatic(ActivityManager.class).mockStatic( + LocalServices.class).strictness(Strictness.LENIENT).startMocking(); + MockitoAnnotations.initMocks(this); + + doReturn(mActivityManager).when(ActivityManager::getService); + doReturn(mActivityManagerInternal).when( + () -> LocalServices.getService(ActivityManagerInternal.class)); + // Real package manager throws if a property is not defined. + when(mPackageManager.getPropertyAsUser(any(), any(), any(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException()); + + mConnectionManager = spy( + new BackupAgentConnectionManager(mOperationStorage, mPackageManager, + mUserBackupManagerService, UserHandle.USER_SYSTEM)); + + mTestApplicationInfo = new ApplicationInfo(); + mTestApplicationInfo.packageName = TEST_PACKAGE; + + mBackupAgentResult = null; + mTestThread = null; + } + + @After + public void tearDown() { + if (mSession != null) { + mSession.finishMocking(); + } + } + + @Test + public void bindToAgentSynchronous_amReturnsFailure_returnsNullAndClearsPendingBackups() + throws Exception { + when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), + anyInt(), anyBoolean())).thenReturn(false); + + IBackupAgent result = mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + assertThat(result).isNull(); + verify(mActivityManagerInternal).clearPendingBackup(UserHandle.USER_SYSTEM); + } + + @Test + public void bindToAgentSynchronous_agentDisconnectedCalled_returnsNullAndClearsPendingBackups() + throws Exception { + when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), + anyInt(), anyBoolean())).thenReturn(true); + // This is so that IBackupAgent.Stub.asInterface() works. + when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub); + when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID); + + // This is going to block until it receives the callback so we need to run it on a + // separate thread. + Thread testThread = new Thread(() -> setBackupAgentResult( + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD)), + "backup-agent-connection-manager-test"); + testThread.start(); + // Give the testThread a head start, otherwise agentConnected() might run before + // bindToAgentSynchronous() is called. + Thread.sleep(500); + mConnectionManager.agentDisconnected(TEST_PACKAGE); + testThread.join(); + + assertThat(mBackupAgentResult).isNull(); + verify(mActivityManagerInternal).clearPendingBackup(UserHandle.USER_SYSTEM); + } + + @Test + public void bindToAgentSynchronous_agentConnectedCalled_returnsBackupAgent() throws Exception { + when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), + anyInt(), anyBoolean())).thenReturn(true); + // This is so that IBackupAgent.Stub.asInterface() works. + when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub); + when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID); + + // This is going to block until it receives the callback so we need to run it on a + // separate thread. + Thread testThread = new Thread(() -> setBackupAgentResult( + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD)), + "backup-agent-connection-manager-test"); + testThread.start(); + // Give the testThread a head start, otherwise agentConnected() might run before + // bindToAgentSynchronous() is called. + Thread.sleep(500); + mConnectionManager.agentConnected(TEST_PACKAGE, mBackupAgentStub); + testThread.join(); + + assertThat(mBackupAgentResult).isEqualTo(mBackupAgentStub); + verify(mActivityManagerInternal, never()).clearPendingBackup(anyInt()); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode() + throws Exception { + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + // Make sure we never hit the code that checks the property. + verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode() + throws Exception { + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + // Make sure we never hit the code that checks the property. + verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode() + throws Exception { + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + // Make sure we never hit the code that checks the property. + verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode() throws Exception { + reset(mPackageManager); + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(), + anyInt())).thenReturn(new PackageManager.Property( + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true, + TEST_PACKAGE, /* className= */ null)); + + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode() + throws Exception { + reset(mPackageManager); + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(), + anyInt())).thenReturn(new PackageManager.Property( + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false, + TEST_PACKAGE, /* className= */ null)); + + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @DisableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode() throws Exception { + reset(mPackageManager); + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(), + anyInt())).thenThrow(new PackageManager.NameNotFoundException()); + + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @EnableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode() + throws Exception { + reset(mPackageManager); + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(), + anyInt())).thenThrow(new PackageManager.NameNotFoundException()); + mConnectionManager.clearNoRestrictedModePackages(); + + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @EnableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode() + throws Exception { + reset(mPackageManager); + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(), + anyInt())).thenThrow(new PackageManager.NameNotFoundException()); + mConnectionManager.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE); + + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @EnableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode() + throws Exception { + reset(mPackageManager); + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(), + anyInt())).thenThrow(new PackageManager.NameNotFoundException()); + mConnectionManager.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP); + + mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + } + + @Test + public void agentDisconnected_cancelsCurrentOperations() throws Exception { + when(mOperationStorage.operationTokensForPackage(eq(TEST_PACKAGE))).thenReturn( + ImmutableSet.of(123, 456, 789)); + when(mConnectionManager.getThreadForCancellation(any())).thenAnswer(invocation -> { + Thread testThread = new Thread((Runnable) invocation.getArgument(0), + "agent-disconnected-test"); + setTestThread(testThread); + return testThread; + }); + + mConnectionManager.agentDisconnected(TEST_PACKAGE); + + mTestThread.join(); + verify(mUserBackupManagerService).handleCancel(eq(123), eq(true)); + verify(mUserBackupManagerService).handleCancel(eq(456), eq(true)); + verify(mUserBackupManagerService).handleCancel(eq(789), eq(true)); + } + + @Test + public void unbindAgent_callsAmUnbindBackupAgent() throws Exception { + mConnectionManager.unbindAgent(mTestApplicationInfo); + + verify(mActivityManager).unbindBackupAgent(eq(mTestApplicationInfo)); + } + + // Needed because variables can't be assigned directly inside lambdas in Java. + private void setBackupAgentResult(IBackupAgent result) { + mBackupAgentResult = result; + } + + // Needed because variables can't be assigned directly inside lambdas in Java. + private void setTestThread(Thread thread) { + mTestThread = thread; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java index 07f2188d30eb..7a9e96f1bc4c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -18,20 +18,18 @@ package com.android.server.backup; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; + import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; -import android.app.ApplicationThreadConstants; import android.app.IActivityManager; import android.app.backup.BackupAgent; import android.app.backup.BackupAnnotations.BackupDestination; @@ -47,8 +45,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Handler; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -57,7 +53,6 @@ import android.util.FeatureFlagUtils; import android.util.KeyValueListParser; import androidx.test.core.app.ApplicationProvider; -import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.backup.internal.BackupHandler; @@ -69,8 +64,6 @@ import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; import com.android.server.backup.utils.BackupManagerMonitorEventSender; -import com.google.common.collect.ImmutableSet; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -84,11 +77,6 @@ import org.mockito.quality.Strictness; import java.util.Arrays; import java.util.List; -import java.util.Set; -import java.util.function.IntConsumer; - -import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; -import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; @Presubmit @RunWith(AndroidJUnit4.class) @@ -96,7 +84,6 @@ public class UserBackupManagerServiceTest { private static final String TEST_PACKAGE = "package1"; private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE }; private static final String TEST_TRANSPORT = "transport"; - private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100; @UserIdInt private static final int USER_ID = 0; @Rule @@ -278,33 +265,6 @@ public class UserBackupManagerServiceTest { } @Test - @FlakyTest - public void testAgentDisconnected_cancelsCurrentOperations() throws Exception { - when(mOperationStorage.operationTokensForPackage(eq("com.android.foo"))).thenReturn( - ImmutableSet.of(123, 456, 789) - ); - - mService.agentDisconnected("com.android.foo"); - - mService.waitForAsyncOperation(); - verify(mOperationStorage).cancelOperation(eq(123), eq(true), any(IntConsumer.class)); - verify(mOperationStorage).cancelOperation(eq(456), eq(true), any()); - verify(mOperationStorage).cancelOperation(eq(789), eq(true), any()); - } - - @Test - public void testAgentDisconnected_unknownPackageName_cancelsNothing() throws Exception { - when(mOperationStorage.operationTokensForPackage(eq("com.android.foo"))).thenReturn( - ImmutableSet.of() - ); - - mService.agentDisconnected("com.android.foo"); - - verify(mOperationStorage, never()) - .cancelOperation(anyInt(), anyBoolean(), any(IntConsumer.class)); - } - - @Test public void testReportDelayedRestoreResult_sendsLogsToMonitor() throws Exception { PackageInfo packageInfo = getPackageInfo(TEST_PACKAGE); when(mPackageManager.getPackageInfoAsUser(anyString(), @@ -324,158 +284,6 @@ public class UserBackupManagerServiceTest { eq(packageInfo), eq(results), eq(OperationType.RESTORE)); } - @Test - @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) - public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode() - throws Exception { - mService.bindToAgentSynchronous(mTestPackageApplicationInfo, - ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); - - verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), - /* useRestrictedMode= */ eq(true)); - // Make sure we never hit the code that checks the property. - verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) - public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode() - throws Exception { - mService.bindToAgentSynchronous(mTestPackageApplicationInfo, - ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD); - - verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), - /* useRestrictedMode= */ eq(false)); - // Make sure we never hit the code that checks the property. - verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) - public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode() - throws Exception { - mService.bindToAgentSynchronous(mTestPackageApplicationInfo, - ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD); - - verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), - /* useRestrictedMode= */ eq(false)); - // Make sure we never hit the code that checks the property. - verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) - public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode() - throws Exception { - when(mPackageManager.getPropertyAsUser( - eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), - eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property( - PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true, - TEST_PACKAGE, /* className= */ null)); - - mService.bindToAgentSynchronous(mTestPackageApplicationInfo, - ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); - - verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), - /* useRestrictedMode= */ eq(true)); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) - public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode() - throws Exception { - when(mPackageManager.getPropertyAsUser( - eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), - eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property( - PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false, - TEST_PACKAGE, /* className= */ null)); - - mService.bindToAgentSynchronous(mTestPackageApplicationInfo, - ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); - - verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), - /* useRestrictedMode= */ eq(false)); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) - @DisableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) - public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode() - throws Exception { - // Mock that the app has not explicitly set the property. - when(mPackageManager.getPropertyAsUser( - eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), - eq(TEST_PACKAGE), any(), anyInt())).thenThrow( - new PackageManager.NameNotFoundException() - ); - - mService.bindToAgentSynchronous(mTestPackageApplicationInfo, - ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); - - verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), - /* useRestrictedMode= */ eq(true)); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) - @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) - public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode() - throws Exception { - // Mock that the app has not explicitly set the property. - when(mPackageManager.getPropertyAsUser( - eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), - eq(TEST_PACKAGE), any(), anyInt())).thenThrow( - new PackageManager.NameNotFoundException() - ); - mService.clearNoRestrictedModePackages(); - - mService.bindToAgentSynchronous(mTestPackageApplicationInfo, - ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); - - verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), - /* useRestrictedMode= */ eq(true)); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) - @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) - public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode() - throws Exception { - // Mock that the app has not explicitly set the property. - when(mPackageManager.getPropertyAsUser( - eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), - eq(TEST_PACKAGE), any(), anyInt())).thenThrow( - new PackageManager.NameNotFoundException() - ); - mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE); - - mService.bindToAgentSynchronous(mTestPackageApplicationInfo, - ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD); - - verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), - /* useRestrictedMode= */ eq(false)); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) - @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) - public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode() - throws Exception { - // Mock that the app has not explicitly set the property. - when(mPackageManager.getPropertyAsUser( - eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), - eq(TEST_PACKAGE), any(), anyInt())).thenThrow( - new PackageManager.NameNotFoundException() - ); - mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP); - - mService.bindToAgentSynchronous(mTestPackageApplicationInfo, - ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); - - verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), - /* useRestrictedMode= */ eq(false)); - } - private static PackageInfo getPackageInfo(String packageName) { PackageInfo packageInfo = new PackageInfo(); packageInfo.applicationInfo = new ApplicationInfo(); @@ -487,8 +295,6 @@ public class UserBackupManagerServiceTest { boolean isEnabledStatePersisted = false; boolean shouldUseNewBackupEligibilityRules = false; - private volatile Thread mWorkerThread = null; - TestBackupService() { super(mContext, mPackageManager, mOperationStorage, mTransportManager, mBackupHandler, createConstants(mContext), mActivityManager, mActivityManagerInternal); @@ -523,26 +329,8 @@ public class UserBackupManagerServiceTest { } @Override - Thread getThreadForAsyncOperation(String operationName, Runnable operation) { - mWorkerThread = super.getThreadForAsyncOperation(operationName, operation); - return mWorkerThread; - } - - @Override BackupManagerMonitorEventSender getBMMEventSender(IBackupManagerMonitor monitor) { return mBackupManagerMonitorEventSender; } - - private void waitForAsyncOperation() { - if (mWorkerThread == null) { - return; - } - - try { - mWorkerThread.join(/* millis */ WORKER_THREAD_TIMEOUT_MILLISECONDS); - } catch (InterruptedException e) { - fail("Failed waiting for worker thread to complete: " + e.getMessage()); - } - } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java index 331057398949..e618433862f2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java @@ -33,6 +33,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; +import com.android.server.backup.BackupAgentConnectionManager; import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.OperationStorage; import com.android.server.backup.TransportManager; @@ -66,6 +67,8 @@ public class PerformFullTransportBackupTaskTest { @Mock UserBackupManagerService mBackupManagerService; @Mock + BackupAgentConnectionManager mBackupAgentConnectionManager; + @Mock BackupTransportClient mBackupTransportClient; @Mock CountDownLatch mLatch; @@ -95,6 +98,8 @@ public class PerformFullTransportBackupTaskTest { when(mBackupManagerService.isSetupComplete()).thenReturn(true); when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn( mBackupAgentTimeoutParameters); + when(mBackupManagerService.getBackupAgentConnectionManager()).thenReturn( + mBackupAgentConnectionManager); when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager); when(mTransportManager.getCurrentTransportClient(any())).thenReturn(mTransportConnection); when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransportClient); @@ -142,11 +147,11 @@ public class PerformFullTransportBackupTaskTest { mTask.run(); - InOrder inOrder = inOrder(mBackupManagerService); - inOrder.verify(mBackupManagerService).setNoRestrictedModePackages( + InOrder inOrder = inOrder(mBackupAgentConnectionManager); + inOrder.verify(mBackupAgentConnectionManager).setNoRestrictedModePackages( eq(Set.of("package1")), eq(BackupAnnotations.OperationType.BACKUP)); - inOrder.verify(mBackupManagerService).clearNoRestrictedModePackages(); + inOrder.verify(mBackupAgentConnectionManager).clearNoRestrictedModePackages(); } private void createTask(String[] packageNames) { diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java index 055adf68ee0f..351aac357c44 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java @@ -44,6 +44,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.modules.utils.testing.TestableDeviceConfig; +import com.android.server.backup.BackupAgentConnectionManager; import com.android.server.backup.Flags; import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.internal.BackupHandler; @@ -95,6 +96,8 @@ public class PerformUnifiedRestoreTaskTest { private TransportConnection mTransportConnection; @Mock private BackupTransportClient mBackupTransportClient; + @Mock + private BackupAgentConnectionManager mBackupAgentConnectionManager; private Set<String> mExcludedkeys = new HashSet<>(); private Map<String, String> mBackupData = new HashMap<>(); @@ -122,6 +125,9 @@ public class PerformUnifiedRestoreTaskTest { mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + when(mBackupManagerService.getBackupAgentConnectionManager()).thenReturn( + mBackupAgentConnectionManager); + mBackupDataSource = new ArrayDeque<>(mBackupData.keySet()); when(mBackupDataInput.readNextHeader()) .then((Answer<Boolean>) invocation -> !mBackupDataSource.isEmpty()); @@ -166,7 +172,7 @@ public class PerformUnifiedRestoreTaskTest { mRestoreTask.setNoRestrictedModePackages(mBackupTransportClient, new PackageInfo[]{packageInfo1, packageInfo2}); - verify(mBackupManagerService).setNoRestrictedModePackages( + verify(mBackupAgentConnectionManager).setNoRestrictedModePackages( eq(Set.of("package1")), eq(BackupAnnotations.OperationType.RESTORE)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index d66bb00ae879..c6870adb8464 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -65,6 +65,7 @@ import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; +import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -103,10 +104,13 @@ import com.android.server.job.controllers.QuotaController.TimedEvent; import com.android.server.job.controllers.QuotaController.TimingSession; import com.android.server.usage.AppStandbyInternal; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; @@ -135,6 +139,9 @@ public class QuotaControllerTest { private static final int SOURCE_USER_ID = 0; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); private QuotaController mQuotaController; private QuotaController.QcConstants mQcConstants; private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); @@ -303,7 +310,7 @@ public class QuotaControllerTest { private int getProcessStateQuotaFreeThreshold() { synchronized (mQuotaController.mLock) { - return mQuotaController.getProcessStateQuotaFreeThreshold(); + return mQuotaController.getProcessStateQuotaFreeThreshold(mSourceUid); } } @@ -5197,6 +5204,101 @@ public class QuotaControllerTest { assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); } + @Test + @EnableCompatChanges({QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, + QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS}) + @RequiresFlagsEnabled({Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS, + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS}) + public void testTracking_OutOfQuota_ForegroundAndBackground_CompactChangeOverrides() { + setDischarging(); + + JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1); + JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2); + trackJobs(jobBg, jobTop); + setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window + // Now the package only has 20 seconds to run. + final long remainingTimeMs = 20 * SECOND_IN_MILLIS; + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession( + JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS, + 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false); + + InOrder inOrder = inOrder(mJobSchedulerService); + + // UID starts out inactive. + setProcessState(ActivityManager.PROCESS_STATE_SERVICE); + // Start the job. + synchronized (mQuotaController.mLock) { + mQuotaController.prepareForExecutionLocked(jobBg); + } + advanceElapsedClock(remainingTimeMs / 2); + // New job starts after UID is in the foreground. Since the app is now in the foreground, it + // should continue to have remainingTimeMs / 2 time remaining. + setProcessState(ActivityManager.PROCESS_STATE_TOP); + synchronized (mQuotaController.mLock) { + mQuotaController.prepareForExecutionLocked(jobTop); + } + advanceElapsedClock(remainingTimeMs); + + // Wait for some extra time to allow for job processing. + inOrder.verify(mJobSchedulerService, + timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) + .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); + synchronized (mQuotaController.mLock) { + assertEquals(remainingTimeMs / 2, + mQuotaController.getRemainingExecutionTimeLocked(jobBg)); + assertEquals(remainingTimeMs / 2, + mQuotaController.getRemainingExecutionTimeLocked(jobTop)); + } + // Go to a background state. + setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); + advanceElapsedClock(remainingTimeMs / 2 + 1); + // Only Bg job will be changed from in-quota to out-of-quota. + inOrder.verify(mJobSchedulerService, + timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) + .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); + // Top job should still be allowed to run. + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + + // New jobs to run. + JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3); + JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4); + JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5); + setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg); + + advanceElapsedClock(20 * SECOND_IN_MILLIS); + setProcessState(ActivityManager.PROCESS_STATE_TOP); + inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) + .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); + trackJobs(jobFg, jobTop); + synchronized (mQuotaController.mLock) { + mQuotaController.prepareForExecutionLocked(jobTop); + } + assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + + // App still in foreground so everything should be in quota. + advanceElapsedClock(20 * SECOND_IN_MILLIS); + setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + + advanceElapsedClock(20 * SECOND_IN_MILLIS); + setProcessState(ActivityManager.PROCESS_STATE_SERVICE); + inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) + .onControllerStateChanged(argThat(jobs -> jobs.size() == 2)); + // App is now in background and out of quota. Fg should now change to out of quota since it + // wasn't started. Top should remain in quota since it started when the app was in TOP. + assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + trackJobs(jobBg2); + assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + } + /** * Tests that TOP jobs are stopped when an app runs out of quota. */ diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java index 0988eeab5913..a55346caeeb1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java @@ -430,7 +430,7 @@ public class FlashNotificationsControllerTest { AudioPlaybackConfiguration config = new AudioPlaybackConfiguration( mock(PlayerBase.PlayerIdCard.class), 0, 0, 0); config.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_STARTED, - AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID); + AudioPlaybackConfiguration.PLAYER_DEVICEIDS_INVALID); AudioAttributes.Builder builder = new AudioAttributes.Builder(); builder.setUsage(AudioAttributes.USAGE_ALARM); diff --git a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java index 84c0ab38ca48..0d44021bae09 100644 --- a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java @@ -314,12 +314,13 @@ public class LoudnessCodecHelperTest { AudioDeviceInfo[] devicesStatic = AudioManager.getDevicesStatic(GET_DEVICES_OUTPUTS); assumeTrue(devIdx < devicesStatic.length); Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + devIdx); - int deviceId = devicesStatic[devIdx].getId(); + int[] deviceIds = new int[1]; + deviceIds[0] = devicesStatic[devIdx].getId(); PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class); AudioPlaybackConfiguration apc = new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/myPid()); - apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId); + apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceIds); apc.handleSessionIdEvent(sessionId); apc.handleAudioAttributesEvent(new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java index 51c2ad1d1134..687a1ab4c7aa 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java @@ -200,7 +200,9 @@ public class VirtualAudioControllerTest { AudioPlaybackConfiguration audioPlaybackConfiguration = new AudioPlaybackConfiguration( playerIdCard, /* piid= */ 1000, appUid, /* pid= */ 1000); - audioPlaybackConfiguration.handleStateEvent(PLAYER_STATE_STARTED, /* deviceId= */1); + int[] deviceIds = new int[1]; + deviceIds[0] = 1; + audioPlaybackConfiguration.handleStateEvent(PLAYER_STATE_STARTED, deviceIds); configs.add(audioPlaybackConfiguration); } return configs; diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 510c2bcabad0..b2a7d20fb948 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -664,7 +664,7 @@ public class MediaProjectionManagerServiceTest { mClockInjector); MediaProjectionManagerService.MediaProjection projection = createProjectionPreconditions( service); - mClock.fastForward(projection.mDefaultTimeoutMs + 10); + mClock.fastForward(projection.mDefaultTimeoutMillis + 10); // Immediate timeout - so no longer valid. assertThat(projection.isValid()).isFalse(); diff --git a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java index 2238a1be97a1..ee8eb9b35088 100644 --- a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java @@ -19,12 +19,14 @@ package com.android.server.security.authenticationpolicy; import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH; import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS; import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; +import static android.security.authenticationpolicy.AuthenticationPolicyManager.ERROR_UNSUPPORTED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; import static com.android.server.security.authenticationpolicy.AuthenticationPolicyService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -95,6 +97,8 @@ public class AuthenticationPolicyServiceTest { private WindowManagerInternal mWindowManager; @Mock private UserManagerInternal mUserManager; + @Mock + private SecureLockDeviceServiceInternal mSecureLockDeviceService; @Captor ArgumentCaptor<LockSettingsStateListener> mLockSettingsStateListenerCaptor; @@ -123,6 +127,11 @@ public class AuthenticationPolicyServiceTest { LocalServices.addService(WindowManagerInternal.class, mWindowManager); LocalServices.removeServiceForTest(UserManagerInternal.class); LocalServices.addService(UserManagerInternal.class, mUserManager); + if (android.security.Flags.secureLockdown()) { + LocalServices.removeServiceForTest(SecureLockDeviceServiceInternal.class); + LocalServices.addService(SecureLockDeviceServiceInternal.class, + mSecureLockDeviceService); + } mAuthenticationPolicyService = new AuthenticationPolicyService( mContext, mLockPatternUtils); @@ -136,6 +145,12 @@ public class AuthenticationPolicyServiceTest { // Set PRIMARY_USER_ID as the parent of MANAGED_PROFILE_USER_ID when(mUserManager.getProfileParentId(eq(MANAGED_PROFILE_USER_ID))) .thenReturn(PRIMARY_USER_ID); + if (android.security.Flags.secureLockdown()) { + when(mSecureLockDeviceService.enableSecureLockDevice(any())) + .thenReturn(ERROR_UNSUPPORTED); + when(mSecureLockDeviceService.disableSecureLockDevice(any())) + .thenReturn(ERROR_UNSUPPORTED); + } } @After @@ -143,6 +158,9 @@ public class AuthenticationPolicyServiceTest { LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.removeServiceForTest(UserManagerInternal.class); + if (android.security.Flags.secureLockdown()) { + LocalServices.removeServiceForTest(SecureLockDeviceServiceInternal.class); + } } @Test diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 6e6b70d319ab..5f2f3ed67432 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -17,10 +17,10 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworks.wmtests"> - <!-- Uses API introduced in P (28) --> + <!-- Uses API introduced in S (31). Using SDK 31+ avoids Google Play Protect popups. --> <uses-sdk android:minSdkVersion="1" - android:targetSdkVersion="28" /> + android:targetSdkVersion="31" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java index b91a5b7afe26..d5ed048032e7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java @@ -17,8 +17,8 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; -import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA; import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA; @@ -228,9 +228,8 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { } @Test - @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT}) @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) - public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() { + public void testShouldApplyCameraCompatFreeformTreatment_notEnabledByOverride_returnsFalse() { runTestScenario((robot) -> { robot.activity().createActivityWithComponentInNewTask(); @@ -239,19 +238,9 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { } @Test - @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT}) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) - public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse() { - runTestScenario((robot) -> { - robot.activity().createActivityWithComponentInNewTask(); - - robot.checkShouldApplyFreeformTreatmentForCameraCompat(false); - }); - } - - @Test - @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) - public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() { + public void testShouldApplyCameraCompatFreeformTreatment_overrideAndFlagEnabled_returnsTrue() { runTestScenario((robot) -> { robot.activity().createActivityWithComponentInNewTask(); @@ -261,8 +250,9 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { @Test @EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA, - OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void testShouldRecomputeConfigurationForCameraCompat() { + OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA, + OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) + public void testShouldRecomputeConfigurationForFreeformTreatment() { runTestScenario((robot) -> { robot.conf().enableCameraCompatSplitScreenAspectRatio(true); robot.applyOnActivity((a) -> { diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java index e447565a55bd..c427583d3001 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java @@ -25,7 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; -import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; @@ -96,61 +96,55 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests"; private static final String TEST_PACKAGE_2 = "com.test.package.two"; private static final String CAMERA_ID_1 = "camera-1"; - private static final String CAMERA_ID_2 = "camera-2"; - private CameraManager mMockCameraManager; - private Handler mMockHandler; private AppCompatConfiguration mAppCompatConfiguration; private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; private CameraCompatFreeformPolicy mCameraCompatFreeformPolicy; private ActivityRecord mActivity; - private Task mTask; private ActivityRefresher mActivityRefresher; @Before public void setUp() throws Exception { mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration; spyOn(mAppCompatConfiguration); - when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) - .thenReturn(true); - when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()) - .thenReturn(true); + when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()).thenReturn(true); + when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()).thenReturn(true); when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(true); - mMockCameraManager = mock(CameraManager.class); + final CameraManager mockCameraManager = mock(CameraManager.class); doAnswer(invocation -> { mCameraAvailabilityCallback = invocation.getArgument(1); return null; - }).when(mMockCameraManager).registerAvailabilityCallback( + }).when(mockCameraManager).registerAvailabilityCallback( any(Executor.class), any(CameraManager.AvailabilityCallback.class)); - when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager); + when(mContext.getSystemService(CameraManager.class)).thenReturn(mockCameraManager); mDisplayContent.setIgnoreOrientationRequest(true); - mMockHandler = mock(Handler.class); + final Handler mockHandler = mock(Handler.class); - when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( + when(mockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; }); - mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler); - CameraStateMonitor cameraStateMonitor = - new CameraStateMonitor(mDisplayContent, mMockHandler); - mCameraCompatFreeformPolicy = - new CameraCompatFreeformPolicy(mDisplayContent, cameraStateMonitor, - mActivityRefresher); + mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mockHandler); + final CameraStateMonitor cameraStateMonitor = new CameraStateMonitor(mDisplayContent, + mockHandler); + mCameraCompatFreeformPolicy = new CameraCompatFreeformPolicy(mDisplayContent, + cameraStateMonitor, mActivityRefresher); setDisplayRotation(Surface.ROTATION_90); mCameraCompatFreeformPolicy.start(); cameraStateMonitor.startListeningToCameraState(); } - @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testFullscreen_doesNotActivateCameraCompatMode() { configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); doReturn(false).when(mActivity).inFreeformWindowingMode(); @@ -160,23 +154,26 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { assertNotInCameraCompatMode(); } - @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testOrientationUnspecified_doesNotActivateCameraCompatMode() { configureActivity(SCREEN_ORIENTATION_UNSPECIFIED); assertNotInCameraCompatMode(); } - @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testNoCameraConnection_doesNotActivateCameraCompatMode() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); assertNotInCameraCompatMode(); } - @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); setDisplayRotation(Surface.ROTATION_0); @@ -187,6 +184,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); setDisplayRotation(Surface.ROTATION_270); @@ -197,6 +196,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception { configureActivity(SCREEN_ORIENTATION_LANDSCAPE); setDisplayRotation(Surface.ROTATION_0); @@ -207,6 +208,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception { configureActivity(SCREEN_ORIENTATION_LANDSCAPE); setDisplayRotation(Surface.ROTATION_270); @@ -216,8 +219,9 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { assertActivityRefreshRequested(/* refreshRequested */ false); } - @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); setDisplayRotation(Surface.ROTATION_270); @@ -236,6 +240,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -246,27 +252,32 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) - @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() { + public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mActivity.info - .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT)); - assertFalse(mCameraCompatFreeformPolicy.isCameraCompatForFreeformEnabledForActivity( - mActivity)); + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + assertFalse(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity, + /* checkOrientation */ true)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) - public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() { + @EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT) + public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mCameraCompatFreeformPolicy.isCameraCompatForFreeformEnabledForActivity( - mActivity)); + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + assertTrue(mActivity.info + .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)); + assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity, + /* checkOrientation */ true)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldRefreshActivity_appBoundsChanged_returnsTrue() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Configuration oldConfiguration = createConfiguration(/* letterbox= */ false); @@ -279,6 +290,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldRefreshActivity_displayRotationChanged_returnsTrue() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); @@ -294,6 +306,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldRefreshActivity_appBoundsNorDisplayChanged_returnsFalse() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); @@ -309,6 +322,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -324,6 +338,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception { when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(false); @@ -338,6 +353,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -352,6 +368,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() { configureActivity(SCREEN_ORIENTATION_FULL_USER); @@ -365,6 +382,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); final float configAspectRatio = 1.5f; @@ -380,6 +398,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); final float configAspectRatio = 1.5f; @@ -406,7 +425,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation, @Orientation int naturalOrientation, @WindowingMode int windowingMode) { - mTask = new TaskBuilder(mSupervisor) + final Task task = new TaskBuilder(mSupervisor) .setDisplay(mDisplayContent) .setWindowingMode(windowingMode) .build(); @@ -416,7 +435,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { .setComponent(ComponentName.createRelative(mContext, com.android.server.wm.CameraCompatFreeformPolicyTests.class.getName())) .setScreenOrientation(activityOrientation) - .setTask(mTask) + .setTask(task) .build(); spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides()); @@ -429,13 +448,11 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) { - assertEquals(mode, mActivity.mAppCompatController.getAppCompatCameraOverrides() - .getFreeformCameraCompatMode()); + assertEquals(mode, mCameraCompatFreeformPolicy.getCameraCompatMode(mActivity)); } private void assertNotInCameraCompatMode() { - assertEquals(CAMERA_COMPAT_FREEFORM_NONE, mActivity.mAppCompatController - .getAppCompatCameraOverrides().getFreeformCameraCompatMode()); + assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE); } private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception { @@ -471,7 +488,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { private Configuration createConfiguration(boolean letterbox) { final Configuration configuration = new Configuration(); - Rect bounds = letterbox ? new Rect(300, 0, 700, 600) : new Rect(0, 0, 1000, 600); + Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/ 600) + : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600); configuration.windowConfiguration.setAppBounds(bounds); return configuration; } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 6490cbe3e31a..7cfdec664a92 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9734,6 +9734,35 @@ public class CarrierConfigManager { "carrier_supported_satellite_services_per_provider_bundle"; /** + * A PersistableBundle that contains a list of key-value pairs, where the values are integer + * arrays. + * <p> + * Keys are the IDs of regional satellite configs as strings and values are + * integer arrays of earfcns in the corresponding regions. + * + * An example config for two regions "1" and "2": + * <pre>{@code + * <carrier_config> + * <pbundle_as_map name="regional_satellite_earfcn_bundle"> + * <int-array name = "1" num = "2"> + * <item value = "100"/> + * <item value = "200"/> + * </int-array> + * <int-array name = "2" num = "1"> + * <item value = "200"/> + * </int-array> + * </pbundle_as_map> + * </carrier_config> + * }</pre> + * <p> + * This config is empty by default. + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public static final String KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE = + "regional_satellite_earfcn_bundle"; + + /** * This config enables modem to scan satellite PLMNs specified as per * {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE} and attach to same * in case cellular networks are not enabled. This will need specific agreement between @@ -11264,6 +11293,9 @@ public class CarrierConfigManager { sDefaults.putPersistableBundle( KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, PersistableBundle.EMPTY); + sDefaults.putPersistableBundle( + KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE, + PersistableBundle.EMPTY); sDefaults.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false); sDefaults.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 180); sDefaults.putIntArray(KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY, diff --git a/telephony/java/android/telephony/CellularIdentifierDisclosure.aidl b/telephony/java/android/telephony/CellularIdentifierDisclosure.aidl new file mode 100644 index 000000000000..1e41d6e2cc31 --- /dev/null +++ b/telephony/java/android/telephony/CellularIdentifierDisclosure.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @hide */ +package android.telephony; + +parcelable CellularIdentifierDisclosure; diff --git a/telephony/java/android/telephony/CellularIdentifierDisclosure.java b/telephony/java/android/telephony/CellularIdentifierDisclosure.java index 7b2db6d59819..0b6a70feac9d 100644 --- a/telephony/java/android/telephony/CellularIdentifierDisclosure.java +++ b/telephony/java/android/telephony/CellularIdentifierDisclosure.java @@ -16,11 +16,16 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -31,16 +36,88 @@ import java.util.Objects; * * @hide */ +@SystemApi +@FlaggedApi(Flags.FLAG_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS) public final class CellularIdentifierDisclosure implements Parcelable { private static final String TAG = "CellularIdentifierDisclosure"; + /* Non-access stratum protocol messages */ + /** Unknown */ + public static final int NAS_PROTOCOL_MESSAGE_UNKNOWN = 0; + /** ATTACH REQUESTS. Sample reference: TS 24.301 8.2.4 Applies to 2g, 3g, and 4g networks */ + public static final int NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST = 1; + /** IDENTITY RESPONSE. Sample Reference: TS 24.301 8.2.19. + * Applies to 2g, 3g, 4g, and 5g networks */ + public static final int NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE = 2; + /** DETACH_REQUEST. Sample Reference: TS 24.301 8.2.11. Applies to 2g, 3g, and 4g networks */ + public static final int NAS_PROTOCOL_MESSAGE_DETACH_REQUEST = 3; + /** TRACKING AREA UPDATE (TAU) REQUEST. Sample Reference: 3GPP TS 24.301 8.2.29. + * Note: that per the spec, only temporary IDs should be sent in the TAU Request, but since the + * EPS Mobile Identity field supports IMSIs, this is included as an extra safety measure to + * combat implementation bugs. Applies to 4g and 5g networks. */ + public static final int NAS_PROTOCOL_MESSAGE_TRACKING_AREA_UPDATE_REQUEST = 4; + /** LOCATION UPDATE REQUEST. Sample Reference: TS 24.008 4.4.3. Applies to 2g and 3g networks */ + public static final int NAS_PROTOCOL_MESSAGE_LOCATION_UPDATE_REQUEST = 5; + /** AUTHENTICATION AND CIPHERING RESPONSE. Reference: 3GPP TS 24.008 4.7.7.1. + * Applies to 2g and 3g networks */ + public static final int NAS_PROTOCOL_MESSAGE_AUTHENTICATION_AND_CIPHERING_RESPONSE = 6; + /** REGISTRATION REQUEST. Reference: 3GPP TS 24.501 8.2.6. Applies to 5g networks */ + public static final int NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST = 7; + /** DEREGISTRATION REQUEST. Reference: 3GPP TS 24.501 8.2.12. Applies to 5g networks */ + public static final int NAS_PROTOCOL_MESSAGE_DEREGISTRATION_REQUEST = 8; + /** CONNECTION MANAGEMENT REESTABLISHMENT REQUEST. Reference: 3GPP TS 24.008 9.2.4. + * Applies to 2g and 3g networks */ + public static final int NAS_PROTOCOL_MESSAGE_CM_REESTABLISHMENT_REQUEST = 9; + /** CONNECTION MANAGEMENT SERVICE REQUEST. Reference: 3GPP TS 24.008 9.2.9. + * Applies to 2g and 3g networks */ + public static final int NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST = 10; + /** IMEI DETATCH INDICATION. Reference: 3GPP TS 24.008 9.2.14. + * Applies to 2g and 3g networks. Used for circuit-switched detach. */ + public static final int NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION = 11; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"NAS_PROTOCOL_MESSAGE_"}, value = {NAS_PROTOCOL_MESSAGE_UNKNOWN, + NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE, + NAS_PROTOCOL_MESSAGE_DETACH_REQUEST, NAS_PROTOCOL_MESSAGE_TRACKING_AREA_UPDATE_REQUEST, + NAS_PROTOCOL_MESSAGE_LOCATION_UPDATE_REQUEST, + NAS_PROTOCOL_MESSAGE_AUTHENTICATION_AND_CIPHERING_RESPONSE, + NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST, NAS_PROTOCOL_MESSAGE_DEREGISTRATION_REQUEST, + NAS_PROTOCOL_MESSAGE_CM_REESTABLISHMENT_REQUEST, + NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST, NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION}) + public @interface NasProtocolMessage { + } + + /* Cellular identifiers */ + /** Unknown */ + public static final int CELLULAR_IDENTIFIER_UNKNOWN = 0; + /** IMSI (International Mobile Subscriber Identity) */ + public static final int CELLULAR_IDENTIFIER_IMSI = 1; + /** IMEI (International Mobile Equipment Identity) */ + public static final int CELLULAR_IDENTIFIER_IMEI = 2; + /** 5G-specific SUCI (Subscription Concealed Identifier) */ + public static final int CELLULAR_IDENTIFIER_SUCI = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"CELLULAR_IDENTIFIER_"}, value = {CELLULAR_IDENTIFIER_UNKNOWN, + CELLULAR_IDENTIFIER_IMSI, CELLULAR_IDENTIFIER_IMEI, CELLULAR_IDENTIFIER_SUCI}) + public @interface CellularIdentifier { + } + private @NasProtocolMessage int mNasProtocolMessage; private @CellularIdentifier int mCellularIdentifier; private String mPlmn; private boolean mIsEmergency; + /** + * Constructor for new CellularIdentifierDisclosure instances. + * + * @hide + */ + @TestApi public CellularIdentifierDisclosure(@NasProtocolMessage int nasProtocolMessage, - @CellularIdentifier int cellularIdentifier, String plmn, boolean isEmergency) { + @CellularIdentifier int cellularIdentifier, @NonNull String plmn, boolean isEmergency) { mNasProtocolMessage = nasProtocolMessage; mCellularIdentifier = cellularIdentifier; mPlmn = plmn; @@ -51,18 +128,30 @@ public final class CellularIdentifierDisclosure implements Parcelable { readFromParcel(in); } + /** + * @return the NAS protocol message associated with the disclosed identifier. + */ public @NasProtocolMessage int getNasProtocolMessage() { return mNasProtocolMessage; } + /** + * @return the identifier disclosed. + */ public @CellularIdentifier int getCellularIdentifier() { return mCellularIdentifier; } - public String getPlmn() { + /** + * @return the PLMN associated with the disclosure. + */ + @NonNull public String getPlmn() { return mPlmn; } + /** + * @return if the disclosure is associated with an emergency call. + */ public boolean isEmergency() { return mIsEmergency; } @@ -73,14 +162,14 @@ public final class CellularIdentifierDisclosure implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(mNasProtocolMessage); out.writeInt(mCellularIdentifier); out.writeBoolean(mIsEmergency); out.writeString8(mPlmn); } - public static final Parcelable.Creator<CellularIdentifierDisclosure> CREATOR = + public static final @NonNull Parcelable.Creator<CellularIdentifierDisclosure> CREATOR = new Parcelable.Creator<CellularIdentifierDisclosure>() { public CellularIdentifierDisclosure createFromParcel(Parcel in) { return new CellularIdentifierDisclosure(in); @@ -120,42 +209,4 @@ public final class CellularIdentifierDisclosure implements Parcelable { mIsEmergency = in.readBoolean(); mPlmn = in.readString8(); } - - public static final int NAS_PROTOCOL_MESSAGE_UNKNOWN = 0; - public static final int NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST = 1; - public static final int NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE = 2; - public static final int NAS_PROTOCOL_MESSAGE_DETACH_REQUEST = 3; - public static final int NAS_PROTOCOL_MESSAGE_TRACKING_AREA_UPDATE_REQUEST = 4; - public static final int NAS_PROTOCOL_MESSAGE_LOCATION_UPDATE_REQUEST = 5; - public static final int NAS_PROTOCOL_MESSAGE_AUTHENTICATION_AND_CIPHERING_RESPONSE = 6; - public static final int NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST = 7; - public static final int NAS_PROTOCOL_MESSAGE_DEREGISTRATION_REQUEST = 8; - public static final int NAS_PROTOCOL_MESSAGE_CM_REESTABLISHMENT_REQUEST = 9; - public static final int NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST = 10; - public static final int NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION = 11; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"NAS_PROTOCOL_MESSAGE_"}, value = {NAS_PROTOCOL_MESSAGE_UNKNOWN, - NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE, - NAS_PROTOCOL_MESSAGE_DETACH_REQUEST, NAS_PROTOCOL_MESSAGE_TRACKING_AREA_UPDATE_REQUEST, - NAS_PROTOCOL_MESSAGE_LOCATION_UPDATE_REQUEST, - NAS_PROTOCOL_MESSAGE_AUTHENTICATION_AND_CIPHERING_RESPONSE, - NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST, NAS_PROTOCOL_MESSAGE_DEREGISTRATION_REQUEST, - NAS_PROTOCOL_MESSAGE_CM_REESTABLISHMENT_REQUEST, - NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST, NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION}) - public @interface NasProtocolMessage { - } - - public static final int CELLULAR_IDENTIFIER_UNKNOWN = 0; - public static final int CELLULAR_IDENTIFIER_IMSI = 1; - public static final int CELLULAR_IDENTIFIER_IMEI = 2; - public static final int CELLULAR_IDENTIFIER_SUCI = 3; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"CELLULAR_IDENTIFIER_"}, value = {CELLULAR_IDENTIFIER_UNKNOWN, - CELLULAR_IDENTIFIER_IMSI, CELLULAR_IDENTIFIER_IMEI, CELLULAR_IDENTIFIER_SUCI}) - public @interface CellularIdentifier { - } } diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.aidl b/telephony/java/android/telephony/SecurityAlgorithmUpdate.aidl new file mode 100644 index 000000000000..bee30bd43df9 --- /dev/null +++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.aidl @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** @hide */ +package android.telephony; + +parcelable SecurityAlgorithmUpdate; diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java index 57209eb68de8..d635b555276b 100644 --- a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java +++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java @@ -16,11 +16,16 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -31,112 +36,42 @@ import java.util.Objects; * * @hide */ +@SystemApi +@FlaggedApi(Flags.FLAG_SECURITY_ALGORITHMS_UPDATE_INDICATIONS) public final class SecurityAlgorithmUpdate implements Parcelable { private static final String TAG = "SecurityAlgorithmUpdate"; - private @ConnectionEvent int mConnectionEvent; - private @SecurityAlgorithm int mEncryption; - private @SecurityAlgorithm int mIntegrity; - private boolean mIsUnprotectedEmergency; - - public SecurityAlgorithmUpdate(@ConnectionEvent int connectionEvent, - @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity, - boolean isUnprotectedEmergency) { - mConnectionEvent = connectionEvent; - mEncryption = encryption; - mIntegrity = integrity; - mIsUnprotectedEmergency = isUnprotectedEmergency; - } - - private SecurityAlgorithmUpdate(Parcel in) { - readFromParcel(in); - } - - public @ConnectionEvent int getConnectionEvent() { - return mConnectionEvent; - } - - public @SecurityAlgorithm int getEncryption() { - return mEncryption; - } - - public @SecurityAlgorithm int getIntegrity() { - return mIntegrity; - } - - public boolean isUnprotectedEmergency() { - return mIsUnprotectedEmergency; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mConnectionEvent); - out.writeInt(mEncryption); - out.writeInt(mIntegrity); - out.writeBoolean(mIsUnprotectedEmergency); - } - - private void readFromParcel(@NonNull Parcel in) { - mConnectionEvent = in.readInt(); - mEncryption = in.readInt(); - mIntegrity = in.readInt(); - mIsUnprotectedEmergency = in.readBoolean(); - } - - public static final Parcelable.Creator<SecurityAlgorithmUpdate> CREATOR = - new Parcelable.Creator<SecurityAlgorithmUpdate>() { - public SecurityAlgorithmUpdate createFromParcel(Parcel in) { - return new SecurityAlgorithmUpdate(in); - } - - public SecurityAlgorithmUpdate[] newArray(int size) { - return new SecurityAlgorithmUpdate[size]; - } - }; - - @Override - public String toString() { - return TAG + ":{ mConnectionEvent = " + mConnectionEvent + " mEncryption = " + mEncryption - + " mIntegrity = " + mIntegrity + " mIsUnprotectedEmergency = " - + mIsUnprotectedEmergency; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SecurityAlgorithmUpdate)) return false; - SecurityAlgorithmUpdate that = (SecurityAlgorithmUpdate) o; - return mConnectionEvent == that.mConnectionEvent - && mEncryption == that.mEncryption - && mIntegrity == that.mIntegrity - && mIsUnprotectedEmergency == that.mIsUnprotectedEmergency; - } - - @Override - public int hashCode() { - return Objects.hash(mConnectionEvent, mEncryption, mIntegrity, mIsUnprotectedEmergency); - } - + /** 2G GSM circuit switched */ public static final int CONNECTION_EVENT_CS_SIGNALLING_GSM = 0; + /** 2G GPRS packet services */ public static final int CONNECTION_EVENT_PS_SIGNALLING_GPRS = 1; + /** 3G circuit switched*/ public static final int CONNECTION_EVENT_CS_SIGNALLING_3G = 2; + /** 3G packet switched*/ public static final int CONNECTION_EVENT_PS_SIGNALLING_3G = 3; + /** 4G Non-access stratum */ public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4; + /** 4G Access-stratum */ public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5; + /** VOLTE SIP */ public static final int CONNECTION_EVENT_VOLTE_SIP = 6; + /** VOLTE SIP SOS (emergency) */ public static final int CONNECTION_EVENT_VOLTE_SIP_SOS = 7; + /** VOLTE RTP */ public static final int CONNECTION_EVENT_VOLTE_RTP = 8; + /** VOLTE RTP SOS (emergency) */ public static final int CONNECTION_EVENT_VOLTE_RTP_SOS = 9; + /** 5G Non-access stratum */ public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 10; + /** 5G Access stratum */ public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 11; + /** VoNR SIP */ public static final int CONNECTION_EVENT_VONR_SIP = 12; + /** VoNR SIP SOS (emergency) */ public static final int CONNECTION_EVENT_VONR_SIP_SOS = 13; + /** VoNR RTP */ public static final int CONNECTION_EVENT_VONR_RTP = 14; + /** VoNR RTP SOS (emergency) */ public static final int CONNECTION_EVENT_VONR_RTP_SOS = 15; /** @hide */ @@ -153,48 +88,101 @@ public final class SecurityAlgorithmUpdate implements Parcelable { public @interface ConnectionEvent { } + /* GSM CS services, see 3GPP TS 43.020 for details */ + /** A5/0 - the null cipher */ public static final int SECURITY_ALGORITHM_A50 = 0; + /** A5/1 cipher */ public static final int SECURITY_ALGORITHM_A51 = 1; + /** A5/2 cipher */ public static final int SECURITY_ALGORITHM_A52 = 2; + /** A5/3 cipher */ public static final int SECURITY_ALGORITHM_A53 = 3; + /** A5/4 cipher */ public static final int SECURITY_ALGORITHM_A54 = 4; + /* GPRS PS services (3GPP TS 43.020) */ + /** GEA0 - null cipher */ public static final int SECURITY_ALGORITHM_GEA0 = 14; + /** GEA1 cipher */ public static final int SECURITY_ALGORITHM_GEA1 = 15; + /** GEA2 cipher */ public static final int SECURITY_ALGORITHM_GEA2 = 16; + /** GEA3 cipher */ public static final int SECURITY_ALGORITHM_GEA3 = 17; + /** GEA4 cipher */ public static final int SECURITY_ALGORITHM_GEA4 = 18; + /** GEA5 cipher */ public static final int SECURITY_ALGORITHM_GEA5 = 19; + /* 3G PS/CS services (3GPP TS 33.102) */ + /** UEA0 - null cipher */ public static final int SECURITY_ALGORITHM_UEA0 = 29; + /** UEA1 cipher */ public static final int SECURITY_ALGORITHM_UEA1 = 30; + /** UEA2 cipher */ public static final int SECURITY_ALGORITHM_UEA2 = 31; + /* 4G PS services & 5G NSA (3GPP TS 33.401) */ + /** EEA0 - null cipher */ public static final int SECURITY_ALGORITHM_EEA0 = 41; + /** EEA1 */ public static final int SECURITY_ALGORITHM_EEA1 = 42; + /** EEA2 */ public static final int SECURITY_ALGORITHM_EEA2 = 43; + /** EEA3 */ public static final int SECURITY_ALGORITHM_EEA3 = 44; + /* 5G PS services (3GPP TS 33.401 for 5G NSA and 3GPP TS 33.501 for 5G SA) */ + /** NEA0 - the null cipher */ public static final int SECURITY_ALGORITHM_NEA0 = 55; + /** NEA1 */ public static final int SECURITY_ALGORITHM_NEA1 = 56; + /** NEA2 */ public static final int SECURITY_ALGORITHM_NEA2 = 57; + /** NEA3 */ public static final int SECURITY_ALGORITHM_NEA3 = 58; + /* IMS and SIP layer security (See 3GPP TS 33.203) */ + /** No IPsec config */ public static final int SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG = 66; + /** No IMS security, recommended to use SIP_NO_IPSEC_CONFIG and SIP_NULL instead */ public static final int SECURITY_ALGORITHM_IMS_NULL = 67; + /* IPSEC is present */ + /** SIP security is not enabled */ public static final int SECURITY_ALGORITHM_SIP_NULL = 68; + /** AES GCM mode */ public static final int SECURITY_ALGORITHM_AES_GCM = 69; + /** AES GMAC mode */ public static final int SECURITY_ALGORITHM_AES_GMAC = 70; + /** AES CBC mode */ public static final int SECURITY_ALGORITHM_AES_CBC = 71; + /** DES EDE3 CBC mode */ public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72; + /** AES EDE3 CBC mode */ public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73; + /** HMAC SHA1 96 */ public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74; + /** HMAC MD5 96 */ public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75; + /* RTP and SRTP (see 3GPP TS 33.328) */ + /** RTP only, SRTP is not being used */ public static final int SECURITY_ALGORITHM_RTP = 85; + /* When SRTP is available and used */ + /** SRTP with null ciphering */ public static final int SECURITY_ALGORITHM_SRTP_NULL = 86; + /** SRTP with AES counter mode */ public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87; + /** SRTP with AES F8 mode */ public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88; + /** SRTP with HMAC SHA1 */ public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89; + /* Ciphers for ePDG (3GPP TS 33.402) */ + /** ePDG encryption - AES GCM mode */ public static final int SECURITY_ALGORITHM_ENCR_AES_GCM_16 = 99; + /** ePDG encryption - AES GCM CBC mode */ public static final int SECURITY_ALGORITHM_ENCR_AES_CBC = 100; + /** ePDG authentication - HMAC SHA1 256 128 */ public static final int SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128 = 101; + /** Unknown */ public static final int SECURITY_ALGORITHM_UNKNOWN = 113; + /** Other */ public static final int SECURITY_ALGORITHM_OTHER = 114; + /** Proprietary algorithms */ public static final int SECURITY_ALGORITHM_ORYX = 124; /** @hide */ @@ -220,4 +208,109 @@ public final class SecurityAlgorithmUpdate implements Parcelable { public @interface SecurityAlgorithm { } + private @ConnectionEvent int mConnectionEvent; + private @SecurityAlgorithm int mEncryption; + private @SecurityAlgorithm int mIntegrity; + private boolean mIsUnprotectedEmergency; + + /** + * Constructor for new SecurityAlgorithmUpdate instances. + * + * @hide + */ + @TestApi + public SecurityAlgorithmUpdate(@ConnectionEvent int connectionEvent, + @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity, + boolean isUnprotectedEmergency) { + mConnectionEvent = connectionEvent; + mEncryption = encryption; + mIntegrity = integrity; + mIsUnprotectedEmergency = isUnprotectedEmergency; + } + + private SecurityAlgorithmUpdate(Parcel in) { + readFromParcel(in); + } + + /** + * @return the connection event. + */ + public @ConnectionEvent int getConnectionEvent() { + return mConnectionEvent; + } + + /** + * @return the encryption algorithm. + */ + public @SecurityAlgorithm int getEncryption() { + return mEncryption; + } + + /** + * @return the integrity algorithm. + */ + public @SecurityAlgorithm int getIntegrity() { + return mIntegrity; + } + + /** + * @return if the security algorithm update is associated with an unprotected emergency call. + */ + public boolean isUnprotectedEmergency() { + return mIsUnprotectedEmergency; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mConnectionEvent); + out.writeInt(mEncryption); + out.writeInt(mIntegrity); + out.writeBoolean(mIsUnprotectedEmergency); + } + + private void readFromParcel(@NonNull Parcel in) { + mConnectionEvent = in.readInt(); + mEncryption = in.readInt(); + mIntegrity = in.readInt(); + mIsUnprotectedEmergency = in.readBoolean(); + } + + public static final @NonNull Parcelable.Creator<SecurityAlgorithmUpdate> CREATOR = + new Parcelable.Creator<SecurityAlgorithmUpdate>() { + public SecurityAlgorithmUpdate createFromParcel(Parcel in) { + return new SecurityAlgorithmUpdate(in); + } + + public SecurityAlgorithmUpdate[] newArray(int size) { + return new SecurityAlgorithmUpdate[size]; + } + }; + + @Override + public String toString() { + return TAG + ":{ mConnectionEvent = " + mConnectionEvent + " mEncryption = " + mEncryption + + " mIntegrity = " + mIntegrity + " mIsUnprotectedEmergency = " + + mIsUnprotectedEmergency; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SecurityAlgorithmUpdate)) return false; + SecurityAlgorithmUpdate that = (SecurityAlgorithmUpdate) o; + return mConnectionEvent == that.mConnectionEvent + && mEncryption == that.mEncryption + && mIntegrity == that.mIntegrity + && mIsUnprotectedEmergency == that.mIsUnprotectedEmergency; + } + + @Override + public int hashCode() { + return Objects.hash(mConnectionEvent, mEncryption, mIntegrity, mIsUnprotectedEmergency); + } } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 887b79883710..23203ed65e9a 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -281,6 +281,14 @@ public final class SatelliteManager { "satellite_access_configuration"; /** + * Bundle key to get the response from + * {@link #requestSelectedNbIotSatelliteSubscriptionId(Executor, OutcomeReceiver)}. + * @hide + */ + public static final String KEY_SELECTED_NB_IOT_SATELLITE_SUBSCRIPTION_ID = + "selected_nb_iot_satellite_subscription_id"; + + /** * The request was successfully processed. * @hide */ @@ -531,6 +539,12 @@ public final class SatelliteManager { @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_RESULT_ENABLE_IN_PROGRESS = 29; + /** + * There is no valid satellite subscription selected. + * @hide + */ + public static final int SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION = 30; + /** @hide */ @IntDef(prefix = {"SATELLITE_RESULT_"}, value = { SATELLITE_RESULT_SUCCESS, @@ -562,7 +576,8 @@ public final class SatelliteManager { SATELLITE_RESULT_LOCATION_NOT_AVAILABLE, SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS, SATELLITE_RESULT_DISABLE_IN_PROGRESS, - SATELLITE_RESULT_ENABLE_IN_PROGRESS + SATELLITE_RESULT_ENABLE_IN_PROGRESS, + SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteResult {} @@ -2464,6 +2479,68 @@ public final class SatelliteManager { } /** + * Request to get the currently selected satellite subscription id as an {@link Integer}. + * + * @param executor The executor on which the callback will be called. + * @param callback The callback object to which the result will be delivered. + * If the request is successful, {@link OutcomeReceiver#onResult(Object)} + * will return the time after which the satellite will be visible. + * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} + * will return a {@link SatelliteException} with the {@link SatelliteResult}. + * + * @throws SecurityException if the caller doesn't have required permission. + * + * @hide + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + public void requestSelectedNbIotSatelliteSubscriptionId( + @NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<Integer, SatelliteException> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + ResultReceiver receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == SATELLITE_RESULT_SUCCESS) { + if (resultData + .containsKey(KEY_SELECTED_NB_IOT_SATELLITE_SUBSCRIPTION_ID)) { + int selectedSatelliteSubscriptionId = + resultData + .getInt(KEY_SELECTED_NB_IOT_SATELLITE_SUBSCRIPTION_ID); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onResult(selectedSatelliteSubscriptionId))); + } else { + loge( + "KEY_SELECTED_NB_IOT_SATELLITE_SUBSCRIPTION_ID does not exist." + ); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError(new SatelliteException( + SATELLITE_RESULT_REQUEST_FAILED)))); + } + } else { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError(new SatelliteException(resultCode)))); + } + } + }; + telephony.requestSelectedNbIotSatelliteSubscriptionId(receiver); + } else { + loge("requestSelectedNbIotSatelliteSubscriptionId() invalid telephony"); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); + } + } catch (RemoteException ex) { + loge("requestSelectedNbIotSatelliteSubscriptionId() RemoteException: " + ex); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); + } + } + + /** * Inform whether the device is aligned with the satellite in both real and demo mode. * * In demo mode, framework will send datagram to modem only when device is aligned with diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index a58427318b55..d22e9fa20101 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3018,6 +3018,17 @@ interface ITelephony { + "android.Manifest.permission.SATELLITE_COMMUNICATION)") void requestTimeForNextSatelliteVisibility(in ResultReceiver receiver); + + /** + * Request to get the currently selected satellite subscription id. + * + * @param receiver Result receiver to get the error code of the request and the currently + * selected satellite subscription id. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + void requestSelectedNbIotSatelliteSubscriptionId(in ResultReceiver receiver); + /** * Inform whether the device is aligned with the satellite in both real and demo mode. * diff --git a/test-mock/src/android/test/mock/MockContentResolver.java b/test-mock/src/android/test/mock/MockContentResolver.java index 8f4bcccb0cba..af6ee3d0f71b 100644 --- a/test-mock/src/android/test/mock/MockContentResolver.java +++ b/test-mock/src/android/test/mock/MockContentResolver.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.IContentProvider; import android.database.ContentObserver; import android.net.Uri; +import android.util.Log; import java.util.Collection; import java.util.HashMap; @@ -53,6 +54,7 @@ import java.util.Map; * </div> */ public class MockContentResolver extends ContentResolver { + private static final String TAG = "MockContentResolver"; Map<String, ContentProvider> mProviders; /** @@ -105,6 +107,7 @@ public class MockContentResolver extends ContentResolver { if (provider != null) { return provider.getIContentProvider(); } else { + Log.w(TAG, "Provider does not exist: " + name); return null; } } diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java index 2cd625eec032..4d495adf727b 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java +++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java @@ -18,7 +18,9 @@ package android.app.jank.tests; import static org.junit.Assert.assertEquals; +import android.app.jank.AppJankStats; import android.app.jank.Flags; +import android.app.jank.FrameOverrunHistogram; import android.app.jank.JankDataProcessor; import android.app.jank.StateTracker; import android.platform.test.annotations.RequiresFlagsEnabled; @@ -39,6 +41,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; @RunWith(AndroidJUnit4.class) @@ -154,6 +157,73 @@ public class JankDataProcessorTest { assertEquals(totalFrames, histogramFrames); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void mergeAppJankStats_confirmStatAddedToPendingStats() { + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + mJankDataProcessor.getPendingJankStats(); + + assertEquals(pendingStats.size(), 0); + + AppJankStats jankStats = getAppJankStats(); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + + pendingStats = mJankDataProcessor.getPendingJankStats(); + + assertEquals(pendingStats.size(), 1); + } + + /** + * This test confirms matching states are combined into one pending stat. When JankStats are + * merged from outside the platform they will contain widget category, widget id and widget + * state. If an incoming JankStats matches a pending stat on all those fields the incoming + * JankStat will be merged into the existing stat. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void mergeAppJankStats_confirmStatsWithMatchingStatesAreCombinedIntoOnePendingStat() { + AppJankStats jankStats = getAppJankStats(); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + mJankDataProcessor.getPendingJankStats(); + assertEquals(pendingStats.size(), 1); + + AppJankStats secondJankStat = getAppJankStats(); + mJankDataProcessor.mergeJankStats(secondJankStat, sActivityName); + + pendingStats = mJankDataProcessor.getPendingJankStats(); + + assertEquals(pendingStats.size(), 1); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void mergeAppJankStats_whenStatsWithMatchingStatesMerge_confirmFrameCountsAdded() { + AppJankStats jankStats = getAppJankStats(); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + mJankDataProcessor.getPendingJankStats(); + + String statKey = pendingStats.keySet().iterator().next(); + JankDataProcessor.PendingJankStat pendingStat = pendingStats.get(statKey); + + assertEquals(pendingStats.size(), 1); + // The same jankStats objects are merged twice, this should result in the frame counts being + // doubled. + assertEquals(jankStats.getJankyFrameCount() * 2, pendingStat.getJankyFrames()); + assertEquals(jankStats.getTotalFrameCount() * 2, pendingStat.getTotalFrames()); + + int[] originalHistogramBuckets = jankStats.getFrameOverrunHistogram().getBucketCounters(); + int[] frameOverrunBuckets = pendingStat.getFrameOverrunBuckets(); + + for (int i = 0; i < frameOverrunBuckets.length; i++) { + assertEquals(originalHistogramBuckets[i] * 2, frameOverrunBuckets[i]); + } + } + // TODO b/375005277 add tests that cover logging and releasing resources back to pool. private long getTotalFramesCounted() { @@ -276,4 +346,26 @@ public class JankDataProcessorTest { return mockData; } + private AppJankStats getAppJankStats() { + AppJankStats jankStats = new AppJankStats( + /*App Uid*/APP_ID, + /*Widget Id*/"test widget id", + /*Widget Category*/AppJankStats.SCROLL, + /*Widget State*/AppJankStats.SCROLLING, + /*Total Frames*/100, + /*Janky Frames*/25, + getOverrunHistogram() + ); + return jankStats; + } + + private FrameOverrunHistogram getOverrunHistogram() { + FrameOverrunHistogram overrunHistogram = new FrameOverrunHistogram(); + overrunHistogram.addFrameOverrunMillis(-2); + overrunHistogram.addFrameOverrunMillis(1); + overrunHistogram.addFrameOverrunMillis(5); + overrunHistogram.addFrameOverrunMillis(25); + return overrunHistogram; + } + } diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt index 2c998c4217ac..64328275085d 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt @@ -92,7 +92,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : } /** Move an app to Desktop by dragging the app handle at the top. */ - fun enterDesktopModeWithDrag( + private fun enterDesktopModeWithDrag( wmHelper: WindowManagerStateHelper, device: UiDevice, motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH) @@ -155,14 +155,16 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n") } - fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) { + fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice, isPip: Boolean = false) { val caption = getCaptionForTheApp(wmHelper, device) val minimizeButton = getMinimizeButtonForTheApp(caption) minimizeButton.click() wmHelper .StateSyncBuilder() .withAppTransitionIdle() - .withWindowSurfaceDisappeared(innerHelper) + .apply { + if (isPip) withPipShown() else withWindowSurfaceDisappeared(innerHelper) + } .waitForAndVerify() } |