diff options
107 files changed, 2259 insertions, 857 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 5b9970b6c15d..6078712fa7d1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -26782,9 +26782,7 @@ package android.media.tv { } public final class TableResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable { - ctor public TableResponse(int, int, int, @Nullable android.net.Uri, int, int); - ctor public TableResponse(int, int, int, @NonNull byte[], int, int); - ctor public TableResponse(int, int, int, @NonNull android.os.SharedMemory, int, int); + ctor @Deprecated public TableResponse(int, int, int, @Nullable android.net.Uri, int, int); method public int getSize(); method @Nullable public byte[] getTableByteArray(); method @Nullable public android.os.SharedMemory getTableSharedMemory(); @@ -26793,6 +26791,14 @@ package android.media.tv { field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TableResponse> CREATOR; } + public static final class TableResponse.Builder { + ctor public TableResponse.Builder(int, int, int, int, int); + method @NonNull public android.media.tv.TableResponse build(); + method @NonNull public android.media.tv.TableResponse.Builder setTableByteArray(@NonNull byte[]); + method @NonNull public android.media.tv.TableResponse.Builder setTableSharedMemory(@NonNull android.os.SharedMemory); + method @NonNull public android.media.tv.TableResponse.Builder setTableUri(@NonNull android.net.Uri); + } + public final class TimelineRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable { ctor public TimelineRequest(int, int, int); ctor public TimelineRequest(int, int, int, @NonNull String); @@ -27337,8 +27343,9 @@ package android.media.tv { field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0 field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1 field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id"; - field public static final String TV_MESSAGE_TYPE_CLOSED_CAPTION = "CC"; - field public static final String TV_MESSAGE_TYPE_WATERMARK = "Watermark"; + field public static final int TV_MESSAGE_TYPE_CLOSED_CAPTION = 2; // 0x2 + field public static final int TV_MESSAGE_TYPE_OTHER = 1000; // 0x3e8 + field public static final int TV_MESSAGE_TYPE_WATERMARK = 1; // 0x1 field public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 4; // 0x4 field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3 field public static final int VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT = 16; // 0x10 @@ -27429,10 +27436,10 @@ package android.media.tv { method public void notifyTrackSelected(int, String); method public void notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>); method public void notifyTuned(@NonNull android.net.Uri); - method public void notifyTvMessage(@NonNull String, @NonNull android.os.Bundle); + method public void notifyTvMessage(int, @NonNull android.os.Bundle); method public void notifyVideoAvailable(); method public void notifyVideoUnavailable(int); - method public void onAdBuffer(@NonNull android.media.tv.AdBuffer); + method public void onAdBufferReady(@NonNull android.media.tv.AdBuffer); method public void onAppPrivateCommand(@NonNull String, android.os.Bundle); method public android.view.View onCreateOverlayView(); method public boolean onGenericMotionEvent(android.view.MotionEvent); @@ -27451,7 +27458,7 @@ package android.media.tv { method public void onSetInteractiveAppNotificationEnabled(boolean); method public abstract void onSetStreamVolume(@FloatRange(from=0.0, to=1.0) float); method public abstract boolean onSetSurface(@Nullable android.view.Surface); - method public void onSetTvMessageEnabled(@NonNull String, boolean); + method public void onSetTvMessageEnabled(int, boolean); method public void onSurfaceChanged(int, int, int); method public long onTimeShiftGetCurrentPosition(); method public long onTimeShiftGetStartPosition(); @@ -27501,17 +27508,17 @@ package android.media.tv { method @NonNull public android.net.Uri getChannelUri(); method @NonNull public java.util.List<android.media.tv.TvContentRating> getContentRatings(); method @NonNull public String getDescription(); - method @NonNull public long getEndPaddingMillis(); + method public long getEndPaddingMillis(); method @NonNull public String getName(); method @Nullable public android.net.Uri getProgramUri(); - method @IntRange(from=0xffffffff) @NonNull public long getRecordingDurationMillis(); + method @IntRange(from=0xffffffff) public long getRecordingDurationMillis(); method @NonNull public String getRecordingId(); - method @IntRange(from=0xffffffff) @NonNull public long getRecordingStartTimeMillis(); + method @IntRange(from=0xffffffff) public long getRecordingStartTimeMillis(); method @Nullable public android.net.Uri getRecordingUri(); - method @NonNull public int getRepeatDays(); - method @IntRange(from=0) @NonNull public long getScheduledDurationMillis(); - method @IntRange(from=0) @NonNull public long getScheduledStartTimeMillis(); - method @NonNull public long getStartPaddingMillis(); + method public int getRepeatDays(); + method @IntRange(from=0) public long getScheduledDurationMillis(); + method @IntRange(from=0) public long getScheduledStartTimeMillis(); + method public long getStartPaddingMillis(); method public void setDescription(@NonNull String); method public void setName(@NonNull String); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -27594,7 +27601,7 @@ package android.media.tv { method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener); method public void setStreamVolume(@FloatRange(from=0.0, to=1.0) float); method public void setTimeShiftPositionCallback(@Nullable android.media.tv.TvView.TimeShiftPositionCallback); - method public void setTvMessageEnabled(@NonNull String, boolean); + method public void setTvMessageEnabled(int, boolean); method public void setZOrderMediaOverlay(boolean); method public void setZOrderOnTop(boolean); method public void timeShiftPause(); @@ -27635,7 +27642,7 @@ package android.media.tv { method public void onTrackSelected(String, int, String); method public void onTracksChanged(String, java.util.List<android.media.tv.TvTrackInfo>); method public void onTuned(@NonNull String, @NonNull android.net.Uri); - method public void onTvMessage(@NonNull String, @NonNull String, @NonNull android.os.Bundle); + method public void onTvMessage(@NonNull String, int, @NonNull android.os.Bundle); method public void onVideoAvailable(String); method public void onVideoSizeChanged(String, int, int); method public void onVideoUnavailable(String, int); @@ -27743,7 +27750,7 @@ package android.media.tv.interactive { ctor public TvInteractiveAppService.Session(@NonNull android.content.Context); method public boolean isMediaViewEnabled(); method @CallSuper public void layoutSurface(int, int, int, int); - method @CallSuper public void notifyAdBuffer(@NonNull android.media.tv.AdBuffer); + method @CallSuper public void notifyAdBufferReady(@NonNull android.media.tv.AdBuffer); method @CallSuper public final void notifyBiInteractiveAppCreated(@NonNull android.net.Uri, @Nullable String); method @CallSuper public void notifySessionStateChanged(int, int); method @CallSuper public final void notifyTeletextAppStateChanged(int); @@ -27795,7 +27802,7 @@ package android.media.tv.interactive { method public boolean onTrackballEvent(@NonNull android.view.MotionEvent); method public void onTracksChanged(@NonNull java.util.List<android.media.tv.TvTrackInfo>); method public void onTuned(@NonNull android.net.Uri); - method public void onTvMessage(@NonNull String, @NonNull android.os.Bundle); + method public void onTvMessage(int, @NonNull android.os.Bundle); method public void onTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo); method public void onTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>); method public void onVideoAvailable(); @@ -27817,7 +27824,7 @@ package android.media.tv.interactive { method @CallSuper public void requestTimeShiftMode(); method @CallSuper public void requestTrackInfoList(); method @CallSuper public void requestTvRecordingInfo(@NonNull String); - method @CallSuper public void requestTvRecordingInfoList(@NonNull int); + method @CallSuper public void requestTvRecordingInfoList(int); method @CallSuper public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle); method @CallSuper public void sendTimeShiftCommandRequest(@NonNull String, @Nullable android.os.Bundle); method @CallSuper public void setMediaViewEnabled(boolean); @@ -27859,7 +27866,7 @@ package android.media.tv.interactive { method public void notifyTimeShiftPlaybackParams(@NonNull android.media.PlaybackParams); method public void notifyTimeShiftStartPositionChanged(@NonNull String, long); method public void notifyTimeShiftStatusChanged(@NonNull String, int); - method public void notifyTvMessage(@NonNull String, @NonNull android.os.Bundle); + method public void notifyTvMessage(@NonNull int, @NonNull android.os.Bundle); method public void onAttachedToWindow(); method public void onDetachedFromWindow(); method public void onLayout(boolean, int, int, int, int); @@ -27916,7 +27923,7 @@ package android.media.tv.interactive { method public void onRequestTimeShiftMode(@NonNull String); method public void onRequestTrackInfoList(@NonNull String); method public void onRequestTvRecordingInfo(@NonNull String, @NonNull String); - method public void onRequestTvRecordingInfoList(@NonNull String, @NonNull int); + method public void onRequestTvRecordingInfoList(@NonNull String, int); method public void onSetTvRecordingInfo(@NonNull String, @NonNull String, @NonNull android.media.tv.TvRecordingInfo); method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect); method public void onStateChanged(@NonNull String, int, int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fb5ee8d94524..cd96a1ea210a 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -33,6 +33,7 @@ package android { field public static final String ADD_TRUSTED_DISPLAY = "android.permission.ADD_TRUSTED_DISPLAY"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE"; + field public static final String ALLOWLISTED_WRITE_DEVICE_CONFIG = "android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG"; field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"; field public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS"; field public static final String ALLOW_SLIPPERY_TOUCHES = "android.permission.ALLOW_SLIPPERY_TOUCHES"; @@ -287,6 +288,7 @@ package android { field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO"; field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL"; field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL"; + field public static final String READ_WRITE_SYNC_DISABLED_MODE_CONFIG = "android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"; field public static final String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS"; field public static final String RECEIVE_BLUETOOTH_MAP = "android.permission.RECEIVE_BLUETOOTH_MAP"; field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"; @@ -10158,6 +10160,8 @@ package android.net.wifi.sharedconnectivity.service { public abstract class SharedConnectivityService extends android.app.Service { ctor public SharedConnectivityService(); + method public static boolean areHotspotNetworksEnabledForService(@NonNull android.content.Context); + method public static boolean areKnownNetworksEnabledForService(@NonNull android.content.Context); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onConnectHotspotNetwork(@NonNull android.net.wifi.sharedconnectivity.app.HotspotNetwork); method public abstract void onConnectKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 46651a9f5409..14c53d100d4f 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -5,6 +5,7 @@ package android { field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; + field public static final String ALLOWLISTED_WRITE_DEVICE_CONFIG = "android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; @@ -39,6 +40,7 @@ package android { field public static final String QUERY_AUDIO_STATE = "android.permission.QUERY_AUDIO_STATE"; field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; + field public static final String READ_WRITE_SYNC_DISABLED_MODE_CONFIG = "android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"; field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; @@ -1957,7 +1959,7 @@ package android.media.tv { } public class TvView extends android.view.ViewGroup { - method public void notifyTvMessage(@NonNull String, @NonNull android.os.Bundle); + method public void notifyTvMessage(int, @NonNull android.os.Bundle); } } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index d810f055e96e..90427cb51c26 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -18,6 +18,7 @@ package android.app; import static android.app.ActivityManager.StopUserOnSwitch; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.PermissionMethod; @@ -47,6 +48,8 @@ import android.util.Pair; import com.android.internal.os.TimeoutRecord; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -991,4 +994,48 @@ public abstract class ActivityManagerInternal { */ public abstract void logFgsApiEnd(int apiType, int uid, int pid); + /** + * The list of the events about the {@link android.media.projection.IMediaProjection} itself. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + MEDIA_PROJECTION_TOKEN_EVENT_CREATED, + MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED, + }) + public @interface MediaProjectionTokenEvent{}; + + /** + * An instance of {@link android.media.projection.IMediaProjection} has been created + * by the system. + * + * @hide + */ + public static final @MediaProjectionTokenEvent int MEDIA_PROJECTION_TOKEN_EVENT_CREATED = 0; + + /** + * An instance of {@link android.media.projection.IMediaProjection} has been destroyed + * by the system. + * + * @hide + */ + public static final @MediaProjectionTokenEvent int MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED = 1; + + /** + * Called after the system created/destroyed a media projection for an app, if the user + * has granted the permission to start a media projection from this app. + * + * <p>This API is specifically for the use case of enforcing the FGS type + * {@code android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}, + * where the app who is starting this type of FGS must have been granted with the permission + * to start the projection via the {@link android.media.projection.MediaProjection} APIs. + * + * @param uid The uid of the app which the system created/destroyed a media projection for. + * @param projectionToken The {@link android.media.projection.IMediaProjection} token that + * the system created/destroyed. + * @param event The actual event happening to the given {@code projectionToken}. + */ + public abstract void notifyMediaProjectionEvent(int uid, @NonNull IBinder projectionToken, + @MediaProjectionTokenEvent int event); } diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index a7824a850e59..be012cf8e202 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -59,6 +59,9 @@ import android.hardware.usb.UsbManager; import android.os.RemoteException; import android.os.ServiceManager; import android.permission.PermissionCheckerManager; +import android.provider.DeviceConfig; +import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; @@ -147,6 +150,108 @@ public abstract class ForegroundServiceTypePolicy { public static final long FGS_TYPE_PERMISSION_CHANGE_ID = 254662522L; /** + * The prefix for the feature flags of the permission enforcement. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX = + "fgs_type_perm_enforcement_flag_"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_DATA_SYNC = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "data_sync"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_MEDIA_PLAYBACK = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "media_playback"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_PHONE_CALL}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_PHONE_CALL = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "phone_call"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_LOCATION = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "location"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_CONNECTED_DEVICE = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "connected_device"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_MEDIA_PROJECTION = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "media_projection"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CAMERA}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_CAMERA = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "camera"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_MICROPHONE = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "microphone"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "health"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_REMOTE_MESSAGING = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "remote_messaging"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_SYSTEM_EXEMPTED = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "system_exempted"; + + /** + * The feature flag of the permission enforcement for + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}, + * in the namespace of {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}. + */ + private static final String FGS_TYPE_PERM_ENFORCEMENT_FLAG_SPECIAL_USE = + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PREFIX + "special_use"; + + /** * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST}. * * @hide @@ -156,8 +261,10 @@ public abstract class ForegroundServiceTypePolicy { FOREGROUND_SERVICE_TYPE_MANIFEST, FGS_TYPE_NONE_DEPRECATION_CHANGE_ID, FGS_TYPE_NONE_DISABLED_CHANGE_ID, - null, - null + null /* allOfPermissions */, + null /* anyOfPermissions */, + null /* permissionEnforcementFlag */, + false /* permissionEnforcementFlagDefaultValue */ ); /** @@ -170,8 +277,10 @@ public abstract class ForegroundServiceTypePolicy { FOREGROUND_SERVICE_TYPE_NONE, FGS_TYPE_NONE_DEPRECATION_CHANGE_ID, FGS_TYPE_NONE_DISABLED_CHANGE_ID, - null, - null + null /* allOfPermissions */, + null /* anyOfPermissions */, + null /* permissionEnforcementFlag */, + false /* permissionEnforcementFlagDefaultValue */ ); /** @@ -187,7 +296,9 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC) }, true), - null + null /* anyOfPermissions */, + FGS_TYPE_PERM_ENFORCEMENT_FLAG_DATA_SYNC /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -203,7 +314,9 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK) }, true), - null + null /* anyOfPermissions */, + FGS_TYPE_PERM_ENFORCEMENT_FLAG_MEDIA_PLAYBACK /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -221,7 +334,9 @@ public abstract class ForegroundServiceTypePolicy { }, true), new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.MANAGE_OWN_CALLS) - }, false) + }, false), + FGS_TYPE_PERM_ENFORCEMENT_FLAG_PHONE_CALL /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -240,7 +355,9 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.ACCESS_COARSE_LOCATION), new RegularPermission(Manifest.permission.ACCESS_FINE_LOCATION), - }, false) + }, false), + FGS_TYPE_PERM_ENFORCEMENT_FLAG_LOCATION /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -268,7 +385,9 @@ public abstract class ForegroundServiceTypePolicy { new RegularPermission(Manifest.permission.UWB_RANGING), new UsbDevicePermission(), new UsbAccessoryPermission(), - }, false) + }, false), + FGS_TYPE_PERM_ENFORCEMENT_FLAG_CONNECTED_DEVICE /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -284,7 +403,12 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION) }, true), - null + new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { + new RegularPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT), + new AppOpPermission(AppOpsManager.OP_PROJECT_MEDIA) + }, false), + FGS_TYPE_PERM_ENFORCEMENT_FLAG_MEDIA_PROJECTION /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -303,7 +427,9 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.CAMERA), new RegularPermission(Manifest.permission.SYSTEM_CAMERA), - }, false) + }, false), + FGS_TYPE_PERM_ENFORCEMENT_FLAG_CAMERA /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -326,7 +452,9 @@ public abstract class ForegroundServiceTypePolicy { new RegularPermission(Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT), new RegularPermission(Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT), new RegularPermission(Manifest.permission.RECORD_AUDIO), - }, false) + }, false), + FGS_TYPE_PERM_ENFORCEMENT_FLAG_MICROPHONE /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -347,7 +475,9 @@ public abstract class ForegroundServiceTypePolicy { new RegularPermission(Manifest.permission.BODY_SENSORS), new RegularPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE), new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS), - }, false) + }, false), + FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -363,7 +493,9 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING) }, true), - null + null /* anyOfPermissions */, + FGS_TYPE_PERM_ENFORCEMENT_FLAG_REMOTE_MESSAGING /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -383,7 +515,9 @@ public abstract class ForegroundServiceTypePolicy { new RegularPermission(Manifest.permission.SCHEDULE_EXACT_ALARM), new RegularPermission(Manifest.permission.USE_EXACT_ALARM), new AppOpPermission(AppOpsManager.OP_ACTIVATE_VPN), - }, false) + }, false), + FGS_TYPE_PERM_ENFORCEMENT_FLAG_SYSTEM_EXEMPTED /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -396,7 +530,10 @@ public abstract class ForegroundServiceTypePolicy { FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID, ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID, - null /* no type specific permissions */, null /* no type specific permissions */ + null /* allOfPermissions */, + null /* anyOfPermissions */, + null /* permissionEnforcementFlag */, + false /* permissionEnforcementFlagDefaultValue */ ); /** @@ -412,7 +549,9 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT) }, true), - null + null /* anyOfPermissions */, + null /* permissionEnforcementFlag */, + false /* permissionEnforcementFlagDefaultValue */ ); /** @@ -428,7 +567,9 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE) }, true), - null + null /* anyOfPermissions */, + FGS_TYPE_PERM_ENFORCEMENT_FLAG_SPECIAL_USE /* permissionEnforcementFlag */, + true /* permissionEnforcementFlagDefaultValue */ ); /** @@ -512,6 +653,14 @@ public abstract class ForegroundServiceTypePolicy { @NonNull String packageName, int callerUid, int callerPid, boolean allowWhileInUse, @NonNull ForegroundServiceTypePolicyInfo policy); + /** + * Run the given {@code policyFunctor} on the matching policy, if the flag is known + * to the policy. + * + * @hide + */ + public abstract void updatePermissionEnforcementFlagIfNecessary(@NonNull String flag); + @GuardedBy("sLock") private static ForegroundServiceTypePolicy sDefaultForegroundServiceTypePolicy = null; @@ -575,11 +724,31 @@ public abstract class ForegroundServiceTypePolicy { final @Nullable ForegroundServiceTypePermissions mAnyOfPermissions; /** + * A permission enforcement flag, unlike the {@link #FGS_TYPE_PERMISSION_CHANGE_ID}, + * here it applies to all apps using this FGS type. + */ + final @Nullable String mPermissionEnforcementFlag; + + /** + * The default value to {@link #mPermissionEnforcementFlag}. + */ + final boolean mPermissionEnforcementFlagDefaultValue; + + /** * A customized check for the permissions. */ @Nullable ForegroundServiceTypePermission mCustomPermission; /** + * The value of the permission enforcement flag, will be updated by the system. + * If the value is {@code false}, the FGS permission check will be ignored. + * + * <p>This value could be updated via the DeviceConfig flag specified + * in the {@link #mPermissionEnforcementFlag}.</p> + */ + volatile boolean mPermissionEnforcementFlagValue; + + /** * Not a real change id, but a place holder. */ private static final long INVALID_CHANGE_ID = 0L; @@ -599,12 +768,17 @@ public abstract class ForegroundServiceTypePolicy { public ForegroundServiceTypePolicyInfo(@ForegroundServiceType int type, long deprecationChangeId, long disabledChangeId, @Nullable ForegroundServiceTypePermissions allOfPermissions, - @Nullable ForegroundServiceTypePermissions anyOfPermissions) { + @Nullable ForegroundServiceTypePermissions anyOfPermissions, + @Nullable String permissionEnforcementFlag, + boolean permissionEnforcementFlagDefaultValue) { mType = type; mDeprecationChangeId = deprecationChangeId; mDisabledChangeId = disabledChangeId; mAllOfPermissions = allOfPermissions; mAnyOfPermissions = anyOfPermissions; + mPermissionEnforcementFlag = permissionEnforcementFlag; + mPermissionEnforcementFlagDefaultValue = permissionEnforcementFlagDefaultValue; + mPermissionEnforcementFlagValue = permissionEnforcementFlagDefaultValue; } /** @@ -650,6 +824,17 @@ public abstract class ForegroundServiceTypePolicy { return sb; } + private void updatePermissionEnforcementFlagIfNecessary(@NonNull String flagName) { + if (mPermissionEnforcementFlag == null + || !TextUtils.equals(flagName, mPermissionEnforcementFlag)) { + return; + } + mPermissionEnforcementFlagValue = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + mPermissionEnforcementFlag, + mPermissionEnforcementFlagDefaultValue); + } + /** * @hide */ @@ -745,6 +930,15 @@ public abstract class ForegroundServiceTypePolicy { ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); platformCompat.clearOverrideForTest(changeId, packageName); } + + /** + * For test only. + * + * @return The permission enforcement flag. + */ + public @Nullable String getPermissionEnforcementFlagForTest() { + return mPermissionEnforcementFlag; + } } /** @@ -991,6 +1185,12 @@ public abstract class ForegroundServiceTypePolicy { new SparseArray<>(); /** + * The map between permission enforcement flag to its permission policy info. + */ + private final ArrayMap<String, ForegroundServiceTypePolicyInfo> + mPermissionEnforcementToPolicyInfoMap = new ArrayMap<>(); + + /** * Constructor */ public DefaultForegroundServiceTypePolicy() { @@ -1022,10 +1222,15 @@ public abstract class ForegroundServiceTypePolicy { FGS_TYPE_POLICY_SYSTEM_EXEMPTED); mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, FGS_TYPE_POLICY_SHORT_SERVICE); - mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT, - FGS_TYPE_POLICY_FILE_MANAGEMENT); + // TODO (b/271950506): revisit it in the next release. + // Hide the file management type for now. If anyone uses it, will default to "none". mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE, FGS_TYPE_POLICY_SPECIAL_USE); + for (int i = 0, size = mForegroundServiceTypePolicies.size(); i < size; i++) { + final ForegroundServiceTypePolicyInfo info = + mForegroundServiceTypePolicies.valueAt(i); + mPermissionEnforcementToPolicyInfoMap.put(info.mPermissionEnforcementFlag, info); + } } @Override @@ -1078,8 +1283,8 @@ public abstract class ForegroundServiceTypePolicy { } } if (permissionResult != PERMISSION_GRANTED) { - return (CompatChanges.isChangeEnabled( - FGS_TYPE_PERMISSION_CHANGE_ID, callerUid)) + return policy.mPermissionEnforcementFlagValue + && (CompatChanges.isChangeEnabled(FGS_TYPE_PERMISSION_CHANGE_ID, callerUid)) ? FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED : FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE; } @@ -1089,5 +1294,14 @@ public abstract class ForegroundServiceTypePolicy { } return FGS_TYPE_POLICY_CHECK_OK; } + + @Override + public void updatePermissionEnforcementFlagIfNecessary(@NonNull String flagName) { + final ForegroundServiceTypePolicyInfo info = + mPermissionEnforcementToPolicyInfoMap.get(flagName); + if (info != null) { + info.updatePermissionEnforcementFlagIfNecessary(flagName); + } + } } } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index cb678b98a998..5a48176d29e7 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -26,7 +26,6 @@ import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.SurfaceUtils; -import android.util.Log; import android.util.Range; import android.util.Size; import android.util.SparseIntArray; @@ -2019,7 +2018,7 @@ public final class StreamConfigurationMap { /** * @hide */ - public static final int HAL_DATASPACE_HEIF = 0x1003; + public static final int HAL_DATASPACE_HEIF = 0x1004; /** * @hide */ diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a8b4da14c1c5..24dcb69f8342 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5718,7 +5718,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 34; private static final int MSG_KEEP_CLEAR_RECTS_CHANGED = 35; private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36; - + private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37; final class ViewRootHandler extends Handler { @Override @@ -6006,6 +6006,11 @@ public final class ViewRootImpl implements ViewParent, case MSG_REQUEST_SCROLL_CAPTURE: handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj); break; + case MSG_PAUSED_FOR_SYNC_TIMEOUT: + Log.e(mTag, "Timedout waiting to unpause for sync"); + mNumPausedForSync = 0; + scheduleTraversals(); + break; } } } @@ -11584,9 +11589,16 @@ public final class ViewRootImpl implements ViewParent, mActiveSurfaceSyncGroup = new SurfaceSyncGroup(mTag); mActiveSurfaceSyncGroup.setAddedToSyncListener(() -> { Runnable runnable = () -> { - mNumPausedForSync--; - if (!mIsInTraversal && mNumPausedForSync == 0) { - scheduleTraversals(); + // Check if it's already 0 because the timeout could have reset the count to + // 0 and we don't want to go negative. + if (mNumPausedForSync > 0) { + mNumPausedForSync--; + } + if (mNumPausedForSync == 0) { + mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT); + if (!mIsInTraversal) { + scheduleTraversals(); + } } }; @@ -11613,6 +11625,8 @@ public final class ViewRootImpl implements ViewParent, } mNumPausedForSync++; + mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT); + mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT, 1000); return mActiveSurfaceSyncGroup; }; diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index 78de9544b59b..0672d6318212 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -22,6 +22,8 @@ import android.annotation.UiThread; import android.os.Binder; import android.os.BinderProxy; import android.os.Debug; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; @@ -60,6 +62,7 @@ public final class SurfaceSyncGroup { private static final int MAX_COUNT = 100; private static final AtomicInteger sCounter = new AtomicInteger(0); + private static final int TRANSACTION_READY_TIMEOUT = 1000; private static Supplier<Transaction> sTransactionFactory = Transaction::new; @@ -111,6 +114,13 @@ public final class SurfaceSyncGroup { */ private final Binder mToken = new Binder(); + private static final Object sHandlerThreadLock = new Object(); + @GuardedBy("sHandlerThreadLock") + private static HandlerThread sHandlerThread; + private Handler mHandler; + + private boolean mTimeoutAdded; + private static boolean isLocalBinder(IBinder binder) { return !(binder instanceof BinderProxy); } @@ -538,6 +548,11 @@ public final class SurfaceSyncGroup { + transactionReadyCallback.hashCode()); } + // Start the timeout when this SurfaceSyncGroup has been added to a parent SurfaceSyncGroup. + // This is because if the other SurfaceSyncGroup has bugs and doesn't complete, this SSG + // will get stuck. It's better to complete this SSG even if the parent SSG is broken. + addTimeout(); + boolean finished = false; Runnable addedToSyncListener = null; synchronized (mLock) { @@ -641,6 +656,9 @@ public final class SurfaceSyncGroup { } mTransactionReadyConsumer.accept(mTransaction); mFinished = true; + if (mTimeoutAdded) { + mHandler.removeCallbacksAndMessages(this); + } } /** @@ -701,6 +719,12 @@ public final class SurfaceSyncGroup { } } + // Start the timeout when another SSG has been added to this SurfaceSyncGroup. This is + // because if the other SurfaceSyncGroup has bugs and doesn't complete, it will affect this + // SSGs. So it's better to just add a timeout in case the other SSG doesn't invoke the + // callback and complete this SSG. + addTimeout(); + return transactionReadyCallback; } @@ -731,6 +755,41 @@ public final class SurfaceSyncGroup { } } + private void addTimeout() { + synchronized (sHandlerThreadLock) { + if (sHandlerThread == null) { + sHandlerThread = new HandlerThread("SurfaceSyncGroupTimer"); + sHandlerThread.start(); + } + } + + synchronized (mLock) { + if (mTimeoutAdded) { + // We only need one timeout for the entire SurfaceSyncGroup since we just want to + // ensure it doesn't stay stuck forever. + return; + } + + if (mHandler == null) { + mHandler = new Handler(sHandlerThread.getLooper()); + } + + mTimeoutAdded = true; + } + + Runnable runnable = () -> { + Log.e(TAG, "Failed to receive transaction ready in " + TRANSACTION_READY_TIMEOUT + + "ms. Marking SurfaceSyncGroup as ready " + mName); + // Clear out any pending syncs in case the other syncs can't complete or timeout due to + // a crash. + synchronized (mLock) { + mPendingSyncs.clear(); + } + markSyncReady(); + }; + mHandler.postDelayed(runnable, this, TRANSACTION_READY_TIMEOUT); + } + /** * A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must * implement onFrameStarted when trying to sync the SurfaceView. This is to ensure the sync diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8fdefab8ff2c..5be18fb482b5 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4203,6 +4203,16 @@ <permission android:name="android.permission.WRITE_DEVICE_CONFIG" android:protectionLevel="signature|verifier|configurator"/> + <!-- @SystemApi @TestApi @hide Allows an application to read/write sync disabled mode config. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" + android:protectionLevel="signature|verifier|configurator"/> + + <!-- @SystemApi @TestApi @hide Allows an application to modify only allowlisted settings. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG" + android:protectionLevel="signature|verifier|configurator"/> + <!-- @SystemApi @hide Allows an application to read config settings. <p>Not for use by third-party applications. --> <permission android:name="android.permission.READ_DEVICE_CONFIG" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 16511a678760..27c477f3f241 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3020,10 +3020,18 @@ >com.android.systemui/com.android.systemui.wifi.WifiDebuggingSecondaryUserActivity</string> <!-- Package name of the system service that implements the shared connectivity service --> - <string translatable="false" name="shared_connectivity_service_package"></string> + <string translatable="false" name="config_sharedConnectivityServicePackage"></string> <!-- Intent action used when binding to the shared connectivity service --> - <string translatable="false" name="shared_connectivity_service_intent_action"></string> + <string translatable="false" name="config_sharedConnectivityServiceIntentAction"></string> + + <!-- The system and settings UI can support all the features of instant tether. If set to false, + instant tether will run in notifications mode --> + <bool name="config_hotspotNetworksEnabledForService">false</bool> + + <!-- The system and settings UI can support all the features of known networks. If set to false, + known networks will run in notifications mode --> + <bool name="config_knownNetworksEnabledForService">false</bool> <!-- Component name of the activity that shows the usb containment status. --> <string name="config_usbContaminantActivity" translatable="false" diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c34d31cca7ba..a15833d36870 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4960,8 +4960,10 @@ <java-symbol type="bool" name="config_stopSystemPackagesByDefault"/> <java-symbol type="string" name="config_wearServiceComponent" /> - <java-symbol type="string" name="shared_connectivity_service_package" /> - <java-symbol type="string" name="shared_connectivity_service_intent_action" /> + <java-symbol type="string" name="config_sharedConnectivityServicePackage" /> + <java-symbol type="string" name="config_sharedConnectivityServiceIntentAction" /> + <java-symbol type="bool" name="config_hotspotNetworksEnabledForService"/> + <java-symbol type="bool" name="config_knownNetworksEnabledForService"/> <!-- Whether to show weather on the lockscreen by default. --> <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" /> diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java index ccf8085f87ff..b6fc137471a4 100644 --- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java +++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java @@ -91,7 +91,7 @@ public class NameValueCacheTest { mConfigsCacheGenerationStore = new MemoryIntArray(2); mConfigsCacheGenerationStore.set(0, 123); mConfigsCacheGenerationStore.set(1, 456); - mSettingsCacheGenerationStore = new MemoryIntArray(3); + mSettingsCacheGenerationStore = new MemoryIntArray(2); mSettingsCacheGenerationStore.set(0, 234); mSettingsCacheGenerationStore.set(1, 567); mConfigsStorage = new HashMap<>(); @@ -163,10 +163,6 @@ public class NameValueCacheTest { Bundle incomingBundle = invocationOnMock.getArgument(4); String key = invocationOnMock.getArgument(3); String value = incomingBundle.getString(Settings.NameValueTable.VALUE); - boolean newSetting = false; - if (!mSettingsStorage.containsKey(key)) { - newSetting = true; - } mSettingsStorage.put(key, value); int currentGeneration; // Different settings have different generation codes @@ -177,18 +173,12 @@ public class NameValueCacheTest { currentGeneration = mSettingsCacheGenerationStore.get(1); mSettingsCacheGenerationStore.set(1, ++currentGeneration); } - if (newSetting) { - // Tracking the generation of all unset settings. - // Increment when a new setting is inserted - currentGeneration = mSettingsCacheGenerationStore.get(2); - mSettingsCacheGenerationStore.set(2, ++currentGeneration); - } return null; }); // Returns the value corresponding to a setting, or null if the setting - // doesn't have a value stored for it. Returns the generation key - // if the caller asked for the generation key. + // doesn't have a value stored for it. Returns the generation key if the value isn't null + // and the caller asked for the generation key. when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()), eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class))).thenAnswer( invocationOnMock -> { @@ -199,15 +189,9 @@ public class NameValueCacheTest { Bundle bundle = new Bundle(); bundle.putSerializable(Settings.NameValueTable.VALUE, value); - if (incomingBundle.containsKey( + if (value != null && incomingBundle.containsKey( Settings.CALL_METHOD_TRACK_GENERATION_KEY)) { - int index; - if (value != null) { - index = key.equals(SETTING) ? 0 : 1; - } else { - // special index for unset settings - index = 2; - } + int index = key.equals(SETTING) ? 0 : 1; bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, mSettingsCacheGenerationStore); bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index); @@ -377,38 +361,16 @@ public class NameValueCacheTest { } @Test - public void testCaching_unsetSetting() throws Exception { + public void testCaching_nullSetting() throws Exception { String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING); verify(mMockIContentProvider, times(1)).call(any(), any(), eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class)); assertThat(returnedValue).isNull(); String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING); - // The first unset setting's generation number is cached - verifyNoMoreInteractions(mMockIContentProvider); - assertThat(cachedValue).isNull(); - - String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2); + // Empty list won't be cached verify(mMockIContentProvider, times(2)).call(any(), any(), eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class)); - assertThat(returnedValue2).isNull(); - - String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING); - // The second unset setting's generation number is cached - verifyNoMoreInteractions(mMockIContentProvider); - assertThat(cachedValue2).isNull(); - - Settings.Secure.putString(mMockContentResolver, SETTING, "a"); - // The generation for unset settings should have changed - returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2); - verify(mMockIContentProvider, times(3)).call(any(), any(), - eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class)); - assertThat(returnedValue2).isNull(); - - // The generation tracker for the first setting should have change because it's set now - returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING); - verify(mMockIContentProvider, times(4)).call(any(), any(), - eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class)); - assertThat(returnedValue).isEqualTo("a"); + assertThat(cachedValue).isNull(); } } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 913eaf2391f6..ffc5ff226607 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1099,6 +1099,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/WindowContainer.java" }, + "-1104347731": { + "message": "Setting requested orientation %s for %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "-1103716954": { "message": "Not removing %s due to exit animation", "level": "VERBOSE", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 852fae695046..ef53839cb2ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -139,6 +139,30 @@ public class BubbleController implements ConfigurationChangeListener { private static final boolean BUBBLE_BAR_ENABLED = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); + + /** + * Common interface to send updates to bubble views. + */ + public interface BubbleViewCallback { + /** Called when the provided bubble should be removed. */ + void removeBubble(Bubble removedBubble); + /** Called when the provided bubble should be added. */ + void addBubble(Bubble addedBubble); + /** Called when the provided bubble should be updated. */ + void updateBubble(Bubble updatedBubble); + /** Called when the provided bubble should be selected. */ + void selectionChanged(BubbleViewProvider selectedBubble); + /** Called when the provided bubble's suppression state has changed. */ + void suppressionChanged(Bubble bubble, boolean isSuppressed); + /** Called when the expansion state of bubbles has changed. */ + void expansionChanged(boolean isExpanded); + /** + * Called when the order of the bubble list has changed. Depending on the expanded state + * the pointer might need to be updated. + */ + void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer); + } + private final Context mContext; private final BubblesImpl mImpl = new BubblesImpl(); private Bubbles.BubbleExpandListener mExpandListener; @@ -162,7 +186,6 @@ public class BubbleController implements ConfigurationChangeListener { // Used to post to main UI thread private final ShellExecutor mMainExecutor; private final Handler mMainHandler; - private final ShellExecutor mBackgroundExecutor; private BubbleLogger mLogger; @@ -1320,6 +1343,58 @@ public class BubbleController implements ConfigurationChangeListener { }); } + private final BubbleViewCallback mBubbleViewCallback = new BubbleViewCallback() { + @Override + public void removeBubble(Bubble removedBubble) { + if (mStackView != null) { + mStackView.removeBubble(removedBubble); + } + } + + @Override + public void addBubble(Bubble addedBubble) { + if (mStackView != null) { + mStackView.addBubble(addedBubble); + } + } + + @Override + public void updateBubble(Bubble updatedBubble) { + if (mStackView != null) { + mStackView.updateBubble(updatedBubble); + } + } + + @Override + public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) { + if (mStackView != null) { + mStackView.updateBubbleOrder(bubbleOrder, updatePointer); + } + } + + @Override + public void suppressionChanged(Bubble bubble, boolean isSuppressed) { + if (mStackView != null) { + mStackView.setBubbleSuppressed(bubble, isSuppressed); + } + } + + @Override + public void expansionChanged(boolean isExpanded) { + if (mStackView != null) { + mStackView.setExpanded(isExpanded); + } + } + + @Override + public void selectionChanged(BubbleViewProvider selectedBubble) { + if (mStackView != null) { + mStackView.setSelectedBubble(selectedBubble); + } + + } + }; + @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { @@ -1342,7 +1417,8 @@ public class BubbleController implements ConfigurationChangeListener { // Lazy load overflow bubbles from disk loadOverflowBubblesFromDisk(); - mStackView.updateOverflowButtonDot(); + // If bubbles in the overflow have a dot, make sure the overflow shows a dot + updateOverflowButtonDot(); // Update bubbles in overflow. if (mOverflowListener != null) { @@ -1357,9 +1433,7 @@ public class BubbleController implements ConfigurationChangeListener { final Bubble bubble = removed.first; @Bubbles.DismissReason final int reason = removed.second; - if (mStackView != null) { - mStackView.removeBubble(bubble); - } + mBubbleViewCallback.removeBubble(bubble); // Leave the notification in place if we're dismissing due to user switching, or // because DND is suppressing the bubble. In both of those cases, we need to be able @@ -1389,49 +1463,47 @@ public class BubbleController implements ConfigurationChangeListener { } mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository); - if (update.addedBubble != null && mStackView != null) { + if (update.addedBubble != null) { mDataRepository.addBubble(mCurrentUserId, update.addedBubble); - mStackView.addBubble(update.addedBubble); + mBubbleViewCallback.addBubble(update.addedBubble); } - if (update.updatedBubble != null && mStackView != null) { - mStackView.updateBubble(update.updatedBubble); + if (update.updatedBubble != null) { + mBubbleViewCallback.updateBubble(update.updatedBubble); } - if (update.suppressedBubble != null && mStackView != null) { - mStackView.setBubbleSuppressed(update.suppressedBubble, true); + if (update.suppressedBubble != null) { + mBubbleViewCallback.suppressionChanged(update.suppressedBubble, true); } - if (update.unsuppressedBubble != null && mStackView != null) { - mStackView.setBubbleSuppressed(update.unsuppressedBubble, false); + if (update.unsuppressedBubble != null) { + mBubbleViewCallback.suppressionChanged(update.unsuppressedBubble, false); } boolean collapseStack = update.expandedChanged && !update.expanded; // At this point, the correct bubbles are inflated in the stack. // Make sure the order in bubble data is reflected in bubble row. - if (update.orderChanged && mStackView != null) { + if (update.orderChanged) { mDataRepository.addBubbles(mCurrentUserId, update.bubbles); // if the stack is going to be collapsed, do not update pointer position // after reordering - mStackView.updateBubbleOrder(update.bubbles, !collapseStack); + mBubbleViewCallback.bubbleOrderChanged(update.bubbles, !collapseStack); } if (collapseStack) { - mStackView.setExpanded(false); + mBubbleViewCallback.expansionChanged(/* expanded= */ false); mSysuiProxy.requestNotificationShadeTopUi(false, TAG); } - if (update.selectionChanged && mStackView != null) { - mStackView.setSelectedBubble(update.selectedBubble); + if (update.selectionChanged) { + mBubbleViewCallback.selectionChanged(update.selectedBubble); } // Expanding? Apply this last. if (update.expandedChanged && update.expanded) { - if (mStackView != null) { - mStackView.setExpanded(true); - mSysuiProxy.requestNotificationShadeTopUi(true, TAG); - } + mBubbleViewCallback.expansionChanged(/* expanded= */ true); + mSysuiProxy.requestNotificationShadeTopUi(true, TAG); } mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate"); @@ -1442,6 +1514,19 @@ public class BubbleController implements ConfigurationChangeListener { } }; + private void updateOverflowButtonDot() { + BubbleOverflow overflow = mBubbleData.getOverflow(); + if (overflow == null) return; + + for (Bubble b : mBubbleData.getOverflowBubbles()) { + if (b.showDot()) { + overflow.setShowDot(true); + return; + } + } + overflow.setShowDot(false); + } + private boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback) { if (isSummaryOfBubbles(entry)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 7d71089ef4fa..0b947c8b9b08 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1359,16 +1359,6 @@ public class BubbleStackView extends FrameLayout updateOverflowVisibility(); } - void updateOverflowButtonDot() { - for (Bubble b : mBubbleData.getOverflowBubbles()) { - if (b.showDot()) { - mBubbleOverflow.setShowDot(true); - return; - } - } - mBubbleOverflow.setShowDot(false); - } - /** * Handle theme changes. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index 9796e4c29352..2d84d211e30a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -267,6 +267,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { mLaunchRootTask = taskInfo; } + if (mHomeTask != null && mHomeTask.taskId == taskInfo.taskId + && !taskInfo.equals(mHomeTask)) { + mHomeTask = taskInfo; + } + super.onTaskInfoChanged(taskInfo); } @@ -376,6 +381,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { final WindowContainerTransaction wct = getWindowContainerTransaction(); final Rect taskBounds = calculateBounds(); wct.setBounds(mLaunchRootTask.token, taskBounds); + wct.setBounds(mHomeTask.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight)); mSyncQueue.queue(wct); final SurfaceControl finalLeash = mLaunchRootLeash; mSyncQueue.runInSync(t -> { 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 18a3849ea17d..a5546e554422 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 @@ -207,6 +207,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private boolean mIsDividerRemoteAnimating; private boolean mIsDropEntering; private boolean mIsExiting; + private boolean mIsRootTranslucent; private DefaultMixedHandler mMixedHandler; private final Toast mSplitUnsupportedToast; @@ -422,6 +423,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + if (!isSplitActive()) { + // prevent the fling divider to center transitioni if split screen didn't active. + mIsDropEntering = true; + } + setSideStagePosition(sideStagePosition, wct); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); targetStage.evictAllChildren(evictWct); @@ -436,28 +442,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // reparent the task to an invisible split root will make the activity invisible. Reorder // the root task to front to make the entering transition from pip to split smooth. wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, true); wct.reorder(targetStage.mRootTaskInfo.token, true); - wct.setForceTranslucent(targetStage.mRootTaskInfo.token, true); - // prevent the fling divider to center transition - mIsDropEntering = true; - targetStage.addTask(task, wct); - if (ENABLE_SHELL_TRANSITIONS) { - prepareEnterSplitScreen(wct); - mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, - null, this, null /* consumedCallback */, (finishWct, finishT) -> { - if (!evictWct.isEmpty()) { - finishWct.merge(evictWct, true); - } - } /* finishedCallback */); - } else { - if (!evictWct.isEmpty()) { - wct.merge(evictWct, true /* transfer */); - } - mTaskOrganizer.applyTransaction(wct); + if (!evictWct.isEmpty()) { + wct.merge(evictWct, true /* transfer */); } + mTaskOrganizer.applyTransaction(wct); return true; } @@ -716,7 +707,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setDivideRatio(splitRatio); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); + setRootForceTranslucent(false, wct); // Make sure the launch options will put tasks in the corresponding split roots mainOptions = mainOptions != null ? mainOptions : new Bundle(); @@ -923,7 +914,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); + setRootForceTranslucent(false, wct); // TODO(b/268008375): Merge APIs to start a split pair into one. if (mainTaskId != INVALID_TASK_ID) { @@ -1351,7 +1342,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.removeAllTasks(wct, false /* toTop */); mMainStage.deactivate(wct, false /* toTop */); wct.reorder(mRootTaskInfo.token, false /* onTop */); - wct.setForceTranslucent(mRootTaskInfo.token, true); + setRootForceTranslucent(true, wct); wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); onTransitionAnimationComplete(); } else { @@ -1383,7 +1374,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */); mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); - finishedWCT.setForceTranslucent(mRootTaskInfo.token, true); + setRootForceTranslucent(true, wct); finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); mSyncQueue.queue(finishedWCT); mSyncQueue.runInSync(at -> { @@ -1505,7 +1496,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.activate(wct, true /* includingTopTask */); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); + setRootForceTranslucent(false, wct); } void finishEnterSplitScreen(SurfaceControl.Transaction t) { @@ -1709,6 +1700,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTaskInfo = null; mRootTaskLeash = null; + mIsRootTranslucent = false; } @@ -1727,7 +1719,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Make the stages adjacent to each other so they occlude what's behind them. wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); - wct.setForceTranslucent(mRootTaskInfo.token, true); + setRootForceTranslucent(true, wct); mSplitLayout.getInvisibleBounds(mTempRect1); wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); mSyncQueue.queue(wct); @@ -1751,7 +1743,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.evictOtherChildren(wct, taskId); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); + setRootForceTranslucent(false, wct); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { @@ -1775,6 +1767,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout); } + private void setRootForceTranslucent(boolean translucent, WindowContainerTransaction wct) { + if (mIsRootTranslucent == translucent) return; + + mIsRootTranslucent = translucent; + wct.setForceTranslucent(mRootTaskInfo.token, translucent); + } + private void onStageVisibilityChanged(StageListenerImpl stageListener) { // If split didn't active, just ignore this callback because we should already did these // on #applyExitSplitScreen. @@ -1801,12 +1800,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Split entering background. wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, true /* setReparentLeafTaskIfRelaunch */); - if (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping) { - wct.setForceTranslucent(mRootTaskInfo.token, true); - } + setRootForceTranslucent(true, wct); } else { wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false /* setReparentLeafTaskIfRelaunch */); + setRootForceTranslucent(false, wct); } mSyncQueue.queue(wct); @@ -1938,7 +1936,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.activate(wct, true /* includingTopTask */); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); + setRootForceTranslucent(false, wct); } mSyncQueue.queue(wct); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java index a3d364a0068e..0bce3acecb3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java @@ -40,6 +40,7 @@ class TaskPositioner implements DragPositioningCallback { private final DisplayController mDisplayController; private final WindowDecoration mWindowDecoration; + private final Rect mTempBounds = new Rect(); private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); @@ -117,17 +118,32 @@ class TaskPositioner implements DragPositioningCallback { final float deltaX = x - mRepositionStartPoint.x; final float deltaY = y - mRepositionStartPoint.y; mRepositionTaskBounds.set(mTaskBoundsAtDragStart); + + final Rect stableBounds = mTempBounds; + // Make sure the new resizing destination in any direction falls within the stable bounds. + // If not, set the bounds back to the old location that was valid to avoid conflicts with + // some regions such as the gesture area. + mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId()) + .getStableBounds(stableBounds); if ((mCtrlType & CTRL_TYPE_LEFT) != 0) { - mRepositionTaskBounds.left += deltaX; + final int candidateLeft = mRepositionTaskBounds.left + (int) deltaX; + mRepositionTaskBounds.left = (candidateLeft > stableBounds.left) + ? candidateLeft : oldLeft; } if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) { - mRepositionTaskBounds.right += deltaX; + final int candidateRight = mRepositionTaskBounds.right + (int) deltaX; + mRepositionTaskBounds.right = (candidateRight < stableBounds.right) + ? candidateRight : oldRight; } if ((mCtrlType & CTRL_TYPE_TOP) != 0) { - mRepositionTaskBounds.top += deltaY; + final int candidateTop = mRepositionTaskBounds.top + (int) deltaY; + mRepositionTaskBounds.top = (candidateTop > stableBounds.top) + ? candidateTop : oldTop; } if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) { - mRepositionTaskBounds.bottom += deltaY; + final int candidateBottom = mRepositionTaskBounds.bottom + (int) deltaY; + mRepositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom) + ? candidateBottom : oldBottom; } if (mCtrlType == CTRL_TYPE_UNDEFINED) { mRepositionTaskBounds.offset((int) deltaX, (int) deltaY); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt index 8f66f4e7e47b..94c064bda763 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt @@ -5,13 +5,16 @@ import android.app.WindowConfiguration import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner +import android.view.Display import android.window.WindowContainerToken +import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING import androidx.test.filters.SmallTest import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_BOTTOM import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED @@ -19,10 +22,11 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any import org.mockito.Mockito.argThat import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations /** @@ -51,6 +55,8 @@ class TaskPositionerTest : ShellTestCase() { private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockDisplayLayout: DisplayLayout + @Mock + private lateinit var mockDisplay: Display private lateinit var taskPositioner: TaskPositioner @@ -68,6 +74,9 @@ class TaskPositionerTest : ShellTestCase() { `when`(taskToken.asBinder()).thenReturn(taskBinder) `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) + `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(STABLE_BOUNDS) + } mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = TASK_ID @@ -78,6 +87,8 @@ class TaskPositionerTest : ShellTestCase() { displayId = DISPLAY_ID configuration.windowConfiguration.bounds = STARTING_BOUNDS } + mockWindowDecoration.mDisplay = mockDisplay + `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID } } @Test @@ -451,6 +462,72 @@ class TaskPositionerTest : ShellTestCase() { }) } + fun testDragResize_toDisallowedBounds_freezesAtLimit() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.bottom.toFloat() + ) + + // Resize the task by 10px to the right and bottom, a valid destination + val newBounds = Rect( + STARTING_BOUNDS.left, + STARTING_BOUNDS.top, + STARTING_BOUNDS.right + 10, + STARTING_BOUNDS.bottom + 10) + taskPositioner.onDragPositioningMove( + newBounds.right.toFloat(), + newBounds.bottom.toFloat() + ) + + // Resize the task by another 10px to the right (allowed) and to just in the disallowed + // area of the Y coordinate. + val newBounds2 = Rect( + newBounds.left, + newBounds.top, + newBounds.right + 10, + DISALLOWED_RESIZE_AREA.top + ) + taskPositioner.onDragPositioningMove( + newBounds2.right.toFloat(), + newBounds2.bottom.toFloat() + ) + + taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat()) + + // The first resize falls in the allowed area, verify there's a change for it. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && change.ofBounds(newBounds) + } + }) + // The second resize falls in the disallowed area, verify there's no change for it. + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && change.ofBounds(newBounds2) + } + }) + // Instead, there should be a change for its allowed portion (the X movement) with the Y + // staying frozen in the last valid resize position. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && change.ofBounds( + Rect( + newBounds2.left, + newBounds2.top, + newBounds2.right, + newBounds.bottom // Stayed at the first resize destination. + ) + ) + } + }) + } + + private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean { + return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) && + bounds == configuration.windowConfiguration.bounds + } + companion object { private const val TASK_ID = 5 private const val MIN_WIDTH = 10 @@ -458,6 +535,19 @@ class TaskPositionerTest : ShellTestCase() { private const val DENSITY_DPI = 20 private const val DEFAULT_MIN = 40 private const val DISPLAY_ID = 1 + private const val NAVBAR_HEIGHT = 50 + private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) private val STARTING_BOUNDS = Rect(0, 0, 100, 100) + private val DISALLOWED_RESIZE_AREA = Rect( + DISPLAY_BOUNDS.left, + DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT, + DISPLAY_BOUNDS.right, + DISPLAY_BOUNDS.bottom) + private val STABLE_BOUNDS = Rect( + DISPLAY_BOUNDS.left, + DISPLAY_BOUNDS.top, + DISPLAY_BOUNDS.right, + DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT + ) } } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 4da4c7fc56de..f9b6ce0a199c 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -9564,7 +9564,10 @@ public class AudioManager { * A stream type is considered independent when the volume changes of that type do not * affect any other independent volume control stream type. * An independent stream type is its own alias when using {@link #getStreamTypeAlias(int)}. - * @return list of independent stream types. + * @return list of independent stream types, where each value can be one of + * {@link #STREAM_VOICE_CALL}, {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, + * {@link #STREAM_MUSIC}, {@link #STREAM_ALARM}, {@link #STREAM_NOTIFICATION}, + * {@link #STREAM_DTMF} and {@link #STREAM_ACCESSIBILITY}. */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index c52cd59311c5..0f8a00a3c7d9 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -55,7 +55,7 @@ oneway interface ITvInputClient { void onCueingMessageAvailability(boolean available, int seq); void onTimeShiftMode(int mode, int seq); void onAvailableSpeeds(in float[] speeds, int seq); - void onTvMessage(in String type, in Bundle data, int seq); + void onTvMessage(int type, in Bundle data, int seq); void onTuned(in Uri channelUri, int seq); // For the recording session diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 113c85802fa2..14c020e94c15 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -116,10 +116,10 @@ interface ITvInputManager { // For ad request void requestAd(in IBinder sessionToken, in AdRequest request, int userId); - void notifyAdBuffer(in IBinder sessionToken, in AdBuffer buffer, int userId); + void notifyAdBufferReady(in IBinder sessionToken, in AdBuffer buffer, int userId); // For TV Message - void notifyTvMessage(in IBinder sessionToken, in String type, in Bundle data, int userId); + void notifyTvMessage(in IBinder sessionToken, int type, in Bundle data, int userId); // For TV input hardware binding List<TvInputHardwareInfo> getHardwareList(); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 165a9ddc26e9..a7bd8d313ac3 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -75,8 +75,8 @@ oneway interface ITvInputSession { // For ad request void requestAd(in AdRequest request); - void notifyAdBuffer(in AdBuffer buffer); + void notifyAdBufferReady(in AdBuffer buffer); // For TV messages - void notifyTvMessage(in String type, in Bundle data); + void notifyTvMessage(int type, in Bundle data); } diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index 449c2d67067e..a52e9a572dca 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -66,5 +66,5 @@ oneway interface ITvInputSessionCallback { void onAdBufferConsumed(in AdBuffer buffer); // For messages sent from the TV input - void onTvMessage(in String type, in Bundle data); + void onTvMessage(int type, in Bundle data); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index 8389706b501c..7946baee200a 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -267,7 +267,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } case DO_SET_TV_MESSAGE_ENABLED: { SomeArgs args = (SomeArgs) msg.obj; - mTvInputSessionImpl.setTvMessageEnabled((String) args.arg1, (Boolean) args.arg2); + mTvInputSessionImpl.setTvMessageEnabled((Integer) args.arg1, (Boolean) args.arg2); break; } case DO_REQUEST_AD: { @@ -275,7 +275,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand break; } case DO_NOTIFY_AD_BUFFER: { - mTvInputSessionImpl.notifyAdBuffer((AdBuffer) msg.obj); + mTvInputSessionImpl.notifyAdBufferReady((AdBuffer) msg.obj); break; } case DO_NOTIFY_TV_MESSAGE: { @@ -465,12 +465,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } @Override - public void notifyAdBuffer(AdBuffer buffer) { + public void notifyAdBufferReady(AdBuffer buffer) { mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER, buffer)); } @Override - public void notifyTvMessage(String type, Bundle data) { + public void notifyTvMessage(int type, Bundle data) { mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data)); } diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java index fb4e99cb1098..c4fc26ef1932 100644 --- a/media/java/android/media/tv/TableResponse.java +++ b/media/java/android/media/tv/TableResponse.java @@ -64,7 +64,10 @@ public final class TableResponse extends BroadcastInfoResponse implements Parcel * @param tableUri The URI of the table in the database. * @param version The version number of requested table. * @param size The Size number of table in bytes. + * + * @deprecated use {@link Builder} instead. */ + @Deprecated public TableResponse(int requestId, int sequence, @ResponseResult int responseResult, @Nullable Uri tableUri, int version, int size) { super(RESPONSE_TYPE, requestId, sequence, responseResult); @@ -76,30 +79,7 @@ public final class TableResponse extends BroadcastInfoResponse implements Parcel } /** - * Constructs a TableResponse with a table URI. - * - * @param requestId The ID is used to associate the response with the request. - * @param sequence The sequence number which indicates the order of related responses. - * @param responseResult The result for the response. It's one of {@link #RESPONSE_RESULT_OK}, - * {@link #RESPONSE_RESULT_CANCEL}, {@link #RESPONSE_RESULT_ERROR}. - * @param tableByteArray The byte array which stores the table in bytes. The structure and - * syntax of the table depends on the table name in - * {@link TableRequest#getTableName()} and the corresponding standard. - * @param version The version number of requested table. - * @param size The Size number of table in bytes. - */ - public TableResponse(int requestId, int sequence, @ResponseResult int responseResult, - @NonNull byte[] tableByteArray, int version, int size) { - super(RESPONSE_TYPE, requestId, sequence, responseResult); - mVersion = version; - mSize = size; - mTableUri = null; - mTableByteArray = tableByteArray; - mTableSharedMemory = null; - } - - /** - * Constructs a TableResponse with a table URI. + * Constructs a TableResponse. * * @param requestId The ID is used to associate the response with the request. * @param sequence The sequence number which indicates the order of related responses. @@ -112,17 +92,128 @@ public final class TableResponse extends BroadcastInfoResponse implements Parcel * {@link TableRequest#getTableName()} and the corresponding standard. * @param version The version number of requested table. * @param size The Size number of table in bytes. + * @param tableUri The URI of the table in the database. + * @param tableByteArray The byte array which stores the table in bytes. The structure and + * syntax of the table depends on the table name in + * @param tableSharedMemory The shared memory which stores the table. The table size can be + * large so using a shared memory optimizes the data + * communication between the table data source and the receiver. The + * structure syntax of the table depends on the table name in + * {@link TableRequest#getTableName()} and the corresponding standard. */ - public TableResponse(int requestId, int sequence, @ResponseResult int responseResult, - @NonNull SharedMemory tableSharedMemory, int version, int size) { + private TableResponse(int requestId, int sequence, @ResponseResult int responseResult, + int version, int size, Uri tableUri, byte[] tableByteArray, + SharedMemory tableSharedMemory) { super(RESPONSE_TYPE, requestId, sequence, responseResult); mVersion = version; mSize = size; - mTableUri = null; - mTableByteArray = null; + mTableUri = tableUri; + mTableByteArray = tableByteArray; mTableSharedMemory = tableSharedMemory; } + /** + * Builder for {@link TableResponse}. + */ + public static final class Builder { + private final int mRequestId; + private final int mSequence; + @ResponseResult + private final int mResponseResult; + private final int mVersion; + private final int mSize; + private Uri mTableUri; + private byte[] mTableByteArray; + private SharedMemory mTableSharedMemory; + + /** + * Constructs a Builder object of {@link TableResponse}. + * + * @param requestId The ID is used to associate the response with the request. + * @param sequence The sequence number which indicates the order of related responses. + * @param responseResult The result for the response. It's one of + * {@link #RESPONSE_RESULT_OK}, {@link #RESPONSE_RESULT_CANCEL}, + * {@link #RESPONSE_RESULT_ERROR}. + * @param version The version number of requested table. + * @param size The Size number of table in bytes. + */ + public Builder(int requestId, int sequence, @ResponseResult int responseResult, int version, + int size) { + mRequestId = requestId; + mSequence = sequence; + mResponseResult = responseResult; + mVersion = version; + mSize = size; + } + + /** + * Sets table URI. + * + * <p>For a single builder instance, at most one of table URI, table byte array, and table + * shared memory can be set. If more than one are set, only the last call takes precedence + * and others are reset to {@code null}. + * + * @param uri The URI of the table. + */ + @NonNull + public Builder setTableUri(@NonNull Uri uri) { + mTableUri = uri; + mTableByteArray = null; + mTableSharedMemory = null; + return this; + } + + /** + * Sets table byte array. + * + * <p>For a single builder instance, at most one of table URI, table byte array, and table + * shared memory can be set. If more than one are set, only the last call takes precedence + * and others are reset to {@code null}. + * + * @param bytes The byte array which stores the table in bytes. The structure and + * syntax of the table depends on the table name in + * {@link TableRequest#getTableName()} and the corresponding standard. + */ + @NonNull + public Builder setTableByteArray(@NonNull byte[] bytes) { + mTableByteArray = bytes; + mTableUri = null; + mTableSharedMemory = null; + return this; + } + + + /** + * Sets table shared memory. + * + * <p>For a single builder instance, at most one of table URI, table byte array, and table + * shared memory can be set. If more than one are set, only the last call takes precedence + * and others are reset to {@code null}. + * + * @param sharedMemory The shared memory which stores the table. The table size can be + * large so using a shared memory optimizes the data + * communication between the table data source and the receiver. The + * structure syntax of the table depends on the table name in + * {@link TableRequest#getTableName()} and the corresponding standard. + */ + @NonNull + public Builder setTableSharedMemory(@NonNull SharedMemory sharedMemory) { + mTableSharedMemory = sharedMemory; + mTableUri = null; + mTableByteArray = null; + return this; + } + + /** + * Builds a {@link TableResponse} object. + */ + @NonNull + public TableResponse build() { + return new TableResponse(mRequestId, mSequence, mResponseResult, mVersion, mSize, + mTableUri, mTableByteArray, mTableSharedMemory); + } + } + TableResponse(Parcel source) { super(RESPONSE_TYPE, source); String uriString = source.readString(); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 55a753f663c8..7d6a8b4c2e97 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -22,7 +22,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -134,14 +133,17 @@ public final class TvInputManager { public @interface VideoUnavailableReason {} /** Indicates that this TV message contains watermarking data */ - public static final String TV_MESSAGE_TYPE_WATERMARK = "Watermark"; + public static final int TV_MESSAGE_TYPE_WATERMARK = 1; /** Indicates that this TV message contains Closed Captioning data */ - public static final String TV_MESSAGE_TYPE_CLOSED_CAPTION = "CC"; + public static final int TV_MESSAGE_TYPE_CLOSED_CAPTION = 2; + + /** Indicates that this TV message contains other data */ + public static final int TV_MESSAGE_TYPE_OTHER = 1000; /** @hide */ @Retention(RetentionPolicy.SOURCE) - @StringDef({TV_MESSAGE_TYPE_WATERMARK, TV_MESSAGE_TYPE_CLOSED_CAPTION}) + @IntDef({TV_MESSAGE_TYPE_WATERMARK, TV_MESSAGE_TYPE_CLOSED_CAPTION, TV_MESSAGE_TYPE_OTHER}) public @interface TvMessageType {} /** @@ -802,7 +804,7 @@ public final class TvInputManager { * @param type The type of message received, such as {@link #TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message */ - public void onTvMessage(Session session, @TvInputManager.TvMessageType String type, + public void onTvMessage(Session session, @TvInputManager.TvMessageType int type, Bundle data) { } @@ -1081,7 +1083,7 @@ public final class TvInputManager { }); } - void postTvMessage(String type, Bundle data) { + void postTvMessage(int type, Bundle data) { mHandler.post(new Runnable() { @Override public void run() { @@ -1620,7 +1622,7 @@ public final class TvInputManager { } @Override - public void onTvMessage(String type, Bundle data, int seq) { + public void onTvMessage(int type, Bundle data, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { @@ -3230,7 +3232,7 @@ public final class TvInputManager { /** * Sends TV messages to the service for testing purposes */ - public void notifyTvMessage(@NonNull @TvMessageType String type, @NonNull Bundle data) { + public void notifyTvMessage(@NonNull @TvMessageType int type, @NonNull Bundle data) { try { mService.notifyTvMessage(mToken, type, data, mUserId); } catch (RemoteException e) { @@ -3642,13 +3644,13 @@ public final class TvInputManager { /** * Notifies when the advertisement buffer is filled and ready to be read. */ - public void notifyAdBuffer(AdBuffer buffer) { + public void notifyAdBufferReady(AdBuffer buffer) { if (mToken == null) { Log.w(TAG, "The session has been already released"); return; } try { - mService.notifyAdBuffer(mToken, buffer, mUserId); + mService.notifyAdBufferReady(mToken, buffer, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 9f40d704f7ac..3c6ed91d2100 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1032,7 +1032,7 @@ public abstract class TvInputService extends Service { * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The data sent with the message. */ - public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType String type, + public void notifyTvMessage(@TvInputManager.TvMessageType int type, @NonNull Bundle data) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @@ -1351,7 +1351,7 @@ public abstract class TvInputService extends Service { * * @param buffer The {@link AdBuffer} that became ready for playback. */ - public void onAdBuffer(@NonNull AdBuffer buffer) { + public void onAdBufferReady(@NonNull AdBuffer buffer) { } /** @@ -1486,11 +1486,12 @@ public abstract class TvInputService extends Service { /** * Called when the application enables or disables the detection of the specified message * type. - * @param type The {@link TvInputManager.TvMessageType} of message that was sent. + * @param type The type of message received, such as + * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param enabled {@code true} if TV message detection is enabled, * {@code false} otherwise. */ - public void onSetTvMessageEnabled(@NonNull @TvInputManager.TvMessageType String type, + public void onSetTvMessageEnabled(@TvInputManager.TvMessageType int type, boolean enabled) { } @@ -1856,9 +1857,9 @@ public abstract class TvInputService extends Service { } /** - * Calls {@link #onSetTvMessageEnabled(String, boolean)}. + * Calls {@link #onSetTvMessageEnabled(int, boolean)}. */ - void setTvMessageEnabled(String type, boolean enabled) { + void setTvMessageEnabled(int type, boolean enabled) { onSetTvMessageEnabled(type, enabled); } @@ -2050,8 +2051,8 @@ public abstract class TvInputService extends Service { onRequestAd(request); } - void notifyAdBuffer(AdBuffer buffer) { - onAdBuffer(buffer); + void notifyAdBufferReady(AdBuffer buffer) { + onAdBufferReady(buffer); } void onTvMessageReceived(String type, Bundle data) { diff --git a/media/java/android/media/tv/TvRecordingInfo.java b/media/java/android/media/tv/TvRecordingInfo.java index 60ceb8394159..59915fc078c0 100644 --- a/media/java/android/media/tv/TvRecordingInfo.java +++ b/media/java/android/media/tv/TvRecordingInfo.java @@ -131,7 +131,6 @@ public final class TvRecordingInfo implements Parcelable { * cause the recording to start later than the specified time. This should cause the actual * duration of the recording to decrease. */ - @NonNull public long getStartPaddingMillis() { return mStartPaddingMillis; } @@ -144,7 +143,6 @@ public final class TvRecordingInfo implements Parcelable { * cause the recording to end earlier than the specified time. This should cause the actual * duration of the recording to decrease. */ - @NonNull public long getEndPaddingMillis() { return mEndPaddingMillis; } @@ -176,7 +174,6 @@ public final class TvRecordingInfo implements Parcelable { * https://www.oipf.tv/docs/OIPF-T1-R2_Specification-Volume-5-Declarative-Application-Environment-v2_3-2014-01-24.pdf * ">Open IPTV Forum Release 2 Specification</a>. It is described in Volume 5, section 7.10.1.1. */ - @NonNull @DaysOfWeek public int getRepeatDays() { return mRepeatDays; @@ -228,7 +225,6 @@ public final class TvRecordingInfo implements Parcelable { * Returns the scheduled start time of the recording in milliseconds since the epoch. */ @IntRange(from = 0) - @NonNull public long getScheduledStartTimeMillis() { return mScheduledStartTimeMillis; } @@ -237,7 +233,6 @@ public final class TvRecordingInfo implements Parcelable { * Returns the scheduled duration of the recording in milliseconds since the epoch. */ @IntRange(from = 0) - @NonNull public long getScheduledDurationMillis() { return mScheduledDurationMillis; } @@ -292,7 +287,6 @@ public final class TvRecordingInfo implements Parcelable { * <p> Returns -1 for recordings that have not yet started. */ @IntRange(from = -1) - @NonNull public long getRecordingStartTimeMillis() { return mRecordingStartTimeMillis; } @@ -306,7 +300,6 @@ public final class TvRecordingInfo implements Parcelable { * <p> Returns -1 for recordings that have not yet started. */ @IntRange(from = -1) - @NonNull public long getRecordingDurationMillis() { return mRecordingDurationMillis; } diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 5aeed1fbbaf5..19a2e5d8d157 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -648,7 +648,7 @@ public class TvView extends ViewGroup { * @hide */ @TestApi - public void notifyTvMessage(@TvInputManager.TvMessageType @NonNull String type, + public void notifyTvMessage(@TvInputManager.TvMessageType int type, @NonNull Bundle data) { if (mSession != null) { mSession.notifyTvMessage(type, data); @@ -738,11 +738,12 @@ public class TvView extends ViewGroup { /** * Enables or disables TV message detection in the stream of the bound TV input. * - * @param type The type of {@link android.media.tv.TvInputManager.TvMessageType} + * @param type The type of message received, such as + * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param enabled {@code true} if you want to enable TV message detection * {@code false} otherwise. */ - public void setTvMessageEnabled(@NonNull @TvInputManager.TvMessageType String type, + public void setTvMessageEnabled(@TvInputManager.TvMessageType int type, boolean enabled) { } @@ -1251,11 +1252,12 @@ public class TvView extends ViewGroup { * This is called when a new TV Message has been received. * * @param inputId The ID of the TV input bound to this view. - * @param type The type of {@link android.media.tv.TvInputManager.TvMessageType} + * @param type The type of message received, such as + * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message */ public void onTvMessage(@NonNull String inputId, - @NonNull @TvInputManager.TvMessageType String type, @NonNull Bundle data) { + @TvInputManager.TvMessageType int type, @NonNull Bundle data) { } } @@ -1670,7 +1672,7 @@ public class TvView extends ViewGroup { } @Override - public void onTvMessage(Session session, String type, Bundle data) { + public void onTvMessage(Session session, int type, Bundle data) { if (DEBUG) { Log.d(TAG, "onTvMessage(type=" + type + ", data=" + data + ")"); } diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl index 36954ad00f5f..77391841c6fe 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl @@ -39,7 +39,7 @@ oneway interface ITvInteractiveAppClient { void onSessionStateChanged(int state, int err, int seq); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq); void onTeletextAppStateChanged(int state, int seq); - void onAdBuffer(in AdBuffer buffer, int seq); + void onAdBufferReady(in AdBuffer buffer, int seq); void onCommandRequest(in String cmdType, in Bundle parameters, int seq); void onTimeShiftCommandRequest(in String cmdType, in Bundle parameters, int seq); void onSetVideoBounds(in Rect rect, int seq); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl index 89847a73ccde..41cbe4ae02d0 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl @@ -94,7 +94,7 @@ interface ITvInteractiveAppManager { void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, String requestId, int userId); void notifyRecordingStopped(in IBinder sessionToken, in String recordingId, int userId); - void notifyTvMessage(in IBinder sessionToken, in String type, in Bundle data, int userId); + void notifyTvMessage(in IBinder sessionToken, in int type, in Bundle data, int userId); void setSurface(in IBinder sessionToken, in Surface surface, int userId); void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height, int userId); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl index f17d1b73994e..052bc3d5adce 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl @@ -72,7 +72,7 @@ oneway interface ITvInteractiveAppSession { void notifySignalStrength(int strength); void notifyRecordingStarted(in String recordingId, in String requestId); void notifyRecordingStopped(in String recordingId); - void notifyTvMessage(in String type, in Bundle data); + void notifyTvMessage(int type, in Bundle data); void setSurface(in Surface surface); void dispatchSurfaceChanged(int format, int width, int height); void notifyBroadcastInfoResponse(in BroadcastInfoResponse response); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl index 7db860489e8a..9e43e79144fd 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl @@ -38,7 +38,7 @@ oneway interface ITvInteractiveAppSessionCallback { void onSessionStateChanged(int state, int err); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId); void onTeletextAppStateChanged(int state); - void onAdBuffer(in AdBuffer buffer); + void onAdBufferReady(in AdBuffer buffer); void onCommandRequest(in String cmdType, in Bundle parameters); void onTimeShiftCommandRequest(in String cmdType, in Bundle parameters); void onSetVideoBounds(in Rect rect); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java index 6eed4832847f..d3f598a7c009 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java @@ -243,7 +243,7 @@ public class ITvInteractiveAppSessionWrapper } case DO_NOTIFY_TV_MESSAGE: { SomeArgs args = (SomeArgs) msg.obj; - mSessionImpl.notifyTvMessage((String) args.arg1, (Bundle) args.arg2); + mSessionImpl.notifyTvMessage((Integer) args.arg1, (Bundle) args.arg2); args.recycle(); break; } @@ -520,7 +520,7 @@ public class ITvInteractiveAppSessionWrapper } @Override - public void notifyTvMessage(String type, Bundle data) { + public void notifyTvMessage(int type, Bundle data) { mCaller.executeOrSendMessage( mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data)); } @@ -558,7 +558,7 @@ public class ITvInteractiveAppSessionWrapper @Override public void notifyRecordingStarted(String recordingId, String requestId) { mCaller.executeOrSendMessage(mCaller.obtainMessageOO( - DO_NOTIFY_RECORDING_STARTED, recordingId, recordingId)); + DO_NOTIFY_RECORDING_STARTED, recordingId, requestId)); } @Override diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java index 3e31bce30baa..fc8fe5c30967 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -681,14 +681,14 @@ public final class TvInteractiveAppManager { } @Override - public void onAdBuffer(AdBuffer buffer, int seq) { + public void onAdBufferReady(AdBuffer buffer, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { Log.e(TAG, "Callback not found for seq " + seq); return; } - record.postAdBuffer(buffer); + record.postAdBufferReady(buffer); } } }; @@ -1751,7 +1751,7 @@ public final class TvInteractiveAppManager { /** * Notifies Interactive APP session when a new TV message is received. */ - public void notifyTvMessage(String type, Bundle data) { + public void notifyTvMessage(int type, Bundle data) { if (mToken == null) { Log.w(TAG, "The session has been already released"); return; @@ -2245,12 +2245,12 @@ public final class TvInteractiveAppManager { }); } - void postAdBuffer(AdBuffer buffer) { + void postAdBufferReady(AdBuffer buffer) { mHandler.post(new Runnable() { @Override public void run() { if (mSession.getInputSession() != null) { - mSession.getInputSession().notifyAdBuffer(buffer); + mSession.getInputSession().notifyAdBufferReady(buffer); } } }); diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 1ae82f43b9dd..8a58abf1914b 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -630,6 +630,8 @@ public abstract class TvInteractiveAppService extends Service { * {@link #requestStartRecording(String, Uri)} is called. * {@code null} if the recording is not triggered by a * {@link #requestStartRecording(String, Uri)} request. + * This ID should be created by the {@link TvInteractiveAppService} and + * can be any string. * @see #onRecordingStopped(String) */ public void onRecordingStarted(@NonNull String recordingId, @Nullable String requestId) { @@ -708,6 +710,8 @@ public abstract class TvInteractiveAppService extends Service { * @param requestId The ID of the request when * {@link #requestScheduleRecording} is called. * {@code null} if the recording is not triggered by a request. + * This ID should be created by the {@link TvInteractiveAppService} and + * can be any string. */ public void onRecordingScheduled(@NonNull String recordingId, @Nullable String requestId) { } @@ -916,7 +920,7 @@ public abstract class TvInteractiveAppService extends Service { * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message */ - public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type, + public void onTvMessage(@TvInputManager.TvMessageType int type, @NonNull Bundle data) { } @@ -1338,7 +1342,10 @@ public abstract class TvInteractiveAppService extends Service { * @param requestId The ID of this request which is used to match the corresponding * response. The request ID in * {@link #onRecordingStarted(String, String)} for this request is the - * same as the ID sent here. + * same as the ID sent here. This should be defined by the + * {@link TvInteractiveAppService} and can be any string. + * Should this API be called with the same requestId twice, both + * requests should be handled regardless by the TV application. * @param programUri The URI for the TV program to record. * @see android.media.tv.TvRecordingClient#startRecording(Uri) */ @@ -1390,7 +1397,10 @@ public abstract class TvInteractiveAppService extends Service { * @param requestId The ID of this request which is used to match the corresponding * response. The request ID in * {@link #onRecordingScheduled(String, String)} for this request is the - * same as the ID sent here. + * same as the ID sent here. This should be defined by the + * {@link TvInteractiveAppService} and can be any string. + * Should this API be called with the same requestId twice, both requests + * should be handled regardless by the TV application. * @param inputId The ID of the TV input for the given channel. * @param channelUri The URI of a channel to be recorded. * @param programUri The URI of the TV program to be recorded. @@ -1424,7 +1434,10 @@ public abstract class TvInteractiveAppService extends Service { * @param requestId The ID of this request which is used to match the corresponding * response. The request ID in * {@link #onRecordingScheduled(String, String)} for this request is the - * same as the ID sent here. + * same as the ID sent here. This should be defined by the + * {@link TvInteractiveAppService} and can be any string. Should this API + * be called with the same requestId twice, both requests should be handled + * regardless by the TV application. * @param inputId The ID of the TV input for the given channel. * @param channelUri The URI of a channel to be recorded. * @param startTime The start time of the recording in milliseconds since epoch. @@ -1507,8 +1520,7 @@ public abstract class TvInteractiveAppService extends Service { * @param type The type of recording to retrieve. */ @CallSuper - public void requestTvRecordingInfoList(@NonNull @TvRecordingInfo.TvRecordingListType - int type) { + public void requestTvRecordingInfoList(@TvRecordingInfo.TvRecordingListType int type) { executeOrPostRunnableOnMainThread(() -> { try { if (DEBUG) { @@ -1753,7 +1765,7 @@ public abstract class TvInteractiveAppService extends Service { onAdResponse(response); } - void notifyTvMessage(String type, Bundle data) { + void notifyTvMessage(int type, Bundle data) { if (DEBUG) { Log.d(TAG, "notifyTvMessage (type=" + type + ", data= " + data + ")"); } @@ -1942,7 +1954,7 @@ public abstract class TvInteractiveAppService extends Service { * @param buffer The {@link AdBuffer} to be received */ @CallSuper - public void notifyAdBuffer(@NonNull AdBuffer buffer) { + public void notifyAdBufferReady(@NonNull AdBuffer buffer) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -1950,10 +1962,10 @@ public abstract class TvInteractiveAppService extends Service { try { if (DEBUG) { Log.d(TAG, - "notifyAdBuffer(buffer=" + buffer + ")"); + "notifyAdBufferReady(buffer=" + buffer + ")"); } if (mSessionCallback != null) { - mSessionCallback.onAdBuffer(buffer); + mSessionCallback.onAdBufferReady(buffer); } } catch (RemoteException e) { Log.w(TAG, "error in notifyAdBuffer", e); diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java index 0a8de127b6c9..1a0319bddc2d 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -675,6 +675,8 @@ public class TvInteractiveAppView extends ViewGroup { * @param requestId The ID of the request when * {@link TvInteractiveAppService.Session#requestStartRecording(String, Uri)} * is called. {@code null} if the recording is not triggered by a request. + * This ID should be created by the {@link TvInteractiveAppService} and + * can be any string. * @see TvInteractiveAppView#notifyRecordingStopped(String) */ public void notifyRecordingStarted(@NonNull String recordingId, @Nullable String requestId) { @@ -682,7 +684,7 @@ public class TvInteractiveAppView extends ViewGroup { Log.d(TAG, "notifyRecordingStarted"); } if (mSession != null) { - mSession.notifyRecordingStarted(recordingId, recordingId); + mSession.notifyRecordingStarted(recordingId, requestId); } } @@ -922,6 +924,8 @@ public class TvInteractiveAppView extends ViewGroup { * @param requestId The ID of the request when * {@link TvInteractiveAppService.Session#requestScheduleRecording} is called. * {@code null} if the recording is not triggered by a request. + * This ID should be created by the {@link TvInteractiveAppService} and + * can be any string. */ public void notifyRecordingScheduled( @NonNull String recordingId, @Nullable String requestId) { @@ -942,7 +946,7 @@ public class TvInteractiveAppView extends ViewGroup { * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message */ - public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType String type, + public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type, @NonNull Bundle data) { if (DEBUG) { Log.d(TAG, "notifyTvMessage type=" + type @@ -1214,14 +1218,17 @@ public class TvInteractiveAppView extends ViewGroup { } /** - * This is called when {@link TvInteractiveAppService.Session#requestStartRecording(Uri)} - * is called. + * This is called when + * {@link TvInteractiveAppService.Session#requestStartRecording(String, Uri)} is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param requestId The ID of this request which is used to match the corresponding * response. The request ID in - * {@link #notifyRecordingStarted(String, String)} - * for this request should be the same as the ID received here. + * {@link #notifyRecordingStarted(String, String)} for this request is the + * same as the ID sent here. This should be defined by the + * TIAS and can be any string. Should this API be called with the + * same requestId twice, both requests should be handled regardless + * by the TV application. * @param programUri The URI of the program to record * */ @@ -1252,8 +1259,11 @@ public class TvInteractiveAppView extends ViewGroup { * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param requestId The ID of this request which is used to match the corresponding * response. The request ID in - * {@link #notifyRecordingScheduled(String, String)} for this request - * should be the same as the ID received here. + * {@link #notifyRecordingScheduled(String, String)} for this request is + * the same as the ID sent here. This should be defined by the + * TIAS and can be any string. Should this API be called with the + * same requestId twice, both requests should be handled regardless + * by the TV application. * @param inputId The ID of the TV input for the given channel. * @param channelUri The URI of a channel to be recorded. * @param programUri The URI of the TV program to be recorded. @@ -1276,8 +1286,11 @@ public class TvInteractiveAppView extends ViewGroup { * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param requestId The ID of this request which is used to match the corresponding * response. The request ID in - * {@link #notifyRecordingScheduled(String, String)} for this request - * should be the same as the ID received here. + * {@link #notifyRecordingScheduled(String, String)} for this request is + * the same as the ID sent here. This should be defined by the + * TIAS and can be any string. Should this API be called with the + * same requestId twice, both requests should be handled regardless + * by the TV application. * @param inputId The ID of the TV input for the given channel. * @param channelUri The URI of a channel to be recorded. * @param startTime The start time of the recording in milliseconds since epoch. @@ -1350,7 +1363,7 @@ public class TvInteractiveAppView extends ViewGroup { */ public void onRequestTvRecordingInfoList( @NonNull String iAppServiceId, - @NonNull @TvRecordingInfo.TvRecordingListType int type) { + @TvRecordingInfo.TvRecordingListType int type) { } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 15f5684a1b82..6fd579b02a4f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -446,6 +446,7 @@ fun MoreOptionsRowIntroCard( BodyMediumText(text = stringResource( R.string.use_provider_for_all_description, entryInfo.userProviderDisplayName)) } + item { Divider(thickness = 24.dp, color = Color.Transparent) } item { CtaButtonRow( leftButton = { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java index db6cc1a39ef1..7f3b0ff8c838 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java @@ -16,7 +16,6 @@ package com.android.providers.settings; -import android.annotation.NonNull; import android.os.Bundle; import android.provider.Settings; import android.util.ArrayMap; @@ -60,10 +59,6 @@ final class GenerationRegistry { // Maximum size of an individual backing store static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize(); - // Use an empty string to track the generation number of all non-predefined, unset settings - // The generation number is only increased when a new non-predefined setting is inserted - private static final String DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS = ""; - public GenerationRegistry(Object lock) { mLock = lock; } @@ -77,10 +72,6 @@ final class GenerationRegistry { (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG); // Only store the prefix if the mutated setting is a config final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name; - incrementGenerationInternal(key, indexMapKey); - } - - private void incrementGenerationInternal(int key, @NonNull String indexMapKey) { synchronized (mLock) { final MemoryIntArray backingStore = getBackingStoreLocked(key, /* createIfNotExist= */ false); @@ -96,8 +87,7 @@ final class GenerationRegistry { final int generation = backingStore.get(index) + 1; backingStore.set(index, generation); if (DEBUG) { - Slog.i(LOG_TAG, "Incremented generation for " - + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey) + Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey + " key:" + SettingsState.keyToString(key) + " at index:" + index); } @@ -108,18 +98,6 @@ final class GenerationRegistry { } } - // A new, non-predefined setting has been inserted, increment the tracking number for all unset - // settings - public void incrementGenerationForUnsetSettings(int key) { - final boolean isConfig = - (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG); - if (isConfig) { - // No need to track new settings for configs - return; - } - incrementGenerationInternal(key, DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS); - } - /** * Return the backing store's reference, the index and the current generation number * of a cached setting. If it was not in the backing store, first create the entry in it before @@ -146,8 +124,8 @@ final class GenerationRegistry { bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY, backingStore.get(index)); if (DEBUG) { - Slog.i(LOG_TAG, "Exported index:" + index + " for " - + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey) + Slog.i(LOG_TAG, "Exported index:" + index + + " for setting:" + indexMapKey + " key:" + SettingsState.keyToString(key)); } } catch (IOException e) { @@ -157,10 +135,6 @@ final class GenerationRegistry { } } - public void addGenerationDataForUnsetSettings(Bundle bundle, int key) { - addGenerationData(bundle, key, /* indexMapKey= */ DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS); - } - public void onUserRemoved(int userId) { final int secureKey = SettingsState.makeKey( SettingsState.SETTINGS_TYPE_SECURE, userId); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 27c8cdfe98f3..2f4ad9fd4ccc 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1173,7 +1173,7 @@ public class SettingsProvider extends ContentProvider { Slog.v(LOG_TAG, "setAllConfigSettings for prefix: " + prefix); } - enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG); + enforceDeviceConfigWritePermission(getContext(), keyValues.keySet()); final String callingPackage = resolveCallingPackage(); synchronized (mLock) { @@ -1192,7 +1192,8 @@ public class SettingsProvider extends ContentProvider { Slog.v(LOG_TAG, "setSyncDisabledModeConfig(" + syncDisabledMode + ")"); } - enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG); + enforceHasAtLeastOnePermission(Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG); synchronized (mLock) { setSyncDisabledModeConfigLocked(syncDisabledMode); @@ -1204,7 +1205,8 @@ public class SettingsProvider extends ContentProvider { Slog.v(LOG_TAG, "getSyncDisabledModeConfig"); } - enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG); + enforceHasAtLeastOnePermission(Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG); synchronized (mLock) { return getSyncDisabledModeConfigLocked(); @@ -1289,13 +1291,13 @@ public class SettingsProvider extends ContentProvider { private boolean mutateConfigSetting(String name, String value, String prefix, boolean makeDefault, int operation, int mode) { - enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG); final String callingPackage = resolveCallingPackage(); // Perform the mutation. synchronized (mLock) { switch (operation) { case MUTATION_OPERATION_INSERT: { + enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name)); return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, name, value, null, makeDefault, true, callingPackage, false, null, @@ -1303,11 +1305,14 @@ public class SettingsProvider extends ContentProvider { } case MUTATION_OPERATION_DELETE: { + enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name)); return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, name, false, null); } case MUTATION_OPERATION_RESET: { + enforceDeviceConfigWritePermission(getContext(), + getAllConfigFlags(prefix).keySet()); mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, callingPackage, mode, null, prefix); } return true; @@ -1464,7 +1469,7 @@ public class SettingsProvider extends ContentProvider { boolean makeDefault, int requestingUserId, int operation, boolean forceNotify, int mode, boolean overrideableByRestore) { // Make sure the caller can change the settings - treated as secure. - enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS); + enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS); // Resolve the userId on whose behalf the call is made. final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); @@ -1752,7 +1757,7 @@ public class SettingsProvider extends ContentProvider { boolean makeDefault, int requestingUserId, int operation, boolean forceNotify, int mode, boolean overrideableByRestore) { // Make sure the caller can change the settings. - enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS); + enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS); // Resolve the userId on whose behalf the call is made. final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); @@ -2277,11 +2282,57 @@ public class SettingsProvider extends ContentProvider { } } - private void enforceWritePermission(String permission) { - if (getContext().checkCallingOrSelfPermission(permission) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Permission denial: writing to settings requires:" - + permission); + private void enforceHasAtLeastOnePermission(String ...permissions) { + for (String permission : permissions) { + if (getContext().checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED) { + return; + } + } + throw new SecurityException("Permission denial, must have one of: " + + Arrays.toString(permissions)); + } + + /** + * Throws an exception if write permissions are not granted for {@code flags}. + * <p> + * Write permissions are granted if the calling UID is root, or the + * WRITE_DEVICE_CONFIG permission is granted, or the WRITE_DEVICE_CONFIG_ALLOWLIST + * permission is granted and each flag in {@code flags} is allowlisted in {@code + * WRITABLE_FLAG_ALLOWLIST_FLAG}. + * + * @param context the {@link Context} this is called in + * @param flags a list of flags to check, each one of the form 'namespace/flagName' + * + * @throws SecurityException if the above criteria are not met. + * @hide + */ + private void enforceDeviceConfigWritePermission( + @NonNull Context context, + @NonNull Set<String> flags) { + boolean hasAllowlistPermission = + context.checkCallingOrSelfPermission( + Manifest.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG) + == PackageManager.PERMISSION_GRANTED; + boolean hasWritePermission = + context.checkCallingOrSelfPermission( + Manifest.permission.WRITE_DEVICE_CONFIG) + == PackageManager.PERMISSION_GRANTED; + boolean isRoot = Binder.getCallingUid() == Process.ROOT_UID; + + if (isRoot || hasWritePermission) { + return; + } else if (hasAllowlistPermission) { + for (String flag : flags) { + if (!DeviceConfig.getAdbWritableFlags().contains(flag)) { + throw new SecurityException("Permission denial for flag '" + + flag + + "'; allowlist permission granted, but must add flag to the allowlist."); + } + } + } else { + throw new SecurityException("Permission denial to mutate flag, must have root, " + + "WRITE_DEVICE_CONFIG, or ALLOWLISTED_WRITE_DEVICE_CONFIG"); } } @@ -2327,15 +2378,11 @@ public class SettingsProvider extends ContentProvider { result.putString(Settings.NameValueTable.VALUE, (setting != null && !setting.isNull()) ? setting.getValue() : null); - synchronized (mLock) { - if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) { - // Individual generation tracking for predefined settings even if they are unset + if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) { + // Don't track generation for non-existent settings unless the name is predefined + synchronized (mLock) { mSettingsRegistry.mGenerationRegistry.addGenerationData(result, SettingsState.makeKey(type, userId), name); - } else { - // All non-predefined, unset settings are tracked using the same generation number - mSettingsRegistry.mGenerationRegistry.addGenerationDataForUnsetSettings(result, - SettingsState.makeKey(type, userId)); } } return result; @@ -2349,8 +2396,7 @@ public class SettingsProvider extends ContentProvider { } else if (type == SETTINGS_TYPE_SYSTEM) { return sAllSystemSettings.contains(name); } else { - // Consider all config settings predefined because they are used by system apps only - return type == SETTINGS_TYPE_CONFIG; + return false; } } @@ -2359,13 +2405,14 @@ public class SettingsProvider extends ContentProvider { Bundle result = new Bundle(); result.putSerializable(Settings.NameValueTable.VALUE, keyValues); if (trackingGeneration) { + // Track generation even if the namespace is empty because this is for system apps synchronized (mLock) { - // Track generation even if namespace is empty because this is for system apps only mSettingsRegistry.mGenerationRegistry.addGenerationData(result, - SettingsState.makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM), - prefix); + mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG, + UserHandle.USER_SYSTEM).mKey, prefix); } } + return result; } @@ -3056,15 +3103,10 @@ public class SettingsProvider extends ContentProvider { final int key = makeKey(type, userId); boolean success = false; - boolean wasUnsetNonPredefinedSetting = false; SettingsState settingsState = peekSettingsStateLocked(key); if (settingsState != null) { - if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) { - wasUnsetNonPredefinedSetting = true; - } success = settingsState.insertSettingLocked(name, value, - tag, makeDefault, forceNonSystemPackage, packageName, - overrideableByRestore); + tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore); } if (success && criticalSettings != null && criticalSettings.contains(name)) { @@ -3073,11 +3115,6 @@ public class SettingsProvider extends ContentProvider { if (forceNotify || success) { notifyForSettingsChange(key, name); - if (wasUnsetNonPredefinedSetting) { - // Increment the generation number for all non-predefined, unset settings, - // because a new non-predefined setting has been inserted - mGenerationRegistry.incrementGenerationForUnsetSettings(key); - } } if (success) { logSettingChanged(userId, name, type, CHANGE_TYPE_INSERT); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 4d8705f135af..c3888268a61d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -759,12 +759,6 @@ final class SettingsState { mPackageToMemoryUsage.put(packageName, newSize); } - public boolean hasSetting(String name) { - synchronized (mLock) { - return hasSettingLocked(name); - } - } - @GuardedBy("mLock") private boolean hasSettingLocked(String name) { return mSettings.indexOfKey(name) >= 0; diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java index 6ec8146baee0..d34fe6943153 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java @@ -151,26 +151,6 @@ public class GenerationRegistryTest { checkBundle(b, 0, 1, false); } - @Test - public void testUnsetSettings() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object()); - final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); - final String testSecureSetting = "test_secure_setting"; - Bundle b = new Bundle(); - generationRegistry.addGenerationData(b, secureKey, testSecureSetting); - checkBundle(b, 0, 1, false); - generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); - checkBundle(b, 1, 1, false); - generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); - // Test that unset settings always have the same index - checkBundle(b, 1, 1, false); - generationRegistry.incrementGenerationForUnsetSettings(secureKey); - // Test that the generation number of the unset settings have increased - generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); - checkBundle(b, 1, 2, false); - } - - private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull) throws IOException { final MemoryIntArray array = getArray(b); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index d02e5693bbf3..8cbf5f8d4c30 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -148,6 +148,8 @@ <uses-permission android:name="android.permission.LOCATION_BYPASS" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" /> <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6176c61d363a..dc24dce76d78 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -967,6 +967,7 @@ android:enabled="false" android:exported="true" android:excludeFromRecents="true" + android:resizeableActivity="false" android:theme="@android:style/Theme.NoDisplay" android:label="@string/note_task_button_label" android:icon="@drawable/ic_note_task_shortcut_widget"> @@ -981,6 +982,7 @@ android:name=".notetask.shortcut.LaunchNoteTaskActivity" android:exported="true" android:excludeFromRecents="true" + android:resizeableActivity="false" android:theme="@android:style/Theme.NoDisplay" /> <!-- endregion --> diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 6209c0ba47de..19ec61d9354a 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -137,7 +137,7 @@ object Flags { * the digits when the clock moves. */ @JvmField - val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation", teamfood = true) + val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation") /** * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository @@ -670,5 +670,5 @@ object Flags { // TODO(b/272036292): Tracking Bug @JvmField val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION = - unreleasedFlag(2602, "large_shade_granular_alpha_interpolation") + unreleasedFlag(2602, "large_shade_granular_alpha_interpolation", teamfood = true) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 911861ddde47..28cc69758308 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -64,7 +64,11 @@ constructor( .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) .collect { pair -> val (isAbleToDream, lastStartedTransition) = pair - if (isAbleToDream && lastStartedTransition.to == KeyguardState.LOCKSCREEN) { + if ( + isAbleToDream && + lastStartedTransition.to == KeyguardState.LOCKSCREEN && + lastStartedTransition.from != KeyguardState.AOD + ) { keyguardTransitionRepository.startTransition( TransitionInfo( name, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index e4351d2c412c..df70f6bd31da 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -839,6 +839,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } mFrame = null; mOrientationHandle = null; + notifyNavigationBarSurface(); } // TODO: Remove this when we update nav bar recreation @@ -1003,7 +1004,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private void notifyNavigationBarSurface() { ViewRootImpl viewRoot = mView.getViewRootImpl(); - SurfaceControl surface = viewRoot != null + SurfaceControl surface = mView.getParent() != null + && viewRoot != null && viewRoot.getSurfaceControl() != null && viewRoot.getSurfaceControl().isValid() ? viewRoot.getSurfaceControl() diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 6cd04c861155..ac22b7ce8b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -89,22 +89,16 @@ constructor( /** * Shows a note task. How the task is shown will depend on when the method is invoked. * - * If in multi-window mode, notes will open as a full screen experience. That is particularly - * important for Large screen devices. These devices may support a taskbar that let users to - * drag and drop a shortcut into multi-window mode, and notes should comply with this behaviour. - * * If the keyguard is locked, notes will open as a full screen experience. A locked device has * no contextual information which let us use the whole screen space available. * - * If not in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be - * collapsed if the notes bubble is already opened. + * If the keyguard is unlocked, notes will open as a bubble OR it will be collapsed if the notes + * bubble is already opened. * * That will let users open other apps in full screen, and take contextual notes. */ - @JvmOverloads fun showNoteTask( entryPoint: NoteTaskEntryPoint, - isInMultiWindowMode: Boolean = false, ) { if (!isEnabled) return @@ -125,13 +119,7 @@ constructor( return } - val info = - resolver.resolveInfo( - entryPoint = entryPoint, - isInMultiWindowMode = isInMultiWindowMode, - isKeyguardLocked = isKeyguardLocked, - ) - ?: return + val info = resolver.resolveInfo(entryPoint, isKeyguardLocked) ?: return infoReference.set(info) diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt index 28d76474efba..2b9f0af046ff 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt @@ -20,12 +20,11 @@ data class NoteTaskInfo( val packageName: String, val uid: Int, val entryPoint: NoteTaskEntryPoint? = null, - val isInMultiWindowMode: Boolean = false, val isKeyguardLocked: Boolean = false, ) { val launchMode: NoteTaskLaunchMode = - if (isInMultiWindowMode || isKeyguardLocked) { + if (isKeyguardLocked) { NoteTaskLaunchMode.Activity } else { NoteTaskLaunchMode.AppBubble diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt index b98a0fd75331..8ecf08192e29 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt @@ -34,7 +34,6 @@ constructor( fun resolveInfo( entryPoint: NoteTaskEntryPoint? = null, - isInMultiWindowMode: Boolean = false, isKeyguardLocked: Boolean = false, ): NoteTaskInfo? { // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking. @@ -48,7 +47,6 @@ constructor( packageName = packageName, uid = packageManager.getUidOf(packageName, user), entryPoint = entryPoint, - isInMultiWindowMode = isInMultiWindowMode, isKeyguardLocked = isKeyguardLocked, ) } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index 30660c492baa..8aed9958a7a2 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -17,6 +17,7 @@ package com.android.systemui.notetask.quickaffordance import android.content.Context +import android.hardware.input.InputSettings import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription @@ -26,36 +27,52 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState +import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEnabledKey import com.android.systemui.notetask.NoteTaskEntryPoint +import com.android.systemui.stylus.StylusManager +import dagger.Lazy import javax.inject.Inject -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map class NoteTaskQuickAffordanceConfig @Inject constructor( context: Context, - private val noteTaskController: NoteTaskController, + private val controller: NoteTaskController, + private val stylusManager: StylusManager, + private val lazyRepository: Lazy<KeyguardQuickAffordanceRepository>, @NoteTaskEnabledKey private val isEnabled: Boolean, ) : KeyguardQuickAffordanceConfig { override val key = BuiltInKeyguardQuickAffordanceKeys.CREATE_NOTE - override val pickerName: String = context.getString(R.string.note_task_button_label) + private val pickerNameResourceId = R.string.note_task_button_label - override val pickerIconResourceId = R.drawable.ic_note_task_shortcut_keyguard + override val pickerName: String = context.getString(pickerNameResourceId) - override val lockScreenState = flowOf(getLockScreenState()) + override val pickerIconResourceId = R.drawable.ic_note_task_shortcut_keyguard - // TODO(b/265949213) - private fun getLockScreenState() = - if (isEnabled) { - val icon = Icon.Resource(pickerIconResourceId, ContentDescription.Loaded(pickerName)) - LockScreenState.Visible(icon) - } else { - LockScreenState.Hidden + // Due to a dependency cycle with KeyguardQuickAffordanceRepository, we need to lazily access + // the repository when lockScreenState is accessed for the first time. + override val lockScreenState by lazy { + val stylusEverUsedFlow = createStylusEverUsedFlow(context, stylusManager) + val configSelectedFlow = createConfigSelectedFlow(lazyRepository.get(), key) + combine(configSelectedFlow, stylusEverUsedFlow) { isSelected, isStylusEverUsed -> + if (isEnabled && (isSelected || isStylusEverUsed)) { + val contentDescription = ContentDescription.Resource(pickerNameResourceId) + val icon = Icon.Resource(pickerIconResourceId, contentDescription) + LockScreenState.Visible(icon) + } else { + LockScreenState.Hidden + } } + } override suspend fun getPickerScreenState() = if (isEnabled) { @@ -65,9 +82,27 @@ constructor( } override fun onTriggered(expandable: Expandable?): OnTriggeredResult { - noteTaskController.showNoteTask( + controller.showNoteTask( entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE, ) return OnTriggeredResult.Handled } } + +private fun createStylusEverUsedFlow(context: Context, stylusManager: StylusManager) = + callbackFlow { + trySendBlocking(InputSettings.isStylusEverUsed(context)) + val callback = + object : StylusManager.StylusCallback { + override fun onStylusFirstUsed() { + trySendBlocking(InputSettings.isStylusEverUsed(context)) + } + } + stylusManager.registerCallback(callback) + awaitClose { stylusManager.unregisterCallback(callback) } + } + +private fun createConfigSelectedFlow(repository: KeyguardQuickAffordanceRepository, key: String) = + repository.selections.map { selected -> + selected.values.flatten().any { selectedConfig -> selectedConfig.key == key } + } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt index 80fce6ae288b..14b0779ab162 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt @@ -28,17 +28,12 @@ import javax.inject.Inject class LaunchNoteTaskActivity @Inject constructor( - private val noteTaskController: NoteTaskController, + private val controller: NoteTaskController, ) : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - noteTaskController.showNoteTask( - entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT, - isInMultiWindowMode = isInMultiWindowMode, - ) - + controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT) finish() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index fc3a6383cd88..5cd24e675a54 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -358,6 +358,50 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun `LOCKSCREEN to DREAMING`() = + testScope.runTest { + // GIVEN a device that is not dreaming or dozing + keyguardRepository.setDreamingWithOverlay(false) + keyguardRepository.setWakefulnessModel(startingToWake()) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + runCurrent() + + // GIVEN a prior transition has run to LOCKSCREEN + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + reset(mockTransitionRepository) + + // WHEN the device begins to dream + keyguardRepository.setDreamingWithOverlay(true) + advanceUntilIdle() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to DREAMING should occur + assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.DREAMING) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun `LOCKSCREEN to DOZING`() = testScope.runTest { // GIVEN a device with AOD not available diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 608d809e7e56..3f940d64f236 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -72,7 +72,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(context.packageManager).thenReturn(packageManager) - whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(noteTaskInfo) + whenever(resolver.resolveInfo(any(), any())).thenReturn(noteTaskInfo) whenever(userManager.isUserUnlocked).thenReturn(true) whenever( devicePolicyManager.getKeyguardDisabledFeatures( @@ -102,7 +102,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { // region onBubbleExpandChanged @Test fun onBubbleExpandChanged_expanding_logNoteTaskOpened() { - val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false) + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false) createNoteTaskController() .apply { infoReference.set(expectedInfo) } @@ -117,7 +117,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Test fun onBubbleExpandChanged_collapsing_logNoteTaskClosed() { - val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false) + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false) createNoteTaskController() .apply { infoReference.set(expectedInfo) } @@ -132,7 +132,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Test fun onBubbleExpandChanged_expandingAndKeyguardLocked_doNothing() { - val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false) + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true) createNoteTaskController() .apply { infoReference.set(expectedInfo) } @@ -146,35 +146,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Test fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_doNothing() { - val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false) - - createNoteTaskController() - .apply { infoReference.set(expectedInfo) } - .onBubbleExpandChanged( - isExpanding = false, - key = Bubble.KEY_APP_BUBBLE, - ) - - verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) - } - - @Test - fun onBubbleExpandChanged_expandingAndInMultiWindowMode_doNothing() { - val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true) - - createNoteTaskController() - .apply { infoReference.set(expectedInfo) } - .onBubbleExpandChanged( - isExpanding = true, - key = Bubble.KEY_APP_BUBBLE, - ) - - verifyZeroInteractions(context, bubbles, keyguardManager, userManager) - } - - @Test - fun onBubbleExpandChanged_notExpandingAndInMultiWindowMode_doNothing() { - val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true) + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true) createNoteTaskController() .apply { infoReference.set(expectedInfo) } @@ -215,16 +187,14 @@ internal class NoteTaskControllerTest : SysuiTestCase() { val expectedInfo = noteTaskInfo.copy( entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, - isInMultiWindowMode = false, isKeyguardLocked = true, ) whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) - whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) + whenever(resolver.resolveInfo(any(), any())).thenReturn(expectedInfo) createNoteTaskController() .showNoteTask( entryPoint = expectedInfo.entryPoint!!, - isInMultiWindowMode = expectedInfo.isInMultiWindowMode, ) val intentCaptor = argumentCaptor<Intent>() @@ -250,16 +220,14 @@ internal class NoteTaskControllerTest : SysuiTestCase() { val expectedInfo = noteTaskInfo.copy( entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, - isInMultiWindowMode = false, isKeyguardLocked = false, ) - whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) + whenever(resolver.resolveInfo(any(), any())).thenReturn(expectedInfo) whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) createNoteTaskController() .showNoteTask( entryPoint = expectedInfo.entryPoint!!, - isInMultiWindowMode = expectedInfo.isInMultiWindowMode, ) verifyZeroInteractions(context) @@ -275,49 +243,10 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - fun showNoteTask_isInMultiWindowMode_shouldStartActivityAndLogUiEvent() { - val expectedInfo = - noteTaskInfo.copy( - entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT, - isInMultiWindowMode = true, - isKeyguardLocked = false, - ) - whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) - whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) - - createNoteTaskController() - .showNoteTask( - entryPoint = expectedInfo.entryPoint!!, - isInMultiWindowMode = expectedInfo.isInMultiWindowMode, - ) - - val intentCaptor = argumentCaptor<Intent>() - val userCaptor = argumentCaptor<UserHandle>() - verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) - - (intentCaptor.value.flags and FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK - - intentCaptor.value.let { intent -> - assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE) - assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) - assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK) - assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK) - .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK) - assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT) - .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT) - assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue() - } - assertThat(userCaptor.value).isEqualTo(userTracker.userHandle) - verify(eventLogger).logNoteTaskOpened(expectedInfo) - verifyZeroInteractions(bubbles) - } - - @Test fun showNoteTask_bubblesIsNull_shouldDoNothing() { createNoteTaskController(bubbles = null) .showNoteTask( entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, - isInMultiWindowMode = false, ) verifyZeroInteractions(context, bubbles, eventLogger) @@ -325,12 +254,11 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Test fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() { - whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(null) + whenever(resolver.resolveInfo(any(), any())).thenReturn(null) createNoteTaskController() .showNoteTask( entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, - isInMultiWindowMode = false, ) verifyZeroInteractions(context, bubbles, eventLogger) @@ -341,7 +269,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController(isEnabled = false) .showNoteTask( entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, - isInMultiWindowMode = false, ) verifyZeroInteractions(context, bubbles, eventLogger) @@ -354,7 +281,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController() .showNoteTask( entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, - isInMultiWindowMode = false, ) verifyZeroInteractions(context, bubbles, eventLogger) @@ -405,11 +331,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) - createNoteTaskController() - .showNoteTask( - isInMultiWindowMode = false, - entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE - ) + createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) verifyZeroInteractions(context, bubbles, eventLogger) } @@ -425,11 +347,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) - createNoteTaskController() - .showNoteTask( - isInMultiWindowMode = false, - entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE - ) + createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) verifyZeroInteractions(context, bubbles, eventLogger) } @@ -445,11 +363,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) - createNoteTaskController() - .showNoteTask( - isInMultiWindowMode = false, - entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE - ) + createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) val intentCaptor = argumentCaptor<Intent>() verify(bubbles).showOrHideAppBubble(capture(intentCaptor)) @@ -472,11 +386,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) - createNoteTaskController() - .showNoteTask( - isInMultiWindowMode = false, - entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE - ) + createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) val intentCaptor = argumentCaptor<Intent>() verify(bubbles).showOrHideAppBubble(capture(intentCaptor)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt index 7e975b6732a5..91cd6ae5d988 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt @@ -31,39 +31,17 @@ internal class NoteTaskInfoTest : SysuiTestCase() { NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID) @Test - fun launchMode_notInMultiWindowModeAndKeyguardUnlocked_launchModeAppBubble() { - val underTest = - createNoteTaskInfo() - .copy( - isKeyguardLocked = false, - isInMultiWindowMode = false, - ) - - assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble) - } - - @Test - fun launchMode_inMultiWindowMode_launchModeActivity() { - val underTest = - createNoteTaskInfo() - .copy( - isKeyguardLocked = false, - isInMultiWindowMode = true, - ) + fun launchMode_keyguardLocked_launchModeActivity() { + val underTest = createNoteTaskInfo().copy(isKeyguardLocked = true) assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity) } @Test - fun launchMode_keyguardLocked_launchModeActivity() { - val underTest = - createNoteTaskInfo() - .copy( - isKeyguardLocked = true, - isInMultiWindowMode = false, - ) + fun launchMode_keyguardUnlocked_launchModeActivity() { + val underTest = createNoteTaskInfo().copy(isKeyguardLocked = false) - assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity) + assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble) } private companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt index e57d0d943aab..d44012f31fa5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt @@ -18,75 +18,139 @@ package com.android.systemui.notetask.quickaffordance +import android.hardware.input.InputSettings import android.test.suitebuilder.annotation.SmallTest -import androidx.test.runner.AndroidJUnit4 +import android.testing.AndroidTestingRunner +import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState +import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint +import com.android.systemui.stylus.StylusManager +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations +import org.mockito.MockitoSession +import org.mockito.quality.Strictness -/** - * Tests for [NoteTaskQuickAffordanceConfig]. - * - * Build/Install/Run: - * - atest SystemUITests:NoteTaskQuickAffordanceConfigTest - */ +/** atest SystemUITests:NoteTaskQuickAffordanceConfigTest */ @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(AndroidTestingRunner::class) internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { - @Mock lateinit var noteTaskController: NoteTaskController + @Mock lateinit var controller: NoteTaskController + @Mock lateinit var stylusManager: StylusManager + @Mock lateinit var repository: KeyguardQuickAffordanceRepository + + private lateinit var mockitoSession: MockitoSession @Before fun setUp() { - MockitoAnnotations.initMocks(this) + mockitoSession = + ExtendedMockito.mockitoSession() + .initMocks(this) + .mockStatic(InputSettings::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + + whenever(InputSettings.isStylusEverUsed(mContext)).then { true } + whenever(repository.selections).then { + val map = mapOf("" to listOf(createUnderTest())) + MutableStateFlow(map) + } } - private fun createUnderTest(isEnabled: Boolean) = + @After + fun tearDown() { + mockitoSession.finishMocking() + } + + private fun createUnderTest(isEnabled: Boolean = true): KeyguardQuickAffordanceConfig = NoteTaskQuickAffordanceConfig( context = context, - noteTaskController = noteTaskController, + controller = controller, + stylusManager = stylusManager, + lazyRepository = { repository }, isEnabled = isEnabled, ) + private fun createLockScreenStateVisible(): LockScreenState = + LockScreenState.Visible( + icon = + Icon.Resource( + res = R.drawable.ic_note_task_shortcut_keyguard, + contentDescription = + ContentDescription.Resource(R.string.note_task_button_label), + ) + ) + @Test - fun lockScreenState_isNotEnabled_shouldEmitHidden() = runTest { - val underTest = createUnderTest(isEnabled = false) + fun lockScreenState_stylusUsed_noCustomShortcutSelected_shouldEmitVisible() = runTest { + val underTest = createUnderTest() + + val actual by collectLastValue(underTest.lockScreenState) + + assertThat(actual).isEqualTo(createLockScreenStateVisible()) + } + + @Test + fun lockScreenState_noStylusEverUsed_noCustomShortcutSelected_shouldEmitVisible() = runTest { + whenever(InputSettings.isStylusEverUsed(mContext)).then { false } + val underTest = createUnderTest() - val actual = collectLastValue(underTest.lockScreenState) + val actual by collectLastValue(underTest.lockScreenState) - assertThat(actual()).isEqualTo(LockScreenState.Hidden) + assertThat(actual).isEqualTo(createLockScreenStateVisible()) } @Test - fun lockScreenState_isEnabled_shouldEmitVisible() = runTest { - val stringResult = "Notetaking" - val underTest = createUnderTest(isEnabled = true) - - val actual = collectLastValue(underTest.lockScreenState) - - val expected = - LockScreenState.Visible( - icon = - Icon.Resource( - res = R.drawable.ic_note_task_shortcut_keyguard, - contentDescription = ContentDescription.Loaded(stringResult), - ) - ) - assertThat(actual()).isEqualTo(expected) + fun lockScreenState_stylusUsed_customShortcutSelected_shouldEmitVisible() = runTest { + whenever(repository.selections).then { + val map = mapOf<String, List<KeyguardQuickAffordanceConfig>>() + MutableStateFlow(map) + } + val underTest = createUnderTest() + + val actual by collectLastValue(underTest.lockScreenState) + + assertThat(actual).isEqualTo(createLockScreenStateVisible()) + } + + @Test + fun lockScreenState_noIsStylusEverUsed_noCustomShortcutSelected_shouldEmitHidden() = runTest { + whenever(InputSettings.isStylusEverUsed(mContext)).then { false } + whenever(repository.selections).then { + val map = mapOf<String, List<KeyguardQuickAffordanceConfig>>() + MutableStateFlow(map) + } + val underTest = createUnderTest() + + val actual by collectLastValue(underTest.lockScreenState) + + assertThat(actual).isEqualTo(LockScreenState.Hidden) + } + + @Test + fun lockScreenState_isNotEnabled_shouldEmitHidden() = runTest { + val underTest = createUnderTest(isEnabled = false) + + val actual by collectLastValue(underTest.lockScreenState) + + assertThat(actual).isEqualTo(LockScreenState.Hidden) } @Test @@ -95,6 +159,6 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { underTest.onTriggered(expandable = null) - verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 24c9e0f55ab9..0b22f36958d1 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -640,6 +640,7 @@ public final class ActiveServices { mAppWidgetManagerInternal = LocalServices.getService(AppWidgetManagerInternal.class); setAllowListWhileInUsePermissionInFgs(); initSystemExemptedFgsTypePermission(); + initMediaProjectFgsTypeCustomPermission(); } private AppStateTracker getAppStateTracker() { @@ -2580,6 +2581,39 @@ public final class ActiveServices { } } + /** + * A custom permission checker for the "mediaProjection" FGS type: + * if the app has been granted the permission to start a media projection via + * the {@link android.media.project.MediaProjectionManager#createScreenCaptureIntent()}, + * it'll get the permission to start a foreground service with type "mediaProjection". + */ + private class MediaProjectionFgsTypeCustomPermission extends ForegroundServiceTypePermission { + MediaProjectionFgsTypeCustomPermission() { + super("Media projection screen capture permission"); + } + + @Override + public int checkPermission(@NonNull Context context, int callerUid, int callerPid, + @NonNull String packageName, boolean allowWhileInUse) { + return mAm.isAllowedMediaProjectionNoOpCheck(callerUid) + ? PERMISSION_GRANTED : PERMISSION_DENIED; + } + } + + /** + * Set a custom permission checker for the "mediaProjection" FGS type. + */ + private void initMediaProjectFgsTypeCustomPermission() { + final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy(); + final ForegroundServiceTypePolicyInfo policyInfo = + policy.getForegroundServiceTypePolicyInfo( + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION, + ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE); + if (policyInfo != null) { + policyInfo.setCustomPermission(new MediaProjectionFgsTypeCustomPermission()); + } + } + ServiceNotificationPolicy applyForegroundServiceNotificationLocked(Notification notification, final String tag, final int id, final String pkg, final int userId) { // By nature of the FGS API, all FGS notifications have a null tag diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 9e95e5fb0a40..569600482873 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -25,6 +25,7 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA import android.annotation.NonNull; import android.app.ActivityThread; +import android.app.ForegroundServiceTypePolicy; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -1196,6 +1197,7 @@ final class ActivityManagerConstants extends ContentObserver { updateUseTieredCachedAdj(); break; default: + updateFGSPermissionEnforcementFlagsIfNecessary(name); break; } } @@ -1951,6 +1953,11 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME); } + private void updateFGSPermissionEnforcementFlagsIfNecessary(@NonNull String name) { + ForegroundServiceTypePolicy.getDefaultPolicy() + .updatePermissionEnforcementFlagIfNecessary(name); + } + @NeverCompile // Avoid size overhead of debugging code. void dump(PrintWriter pw) { pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) " diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 37f744f67eba..fdd55a8406ce 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -38,6 +38,8 @@ import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.StopUserOnSwitch; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; +import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED; +import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_NONE; import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; @@ -178,6 +180,7 @@ import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal.BindServiceEventListener; import android.app.ActivityManagerInternal.BroadcastEventListener; import android.app.ActivityManagerInternal.ForegroundServiceStateListener; +import android.app.ActivityManagerInternal.MediaProjectionTokenEvent; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.ActivityThread; import android.app.AnrController; @@ -1508,6 +1511,17 @@ public class ActivityManagerService extends IActivityManager.Stub final AppRestrictionController mAppRestrictionController; + /** + * The collection of the MediaProjection tokens per UID, for the apps that are allowed to + * start FGS with the type "mediaProjection"; this permission is granted via the request over + * the call to {@link android.media.project.MediaProjectionManager#createScreenCaptureIntent()}. + * + * <p>Note, the "token" here is actually an instance of + * {@link android.media.projection.IMediaProjection}.</p> + */ + @GuardedBy("mMediaProjectionTokenMap") + private final SparseArray<ArraySet<IBinder>> mMediaProjectionTokenMap = new SparseArray(); + private final class AppDeathRecipient implements IBinder.DeathRecipient { final ProcessRecord mApp; final int mPid; @@ -9866,7 +9880,6 @@ public class ActivityManagerService extends IActivityManager.Stub boolean dumpNormalPriority = false; boolean dumpVisibleStacksOnly = false; boolean dumpFocusedStackOnly = false; - boolean dumpVerbose = false; int dumpDisplayId = INVALID_DISPLAY; String dumpPackage = null; int dumpUserId = UserHandle.USER_ALL; @@ -9925,8 +9938,6 @@ public class ActivityManagerService extends IActivityManager.Stub return; } dumpClient = true; - } else if ("--verbose".equals(opt)) { - dumpVerbose = true; } else if ("-h".equals(opt)) { ActivityManagerShellCommand.dumpHelp(pw, true); return; @@ -10213,8 +10224,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else { // Dumping a single activity? if (!mAtmInternal.dumpActivity(fd, pw, cmd, args, opti, dumpAll, - dumpVisibleStacksOnly, dumpFocusedStackOnly, dumpVerbose, dumpDisplayId, - dumpUserId)) { + dumpVisibleStacksOnly, dumpFocusedStackOnly, dumpDisplayId, dumpUserId)) { ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true); int res = shell.exec(this, null, fd, null, args, null, new ResultReceiver(null)); @@ -18141,7 +18151,7 @@ public class ActivityManagerService extends IActivityManager.Stub | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); final Bundle configChangedOptions = new BroadcastOptions() .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) - .setDeferUntilActive(true) + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) .toBundle(); broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, null, null, OP_NONE, configChangedOptions, false, false, MY_PID, SYSTEM_UID, @@ -18160,7 +18170,7 @@ public class ActivityManagerService extends IActivityManager.Stub PowerExemptionManager.REASON_LOCALE_CHANGED, ""); bOptions.setDeliveryGroupPolicy( BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); - bOptions.setDeferUntilActive(true); + bOptions.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), @@ -18205,7 +18215,7 @@ public class ActivityManagerService extends IActivityManager.Stub final BroadcastOptions options = new BroadcastOptions() .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) - .setDeferUntilActive(true); + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); if (reason != null) { options.setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, reason); } @@ -18741,6 +18751,12 @@ public class ActivityManagerService extends IActivityManager.Stub int uid, int pid) { ActivityManagerService.this.logFgsApiEnd(apiType, uid, pid); } + + @Override + public void notifyMediaProjectionEvent(int uid, @NonNull IBinder projectionToken, + @MediaProjectionTokenEvent int event) { + ActivityManagerService.this.notifyMediaProjectionEvent(uid, projectionToken, event); + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) { @@ -19974,4 +19990,41 @@ public class ActivityManagerService extends IActivityManager.Stub return invalidValue; } } + + private void notifyMediaProjectionEvent(int uid, @NonNull IBinder projectionToken, + @MediaProjectionTokenEvent int event) { + synchronized (mMediaProjectionTokenMap) { + final int index = mMediaProjectionTokenMap.indexOfKey(uid); + ArraySet<IBinder> tokens; + if (event == MEDIA_PROJECTION_TOKEN_EVENT_CREATED) { + if (index < 0) { + tokens = new ArraySet(); + mMediaProjectionTokenMap.put(uid, tokens); + } else { + tokens = mMediaProjectionTokenMap.valueAt(index); + } + tokens.add(projectionToken); + } else if (event == MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED && index >= 0) { + tokens = mMediaProjectionTokenMap.valueAt(index); + tokens.remove(projectionToken); + if (tokens.isEmpty()) { + mMediaProjectionTokenMap.removeAt(index); + } + } + } + } + + /** + * @return {@code true} if the MediaProjectionManagerService has created a media projection + * for the given {@code uid} because the user has granted the permission; + * it doesn't necessarily mean it has started the projection. + * + * <p>It doesn't check the {@link AppOpsManager#OP_PROJECT_MEDIA}.</p> + */ + boolean isAllowedMediaProjectionNoOpCheck(int uid) { + synchronized (mMediaProjectionTokenMap) { + final int index = mMediaProjectionTokenMap.indexOfKey(uid); + return index >= 0 && !mMediaProjectionTokenMap.valueAt(index).isEmpty(); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 01bb549630b8..809f3f7ff857 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -3957,7 +3957,6 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" --checkin: output checkin format, resetting data."); pw.println(" --C: output checkin format, not resetting data."); pw.println(" --proto: output dump in protocol buffer format."); - pw.println(" --verbose: dumps extra information."); pw.printf(" %s: dump just the DUMPABLE-related state of an activity. Use the %s " + "option to list the supported DUMPABLEs\n", Activity.DUMP_ARG_DUMP_DUMPABLE, Activity.DUMP_ARG_LIST_DUMPABLES); diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java index 37926259ffb3..9ff2cd0649d4 100644 --- a/services/core/java/com/android/server/am/DropboxRateLimiter.java +++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java @@ -31,11 +31,17 @@ public class DropboxRateLimiter { // process/eventType) further entries will be rejected until RATE_LIMIT_BUFFER_DURATION has // elapsed, after which the current count for this breakdown will be reset. private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.MINUTE_IN_MILLIS; - // The time duration after which the rate limit buffer will be cleared. - private static final long RATE_LIMIT_BUFFER_EXPIRY = 3 * RATE_LIMIT_BUFFER_DURATION; + // Indicated how many buffer durations to wait before the rate limit buffer will be cleared. + // E.g. if set to 3 will wait 3xRATE_LIMIT_BUFFER_DURATION before clearing the buffer. + private static final long RATE_LIMIT_BUFFER_EXPIRY_FACTOR = 3; // The number of entries to keep per breakdown of process/eventType. private static final int RATE_LIMIT_ALLOWED_ENTRIES = 6; + // If a process is rate limited twice in a row we consider it crash-looping and rate limit it + // more aggressively. + private static final int STRICT_RATE_LIMIT_ALLOWED_ENTRIES = 1; + private static final long STRICT_RATE_LIMIT_BUFFER_DURATION = 60 * DateUtils.MINUTE_IN_MILLIS; + @GuardedBy("mErrorClusterRecords") private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>(); private final Clock mClock; @@ -71,15 +77,27 @@ public class DropboxRateLimiter { return new RateLimitResult(false, 0); } - if (now - errRecord.getStartTime() > RATE_LIMIT_BUFFER_DURATION) { + final long timeSinceFirstError = now - errRecord.getStartTime(); + if (timeSinceFirstError > errRecord.getBufferDuration()) { final int errCount = recentlyDroppedCount(errRecord); errRecord.setStartTime(now); errRecord.setCount(1); + + // If this error happened exactly the next "rate limiting cycle" after the last + // error and the previous cycle was rate limiting then increment the successive + // rate limiting cycle counter. If a full "cycle" has passed since the last error + // then this is no longer a continuous occurrence and will be rate limited normally. + if (errCount > 0 && timeSinceFirstError < 2 * errRecord.getBufferDuration()) { + errRecord.incrementSuccessiveRateLimitCycles(); + } else { + errRecord.setSuccessiveRateLimitCycles(0); + } + return new RateLimitResult(false, errCount); } errRecord.incrementCount(); - if (errRecord.getCount() > RATE_LIMIT_ALLOWED_ENTRIES) { + if (errRecord.getCount() > errRecord.getAllowedEntries()) { return new RateLimitResult(true, recentlyDroppedCount(errRecord)); } } @@ -91,16 +109,19 @@ public class DropboxRateLimiter { * dropped. Resets every RATE_LIMIT_BUFFER_DURATION if events are still actively created or * RATE_LIMIT_BUFFER_EXPIRY if not. */ private int recentlyDroppedCount(ErrorRecord errRecord) { - if (errRecord == null || errRecord.getCount() < RATE_LIMIT_ALLOWED_ENTRIES) return 0; - return errRecord.getCount() - RATE_LIMIT_ALLOWED_ENTRIES; + if (errRecord == null || errRecord.getCount() < errRecord.getAllowedEntries()) return 0; + return errRecord.getCount() - errRecord.getAllowedEntries(); } - private void maybeRemoveExpiredRecords(long now) { - if (now - mLastMapCleanUp <= RATE_LIMIT_BUFFER_EXPIRY) return; + private void maybeRemoveExpiredRecords(long currentTime) { + if (currentTime - mLastMapCleanUp + <= RATE_LIMIT_BUFFER_EXPIRY_FACTOR * RATE_LIMIT_BUFFER_DURATION) { + return; + } for (int i = mErrorClusterRecords.size() - 1; i >= 0; i--) { - if (now - mErrorClusterRecords.valueAt(i).getStartTime() > RATE_LIMIT_BUFFER_EXPIRY) { + if (mErrorClusterRecords.valueAt(i).hasExpired(currentTime)) { Counter.logIncrement( "stability_errors.value_dropbox_buffer_expired_count", mErrorClusterRecords.valueAt(i).getCount()); @@ -108,7 +129,7 @@ public class DropboxRateLimiter { } } - mLastMapCleanUp = now; + mLastMapCleanUp = currentTime; } /** Resets the rate limiter memory. */ @@ -153,10 +174,12 @@ public class DropboxRateLimiter { private class ErrorRecord { long mStartTime; int mCount; + int mSuccessiveRateLimitCycles; ErrorRecord(long startTime, int count) { mStartTime = startTime; mCount = count; + mSuccessiveRateLimitCycles = 0; } public void setStartTime(long startTime) { @@ -171,6 +194,14 @@ public class DropboxRateLimiter { mCount++; } + public void setSuccessiveRateLimitCycles(int successiveRateLimitCycles) { + mSuccessiveRateLimitCycles = successiveRateLimitCycles; + } + + public void incrementSuccessiveRateLimitCycles() { + mSuccessiveRateLimitCycles++; + } + public long getStartTime() { return mStartTime; } @@ -178,6 +209,27 @@ public class DropboxRateLimiter { public int getCount() { return mCount; } + + public int getSuccessiveRateLimitCycles() { + return mSuccessiveRateLimitCycles; + } + + public boolean isRepeated() { + return mSuccessiveRateLimitCycles >= 2; + } + + public int getAllowedEntries() { + return isRepeated() ? STRICT_RATE_LIMIT_ALLOWED_ENTRIES : RATE_LIMIT_ALLOWED_ENTRIES; + } + + public long getBufferDuration() { + return isRepeated() ? STRICT_RATE_LIMIT_BUFFER_DURATION : RATE_LIMIT_BUFFER_DURATION; + } + + public boolean hasExpired(long currentTime) { + long bufferExpiry = RATE_LIMIT_BUFFER_EXPIRY_FACTOR * getBufferDuration(); + return currentTime - mStartTime > bufferExpiry; + } } private static class DefaultClock implements Clock { diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java index dd06464c4699..a028ae16da2f 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java @@ -32,7 +32,7 @@ import java.io.PrintWriter; * Trace.traceEnd. These traces are used for performance testing. */ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServiceInterface { - private static final long TRACE_TAG = Trace.TRACE_TAG_SYSTEM_SERVER; + private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER; private final AppOpsCheckingServiceInterface mService; AppOpsCheckingServiceTracingDecorator( diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 1ce917cb7841..6d3f8fd77232 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.CONTROL_VPN; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; @@ -29,6 +30,7 @@ import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO; import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO; import static android.os.PowerWhitelistManager.REASON_VPN; import static android.os.UserHandle.PER_USER_RANGE; +import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT; import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; @@ -79,7 +81,9 @@ import android.net.NetworkInfo.DetailedState; import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.NetworkScore; +import android.net.NetworkSpecifier; import android.net.RouteInfo; +import android.net.TelephonyNetworkSpecifier; import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; @@ -127,12 +131,16 @@ import android.security.keystore.KeyProperties; import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; import android.system.keystore2.KeyPermission; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.LocalLog; import android.util.Log; import android.util.Range; +import android.util.SparseArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -268,6 +276,10 @@ public class Vpn { private final ConnectivityManager mConnectivityManager; private final AppOpsManager mAppOpsManager; private final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager; + private final TelephonyManager mTelephonyManager; + private final CarrierConfigManager mCarrierConfigManager; + private final SubscriptionManager mSubscriptionManager; + // The context is for specific user which is created from mUserId private final Context mUserIdContext; @VisibleForTesting final Dependencies mDeps; @@ -314,6 +326,14 @@ public class Vpn { private final LocalLog mVpnManagerEvents = new LocalLog(MAX_EVENTS_LOGS); /** + * Cached Map of <subscription ID, keepalive delay ms> since retrieving the PersistableBundle + * and the target value from CarrierConfigManager is somewhat expensive as it has hundreds of + * fields. This cache is cleared when the carrier config changes to ensure data freshness. + */ + @GuardedBy("this") + private final SparseArray<Integer> mCachedKeepalivePerSubId = new SparseArray<>(); + + /** * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This * only applies to {@link VpnService} connections. */ @@ -626,6 +646,10 @@ public class Vpn { mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); mConnectivityDiagnosticsManager = mContext.getSystemService(ConnectivityDiagnosticsManager.class); + mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); + mDeps = deps; mNms = netService; mNetd = netd; @@ -2893,6 +2917,24 @@ public class Vpn { */ private int mRetryCount = 0; + private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener = + new CarrierConfigManager.CarrierConfigChangeListener() { + @Override + public void onCarrierConfigChanged(int slotIndex, int subId, int carrierId, + int specificCarrierId) { + synchronized (Vpn.this) { + mCachedKeepalivePerSubId.remove(subId); + + // Ignore stale runner. + if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return; + + maybeMigrateIkeSession(mActiveNetwork); + } + // TODO: update the longLivedTcpConnectionsExpensive value in the + // networkcapabilities of the VPN network. + } + }; + IkeV2VpnRunner( @NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) { super(TAG); @@ -2918,6 +2960,9 @@ public class Vpn { setVpnNetworkPreference(mSessionKey, createUserAndRestrictedProfilesRanges(mUserId, mConfig.allowedApplications, mConfig.disallowedApplications)); + + mCarrierConfigManager.registerCarrierConfigChangeListener(mExecutor, + mCarrierConfigChangeListener); } @Override @@ -3257,8 +3302,6 @@ public class Vpn { mUnderlyingLinkProperties = null; mUnderlyingNetworkCapabilities = null; mRetryCount = 0; - - startOrMigrateIkeSession(network); } @NonNull @@ -3356,9 +3399,36 @@ public class Vpn { } private int guessNattKeepaliveTimerForNetwork() { - // TODO : guess the keepalive delay based on carrier if auto keepalive timer is - // enabled - return AUTOMATIC_KEEPALIVE_DELAY_SECONDS; + final int subId = getCellSubIdForNetworkCapabilities(mUnderlyingNetworkCapabilities); + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + Log.d(TAG, "Underlying network is not a cellular network"); + return AUTOMATIC_KEEPALIVE_DELAY_SECONDS; + } + + synchronized (Vpn.this) { + if (mCachedKeepalivePerSubId.contains(subId)) { + Log.d(TAG, "Get cached keepalive config"); + return mCachedKeepalivePerSubId.get(subId); + } + + final TelephonyManager perSubTm = mTelephonyManager.createForSubscriptionId(subId); + if (perSubTm.getSimApplicationState() != TelephonyManager.SIM_STATE_LOADED) { + Log.d(TAG, "SIM card is not ready on sub " + subId); + return AUTOMATIC_KEEPALIVE_DELAY_SECONDS; + } + + final PersistableBundle carrierConfig = + mCarrierConfigManager.getConfigForSubId(subId); + if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) { + return AUTOMATIC_KEEPALIVE_DELAY_SECONDS; + } + + final int natKeepalive = + carrierConfig.getInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT); + mCachedKeepalivePerSubId.put(subId, natKeepalive); + Log.d(TAG, "Get customized keepalive=" + natKeepalive); + return natKeepalive; + } } boolean maybeMigrateIkeSession(@NonNull Network underlyingNetwork) { @@ -3456,7 +3526,15 @@ public class Vpn { /** Called when the NetworkCapabilities of underlying network is changed */ public void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc) { + final NetworkCapabilities oldNc = mUnderlyingNetworkCapabilities; mUnderlyingNetworkCapabilities = nc; + if (oldNc == null) { + // A new default network is available. + startOrMigrateIkeSession(mActiveNetwork); + } else if (!nc.getSubscriptionIds().equals(oldNc.getSubscriptionIds())) { + // Renew carrierConfig values. + maybeMigrateIkeSession(mActiveNetwork); + } } /** Called when the LinkProperties of underlying network is changed */ @@ -3812,6 +3890,8 @@ public class Vpn { resetIkeState(); + mCarrierConfigManager.unregisterCarrierConfigChangeListener( + mCarrierConfigChangeListener); mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback( mDiagnosticsCallback); @@ -4804,6 +4884,8 @@ public class Vpn { pw.println("Reset session scheduled"); } } + pw.println("mCachedKeepalivePerSubId=" + mCachedKeepalivePerSubId); + pw.println("mUnderlyNetworkChanges (most recent first):"); pw.increaseIndent(); mUnderlyNetworkChanges.reverseDump(pw); @@ -4815,4 +4897,19 @@ public class Vpn { pw.decreaseIndent(); } } + + private static int getCellSubIdForNetworkCapabilities(@Nullable NetworkCapabilities nc) { + if (nc == null) return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + if (!nc.hasTransport(TRANSPORT_CELLULAR)) { + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + + final NetworkSpecifier specifier = nc.getNetworkSpecifier(); + if (specifier instanceof TelephonyNetworkSpecifier) { + return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); + } + + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } } diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java index 1bb34abe9025..f93d9ee55d30 100644 --- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java +++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java @@ -84,7 +84,11 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme @Override public void notifyThrottling(Temperature temp) { @Temperature.ThrottlingStatus int currentStatus = temp.getStatus(); + synchronized (mThermalObserverLock) { + if (mStatus == currentStatus) { + return; // status not changed, skip update + } mStatus = currentStatus; mHandler.post(this::updateVotes); } @@ -188,6 +192,10 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme localStatus = mStatus; localMap = mThermalThrottlingByDisplay.get(displayId); } + if (localMap == null) { + Slog.d(TAG, "Updating votes, display already removed, display=" + displayId); + return; + } if (mLoggingEnabled) { Slog.d(TAG, "Updating votes for status=" + localStatus + ", display =" + displayId + ", map=" + localMap); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java index dc2799e8e434..6cd2493cfdff 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java @@ -21,6 +21,8 @@ import static android.provider.InputMethodManagerDeviceConfig.KEY_HIDE_IME_WHEN_ import android.app.ActivityThread; import android.provider.DeviceConfig; +import com.android.internal.annotations.VisibleForTesting; + /** * Class for the device-level configuration related to the input method manager * platform features in {@link DeviceConfig}. @@ -56,4 +58,9 @@ final class InputMethodDeviceConfigs { public boolean shouldHideImeWhenNoEditorFocus() { return mHideImeWhenNoEditorFocus; } + + @VisibleForTesting + void destroy() { + DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); + } } 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 77b9abed0d41..48326188e215 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -16,6 +16,9 @@ package com.android.server.media.projection; +import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED; +import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -484,6 +487,9 @@ public final class MediaProjectionManagerService extends SystemService userHandle = new UserHandle(UserHandle.getUserId(uid)); mTargetSdkVersion = targetSdkVersion; mIsPrivileged = isPrivileged; + // TODO(b/267740338): Add unit test. + mActivityManagerInternal.notifyMediaProjectionEvent(uid, asBinder(), + MEDIA_PROJECTION_TOKEN_EVENT_CREATED); } @Override // Binder call @@ -630,6 +636,9 @@ public final class MediaProjectionManagerService extends SystemService mToken = null; unregisterCallback(mCallback); mCallback = null; + // TODO(b/267740338): Add unit test. + mActivityManagerInternal.notifyMediaProjectionEvent(uid, asBinder(), + MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED); } } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 85f13572db68..646dc4e98c39 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -29,6 +29,7 @@ import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; import com.android.server.UiThread; import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult; +import com.android.server.wm.EventLogTags; import java.io.PrintWriter; @@ -255,6 +256,11 @@ public class KeyguardServiceDelegate { public void setOccluded(boolean isOccluded, boolean notify) { if (mKeyguardService != null && notify) { if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ")"); + EventLogTags.writeWmSetKeyguardOccluded( + isOccluded ? 1 : 0, + 0 /* animate */, + 0 /* transit */, + "setOccluded"); mKeyguardService.setOccluded(isOccluded, false /* animate */); } mKeyguardState.occluded = isOccluded; diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index c6684dfdb6e0..5442b6dc4b09 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2125,7 +2125,7 @@ public final class TvInputManagerService extends SystemService { } @Override - public void notifyTvMessage(IBinder sessionToken, String type, Bundle data, int userId) { + public void notifyTvMessage(IBinder sessionToken, int type, Bundle data, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, userId, "timeShiftEnablePositionTracking"); @@ -2590,7 +2590,7 @@ public final class TvInputManagerService extends SystemService { } @Override - public void notifyAdBuffer( + public void notifyAdBufferReady( IBinder sessionToken, AdBuffer buffer, int userId) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -2602,7 +2602,7 @@ public final class TvInputManagerService extends SystemService { try { SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, resolvedUserId); - getSessionLocked(sessionState).notifyAdBuffer(buffer); + getSessionLocked(sessionState).notifyAdBufferReady(buffer); } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in notifyAdBuffer", e); } @@ -3765,7 +3765,7 @@ public final class TvInputManagerService extends SystemService { } @Override - public void onTvMessage(String type, Bundle data) { + public void onTvMessage(int type, Bundle data) { synchronized (mLock) { if (DEBUG) { Slog.d(TAG, "onTvMessage(type=" + type + ", data=" + data + ")"); diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index 61c137ed1c5b..0d4a76ee3add 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -1117,7 +1117,7 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override - public void notifyTvMessage(IBinder sessionToken, String type, Bundle data, int userId) { + public void notifyTvMessage(IBinder sessionToken, int type, Bundle data, int userId) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, @@ -3047,16 +3047,16 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override - public void onAdBuffer(AdBuffer buffer) { + public void onAdBufferReady(AdBuffer buffer) { synchronized (mLock) { if (DEBUG) { - Slogf.d(TAG, "onAdBuffer(buffer=" + buffer + ")"); + Slogf.d(TAG, "onAdBufferReady(buffer=" + buffer + ")"); } if (mSessionState.mSession == null || mSessionState.mClient == null) { return; } try { - mSessionState.mClient.onAdBuffer(buffer, mSessionState.mSeq); + mSessionState.mClient.onAdBufferReady(buffer, mSessionState.mSeq); } catch (RemoteException e) { Slogf.e(TAG, "error in onAdBuffer", e); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 28b974c7330a..c7754194d3b1 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1734,7 +1734,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A : null; } - private void clearLastParentBeforePip() { + void clearLastParentBeforePip() { if (mLastParentBeforePip != null) { mLastParentBeforePip.mChildPipActivity = null; mLastParentBeforePip = null; @@ -7824,27 +7824,38 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * Returns the requested {@link Configuration.Orientation} for the current activity. + */ + @Configuration.Orientation + @Override + int getRequestedConfigurationOrientation(boolean forDisplay) { + return getRequestedConfigurationOrientation(forDisplay, getOverrideOrientation()); + } + + /** + * Returns the requested {@link Configuration.Orientation} for the requested + * {@link ActivityInfo.ScreenOrientation}. * - * <p>When The current orientation is set to {@link SCREEN_ORIENTATION_BEHIND} it returns the - * requested orientation for the activity below which is the first activity with an explicit + * <p>When the current screen orientation is set to {@link SCREEN_ORIENTATION_BEHIND} it returns + * the requested orientation for the activity below which is the first activity with an explicit * (different from {@link SCREEN_ORIENTATION_UNSET}) orientation which is not {@link * SCREEN_ORIENTATION_BEHIND}. */ @Configuration.Orientation - @Override - int getRequestedConfigurationOrientation(boolean forDisplay) { + int getRequestedConfigurationOrientation(boolean forDisplay, + @ActivityInfo.ScreenOrientation int requestedOrientation) { if (mLetterboxUiController.hasInheritedOrientation()) { final RootDisplayArea root = getRootDisplayArea(); if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { - return ActivityInfo.reverseOrientation( + return reverseConfigurationOrientation( mLetterboxUiController.getInheritedOrientation()); } else { return mLetterboxUiController.getInheritedOrientation(); } } - if (task != null && getOverrideOrientation() == SCREEN_ORIENTATION_BEHIND) { + if (task != null && requestedOrientation == SCREEN_ORIENTATION_BEHIND) { // We use Task here because we want to be consistent with what happens in // multi-window mode where other tasks orientations are ignored. + android.util.Log.d("orientation", "We are here"); final ActivityRecord belowCandidate = task.getActivity( a -> a.canDefineOrientationForActivitiesAbove() /* callback */, this /* boundary */, false /* includeBoundary */, @@ -7853,7 +7864,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return belowCandidate.getRequestedConfigurationOrientation(forDisplay); } } - return super.getRequestedConfigurationOrientation(forDisplay); + return super.getRequestedConfigurationOrientation(forDisplay, requestedOrientation); + } + + /** + * Returns the reversed configuration orientation. + * @hide + */ + @Configuration.Orientation + public static int reverseConfigurationOrientation(@Configuration.Orientation int orientation) { + switch (orientation) { + case ORIENTATION_LANDSCAPE: + return ORIENTATION_PORTRAIT; + case ORIENTATION_PORTRAIT: + return ORIENTATION_LANDSCAPE; + default: + return orientation; + } } /** @@ -7899,6 +7926,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) { return; } + // This is necessary in order to avoid going into size compat mode when the orientation + // change request comes from the app + if (mWmService.mLetterboxConfiguration + .isSizeCompatModeDisabledAfterOrientationChangeFromApp() + && getRequestedConfigurationOrientation(false, requestedOrientation) + != getRequestedConfigurationOrientation(false /*forDisplay */)) { + // Do not change the requested configuration now, because this will be done when setting + // the orientation below with the new mCompatDisplayInsets + clearSizeCompatModeAttributes(); + } + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Setting requested orientation %s for %s", + ActivityInfo.screenOrientationToString(requestedOrientation), this); setOrientation(requestedOrientation, this); // Push the new configuration to the requested app in case where it's not pushed, e.g. when @@ -8139,17 +8179,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio); } - @VisibleForTesting - void clearSizeCompatMode() { - final float lastSizeCompatScale = mSizeCompatScale; + private void clearSizeCompatModeAttributes() { mInSizeCompatModeForBounds = false; mSizeCompatScale = 1f; mSizeCompatBounds = null; mCompatDisplayInsets = null; + } + + @VisibleForTesting + void clearSizeCompatMode() { + final float lastSizeCompatScale = mSizeCompatScale; + clearSizeCompatModeAttributes(); if (mSizeCompatScale != lastSizeCompatScale) { forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); } - // Clear config override in #updateCompatDisplayInsets(). final int activityType = getActivityType(); final Configuration overrideConfig = getRequestedOverrideConfiguration(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 5ea28542207e..bfb735de2d0a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -490,8 +490,7 @@ public abstract class ActivityTaskManagerInternal { /** Dump the current activities state. */ public abstract boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly, - boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter, - @UserIdInt int userId); + boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId); /** Dump the current state for inclusion in oom dump. */ public abstract void dumpForOom(PrintWriter pw); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 898b47785a7b..992743ab8593 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4205,8 +4205,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly, - boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter, - @UserIdInt int userId) { + boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId) { ArrayList<ActivityRecord> activities; synchronized (mGlobalLock) { @@ -4251,7 +4250,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } } - dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll, dumpVerbose); + dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll); } if (!printedAnything) { // Typically happpens when no task matches displayIdFilter @@ -4265,7 +4264,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { * there is a thread associated with the activity. */ private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw, - ActivityRecord r, String[] args, boolean dumpAll, boolean dumpVerbose) { + ActivityRecord r, String[] args, boolean dumpAll) { String innerPrefix = prefix + " "; IApplicationThread appThread = null; synchronized (mGlobalLock) { @@ -4281,15 +4280,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } else { pw.print("(not running)"); } - if (dumpVerbose) { - pw.print(" userId="); - pw.print(r.mUserId); - pw.print(" uid="); - pw.print(r.getUid()); - printDisplayInfoAndNewLine(pw, r); - } else { - pw.println(); - } + pw.print(" userId="); + pw.print(r.mUserId); + pw.print(" uid="); + pw.print(r.getUid()); + printDisplayInfoAndNewLine(pw, r); if (dumpAll) { r.dump(pw, innerPrefix, /* dumpAll= */ true); } @@ -6618,11 +6613,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly, - boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter, + boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId) { return ActivityTaskManagerService.this.dumpActivity(fd, pw, name, args, opti, dumpAll, - dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, dumpVerbose, displayIdFilter, - userId); + dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, displayIdFilter, userId); } @Override diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags index 594929bea77a..244656ca68e6 100644 --- a/services/core/java/com/android/server/wm/EventLogTags.logtags +++ b/services/core/java/com/android/server/wm/EventLogTags.logtags @@ -49,7 +49,7 @@ option java_package com.android.server.wm 30066 wm_add_to_stopping (User|1|5),(Token|1|5),(Component Name|3),(Reason|3) # Keyguard status changed -30067 wm_set_keyguard_shown (Display Id|1|5),(keyguardShowing|1),(aodShowing|1),(keyguardGoingAway|1),(Reason|3) +30067 wm_set_keyguard_shown (Display Id|1|5),(keyguardShowing|1),(aodShowing|1),(keyguardGoingAway|1),(occluded|1),(Reason|3) # Out of memory for surfaces. 31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3) @@ -65,6 +65,8 @@ option java_package com.android.server.wm # bootanim finished: 31007 wm_boot_animation_done (time|2|3) +# Notify keyguard occlude statuc change to SysUI. +31008 wm_set_keyguard_occluded (occluded|1),(animate|1),(transit|1),(Channel|3) # Back navigation. 31100 wm_back_navi_canceled (Reason|3) diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 32b3ccf85c94..08a6358afbc7 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -188,6 +188,7 @@ class KeyguardController { keyguardShowing ? 1 : 0, aodShowing ? 1 : 0, state.mKeyguardGoingAway ? 1 : 0, + state.mOccluded ? 1 : 0, "setKeyguardShown"); // Update the task snapshot if the screen will not be turned off. To make sure that the @@ -253,9 +254,10 @@ class KeyguardController { try { EventLogTags.writeWmSetKeyguardShown( displayId, - 1 /* keyguardShowing */, + state.mKeyguardShowing ? 1 : 0, state.mAodShowing ? 1 : 0, 1 /* keyguardGoingAway */, + state.mOccluded ? 1 : 0, "keyguardGoingAway"); final int transitFlags = convertTransitFlags(flags); final DisplayContent dc = mRootWindowContainer.getDefaultDisplay(); @@ -669,6 +671,15 @@ class KeyguardController { boolean hasChange = false; if (lastOccluded != mOccluded) { + if (mDisplayId == DEFAULT_DISPLAY) { + EventLogTags.writeWmSetKeyguardShown( + mDisplayId, + mKeyguardShowing ? 1 : 0, + mAodShowing ? 1 : 0, + mKeyguardGoingAway ? 1 : 0, + mOccluded ? 1 : 0, + "updateVisibility"); + } controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity); hasChange = true; } else if (!lastKeyguardGoingAway && mKeyguardGoingAway) { diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index fa49a6ba6c2b..37cf5bc95a23 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST; +import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP; import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT; import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_COMPAT_FAKE_FOCUS; import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY; @@ -314,6 +315,10 @@ final class LetterboxConfiguration { mDeviceConfig.updateFlagActiveStatus( /* isActive */ mTranslucentLetterboxingEnabled, /* key */ KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY); + mDeviceConfig.updateFlagActiveStatus( + /* isActive */ true, + /* key */ KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP); + mLetterboxConfigurationPersister = letterboxConfigurationPersister; mLetterboxConfigurationPersister.start(); } @@ -327,6 +332,16 @@ final class LetterboxConfiguration { } /** + * Whether size compat mode is disabled after an orientation change request comes from the app. + * This value is controlled via {@link android.provider.DeviceConfig}. + */ + // TODO(b/270356567) Clean up this flag + boolean isSizeCompatModeDisabledAfterOrientationChangeFromApp() { + return mDeviceConfig.getFlag( + KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP); + } + + /** * Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link * #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java index df3c8f0fdccc..1651af328b1f 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java +++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.provider.DeviceConfig; import android.util.ArraySet; - import com.android.internal.annotations.VisibleForTesting; import java.util.Map; @@ -53,6 +52,11 @@ final class LetterboxConfigurationDeviceConfig private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = true; + static final String KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP = + "disable_size_compat_mode_after_orientation_change_from_app"; + private static final boolean + DEFAULT_VALUE_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP = true; + @VisibleForTesting static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of( KEY_ENABLE_CAMERA_COMPAT_TREATMENT, @@ -64,7 +68,9 @@ final class LetterboxConfigurationDeviceConfig KEY_ENABLE_COMPAT_FAKE_FOCUS, DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS, KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY, - DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY + DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY, + KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP, + DEFAULT_VALUE_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP ); // Whether camera compatibility treatment is enabled. @@ -93,6 +99,10 @@ final class LetterboxConfigurationDeviceConfig private boolean mIsTranslucentLetterboxingAllowed = DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY; + // Whether size compat mode is disabled after an orientation change request comes from the app + private boolean mIsSizeCompatModeDisabledAfterOrientationChangeFromApp = + DEFAULT_VALUE_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP; + // Set of active device configs that need to be updated in // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged. private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>(); @@ -142,6 +152,8 @@ final class LetterboxConfigurationDeviceConfig return mIsCompatFakeFocusAllowed; case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY: return mIsTranslucentLetterboxingAllowed; + case KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP: + return mIsSizeCompatModeDisabledAfterOrientationChangeFromApp; default: throw new AssertionError("Unexpected flag name: " + key); } @@ -169,6 +181,10 @@ final class LetterboxConfigurationDeviceConfig case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY: mIsTranslucentLetterboxingAllowed = getDeviceConfig(key, defaultValue); break; + case KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP: + mIsSizeCompatModeDisabledAfterOrientationChangeFromApp = + getDeviceConfig(key, defaultValue); + break; default: throw new AssertionError("Unexpected flag name: " + key); } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 7208934efd51..f355f088b608 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1395,9 +1395,9 @@ final class LetterboxUiController { FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, mActivityRecord /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */); - if (firstOpaqueActivityBeneath == null) { + if (firstOpaqueActivityBeneath == null || firstOpaqueActivityBeneath.isEmbedded()) { // We skip letterboxing if the translucent activity doesn't have any opaque - // activities beneath + // activities beneath or the activity below is embedded which never has letterbox. return; } inheritConfiguration(firstOpaqueActivityBeneath); diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 3635ebbad018..b879d1a8ec56 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE; + import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS; import static com.android.server.wm.AnimationAdapterProto.REMOTE; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; @@ -195,6 +197,13 @@ class RemoteAnimationController implements DeathRecipient { + " transit=%s, apps=%d, wallpapers=%d, nonApps=%d", AppTransition.appTransitionOldToString(transit), appTargets.length, wallpaperTargets.length, nonAppTargets.length); + if (AppTransition.isKeyguardOccludeTransitOld(transit)) { + EventLogTags.writeWmSetKeyguardOccluded( + transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE ? 0 : 1, + 1 /* animate */, + transit, + "onAnimationStart"); + } mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets, wallpaperTargets, nonAppTargets, mFinishedCallback); } catch (RemoteException e) { @@ -356,6 +365,11 @@ class RemoteAnimationController implements DeathRecipient { final boolean isKeyguardOccluded = mDisplayContent.isKeyguardOccluded(); try { + EventLogTags.writeWmSetKeyguardOccluded( + isKeyguardOccluded ? 1 : 0, + 0 /* animate */, + 0 /* transit */, + "onAnimationCancelled"); mRemoteAnimationAdapter.getRunner().onAnimationCancelled(isKeyguardOccluded); } catch (RemoteException e) { Slog.e(TAG, "Failed to notify cancel", e); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3680e6dff9fe..254c911a52da 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2584,6 +2584,9 @@ class Task extends TaskFragment { EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(), reason); clearPinnedTaskIfNeed(); + if (mChildPipActivity != null) { + mChildPipActivity.clearLastParentBeforePip(); + } // If applicable let the TaskOrganizer know the Task is vanishing. setTaskOrganizer(null); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 132f5a72aadb..d42a62971395 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1493,7 +1493,26 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ @Configuration.Orientation int getRequestedConfigurationOrientation(boolean forDisplay) { - int requestedOrientation = getOverrideOrientation(); + return getRequestedConfigurationOrientation(forDisplay, getOverrideOrientation()); + } + + /** + * Gets the configuration orientation by the requested screen orientation + * + * @param forDisplay whether it is the requested config orientation for display. + * If {@code true}, we may reverse the requested orientation if the root is + * different from the display, so that when the display rotates to the + * reversed orientation, the requested app will be in the requested + * orientation. + * @param requestedOrientation the screen orientation({@link ScreenOrientation}) that is + * requested + * @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE}, + * {@link Configuration#ORIENTATION_PORTRAIT}, + * {@link Configuration#ORIENTATION_UNDEFINED}). + */ + @Configuration.Orientation + int getRequestedConfigurationOrientation(boolean forDisplay, + @ScreenOrientation int requestedOrientation) { final RootDisplayArea root = getRootDisplayArea(); if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { // Reverse the requested orientation if the orientation of its root is different from @@ -1503,7 +1522,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // (portrait). // When an app below the DAG is requesting landscape, it should actually request the // display to be portrait, so that the DAG and the app will be in landscape. - requestedOrientation = reverseOrientation(getOverrideOrientation()); + requestedOrientation = reverseOrientation(requestedOrientation); } if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) { diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java index 14c49b312f34..3b92cc9647f7 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java +++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java @@ -25,17 +25,19 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; - +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; /** Contains information on what CredentialProvider has what provisioned Credential. */ public class CredentialDescriptionRegistry { + private static final String FLAT_STRING_SPLIT_REGEX = ";"; private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128; private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16; @GuardedBy("sLock") @@ -164,14 +166,16 @@ public class CredentialDescriptionRegistry { /** Returns package names and entries of a CredentialProviders that can satisfy a given * {@link CredentialDescription}. */ public Set<FilterResult> getFilteredResultForProvider(String packageName, - String flatRequestStrings) { + String flatRequestString) { Set<FilterResult> result = new HashSet<>(); if (!mCredentialDescriptions.containsKey(packageName)) { return result; } Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName); + Set<String> unflattenedRequestString = flatStringToSet(flatRequestString); for (CredentialDescription containedDescription: currentSet) { - if (flatRequestStrings.equals(containedDescription.getFlattenedRequestString())) { + if (checkForMatch(flatStringToSet(containedDescription.getFlattenedRequestString()), + unflattenedRequestString)) { result.add(new FilterResult(packageName, containedDescription.getFlattenedRequestString(), containedDescription .getCredentialEntries())); @@ -182,12 +186,16 @@ public class CredentialDescriptionRegistry { /** Returns package names of CredentialProviders that can satisfy a given * {@link CredentialDescription}. */ - public Set<FilterResult> getMatchingProviders(Set<String> flatRequestString) { + public Set<FilterResult> getMatchingProviders(Set<String> flatRequestStrings) { Set<FilterResult> result = new HashSet<>(); + Set<Set<String>> unflattenedRequestStrings = flatRequestStrings.stream().map( + CredentialDescriptionRegistry::flatStringToSet).collect(Collectors.toSet()); for (String packageName: mCredentialDescriptions.keySet()) { Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName); for (CredentialDescription containedDescription : currentSet) { - if (flatRequestString.contains(containedDescription.getFlattenedRequestString())) { + if (canProviderSatisfyAny(flatStringToSet(containedDescription + .getFlattenedRequestString()), + unflattenedRequestStrings)) { result.add(new FilterResult(packageName, containedDescription.getFlattenedRequestString(), containedDescription .getCredentialEntries())); @@ -203,4 +211,24 @@ public class CredentialDescriptionRegistry { } } + private static boolean canProviderSatisfyAny(Set<String> registeredUnflattenedStrings, + Set<Set<String>> requestedUnflattenedStrings) { + for (Set<String> requestedUnflattenedString : requestedUnflattenedStrings) { + if (registeredUnflattenedStrings.containsAll(requestedUnflattenedString)) { + return true; + } + } + return false; + } + + private static boolean checkForMatch(Set<String> registeredUnflattenedStrings, + Set<String> requestedUnflattenedString) { + return registeredUnflattenedStrings.containsAll(requestedUnflattenedString); + } + + private static Set<String> flatStringToSet(String flatString) { + return new HashSet<>(Arrays + .asList(flatString.split(FLAT_STRING_SPLIT_REGEX))); + } + } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 6e998c4ef195..55973fa41ab6 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -33,7 +33,6 @@ import android.content.pm.ServiceInfo; import android.credentials.ClearCredentialStateRequest; import android.credentials.CreateCredentialException; import android.credentials.CreateCredentialRequest; -import android.credentials.CredentialDescription; import android.credentials.CredentialManager; import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; @@ -296,11 +295,6 @@ public final class CredentialManagerService mContext, UserHandle.getCallingUserId(), session, - CredentialProviderInfoFactory.getCredentialProviderFromPackageName( - mContext, UserHandle.getCallingUserId() , - result.second.mPackageName, - CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS, - new HashSet<>()), session.mClientAppInfo, result.second.mPackageName, result.first)); @@ -752,44 +746,6 @@ public final class CredentialManagerService enforceCallingPackage(callingPackage, Binder.getCallingUid()); - List<CredentialProviderInfo> services = - getServicesForCredentialDescription(UserHandle.getCallingUserId()); - - List<String> providers = - services.stream() - .map( - credentialProviderInfo -> - credentialProviderInfo.getServiceInfo().packageName) - .toList(); - - if (!providers.contains(callingPackage)) { - throw new NonCredentialProviderCallerException(callingPackage); - } - - List<CredentialProviderInfo> matchingService = - services.stream() - .filter( - credentialProviderInfo -> - credentialProviderInfo - .getServiceInfo() - .packageName - .equals(callingPackage)) - .toList(); - - CredentialProviderInfo credentialProviderInfo = matchingService.get(0); - - Set<String> supportedTypes = - request.getCredentialDescriptions().stream() - .map(CredentialDescription::getType) - .filter(credentialProviderInfo::hasCapability) - .collect(Collectors.toSet()); - - if (supportedTypes.size() != request.getCredentialDescriptions().size()) { - throw new IllegalArgumentException( - "CredentialProvider does not support one or more" - + "of the registered types. Check your XML entry."); - } - CredentialDescriptionRegistry session = CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId()); @@ -808,20 +764,6 @@ public final class CredentialManagerService enforceCallingPackage(callingPackage, Binder.getCallingUid()); - List<CredentialProviderInfo> services = - getServicesForCredentialDescription(UserHandle.getCallingUserId()); - - List<String> providers = - services.stream() - .map( - credentialProviderInfo -> - credentialProviderInfo.getServiceInfo().packageName) - .toList(); - - if (!providers.contains(callingPackage)) { - throw new NonCredentialProviderCallerException(callingPackage); - } - CredentialDescriptionRegistry session = CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId()); diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 255b2f80ff44..2dc930d42d62 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -24,7 +24,7 @@ import android.util.Log; import com.android.internal.util.FrameworkStatsLog; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; -import com.android.server.credentials.metrics.CandidateProviderMetric; +import com.android.server.credentials.metrics.CandidatePhaseMetric; import com.android.server.credentials.metrics.ChosenProviderMetric; import java.util.Map; @@ -98,7 +98,7 @@ public class MetricUtilities { int[] candidateStatusList = new int[providerSize]; int index = 0; for (var session : providerSessions) { - CandidateProviderMetric metric = session.mCandidateProviderMetric; + CandidatePhaseMetric metric = session.mCandidateProviderMetric; candidateUidList[index] = metric.getCandidateUid(); candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds(); candidateStatusList[index] = metric.getProviderQueryStatus(); diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index b7a4cd581a1a..ab29acc72a53 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java @@ -72,7 +72,8 @@ public final class ProviderClearSession extends ProviderSession<ClearCredential ProviderInternalCallback callbacks, int userId, RemoteCredentialService remoteCredentialService, ClearCredentialStateRequest providerRequest) { - super(context, info, providerRequest, callbacks, userId, remoteCredentialService); + super(context, providerRequest, callbacks, info.getComponentName(), + userId, remoteCredentialService); setStatus(Status.PENDING); } @@ -95,7 +96,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredential /** Called when provider service dies. */ @Override // Callback from the remote provider public void onProviderServiceDied(RemoteCredentialService service) { - if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) { + if (service.getComponentName().equals(mComponentName)) { updateStatusAndInvokeCallback(Status.SERVICE_DEAD); } else { Slog.i(TAG, "Component names different in onProviderServiceDied - " diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index 640cc3331b1a..8c9c6cfe24da 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -133,7 +133,7 @@ public final class ProviderCreateSession extends ProviderSession< @NonNull BeginCreateCredentialRequest beginCreateRequest, @NonNull CreateCredentialRequest completeCreateRequest, String hybridService) { - super(context, info, beginCreateRequest, callbacks, userId, + super(context, beginCreateRequest, callbacks, info.getComponentName(), userId, remoteCredentialService); mCompleteRequest = completeCreateRequest; setStatus(Status.PENDING); @@ -161,7 +161,7 @@ public final class ProviderCreateSession extends ProviderSession< /** Called when provider service dies. */ @Override public void onProviderServiceDied(RemoteCredentialService service) { - if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) { + if (service.getComponentName().equals(mComponentName)) { updateStatusAndInvokeCallback(Status.SERVICE_DEAD); } else { Slog.i(TAG, "Component names different in onProviderServiceDied - " diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 07e2f877bfc0..851ccb33aa43 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -169,7 +169,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential CallingAppInfo callingAppInfo, Map<String, CredentialOption> beginGetOptionToCredentialOptionMap, String hybridService) { - super(context, info, beginGetRequest, callbacks, userId, remoteCredentialService); + super(context, beginGetRequest, callbacks, info.getComponentName() , + userId, remoteCredentialService); mCompleteRequest = completeGetRequest; mCallingAppInfo = callingAppInfo; setStatus(Status.PENDING); @@ -196,7 +197,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential /** Called when provider service dies. */ @Override // Callback from the remote provider public void onProviderServiceDied(RemoteCredentialService service) { - if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) { + if (service.getComponentName().equals(mComponentName)) { updateStatusAndInvokeCallback(Status.SERVICE_DEAD); } else { Slog.i(TAG, "Component names different in onProviderServiceDied - " diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index 457806de7c9f..a5196c519740 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -19,10 +19,10 @@ package com.android.server.credentials; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.credentials.CredentialOption; -import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; import android.credentials.GetCredentialResponse; import android.credentials.ui.Entry; @@ -64,7 +64,6 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption @NonNull Context context, @UserIdInt int userId, @NonNull GetRequestSession getRequestSession, - @NonNull CredentialProviderInfo credentialProviderInfo, @NonNull CallingAppInfo callingAppInfo, @NonNull String credentialProviderPackageName, @NonNull CredentialOption requestOption) { @@ -72,7 +71,6 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption context, userId, getRequestSession, - credentialProviderInfo, callingAppInfo, credentialProviderPackageName, requestOption); @@ -94,11 +92,12 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption protected ProviderRegistryGetSession(@NonNull Context context, @NonNull int userId, @NonNull GetRequestSession session, - @NonNull CredentialProviderInfo credentialProviderInfo, @NonNull CallingAppInfo callingAppInfo, @NonNull String servicePackageName, @NonNull CredentialOption requestOption) { - super(context, credentialProviderInfo, requestOption, session, userId, null); + super(context, requestOption, session, + new ComponentName(servicePackageName, servicePackageName) , + userId, null); mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId); mCallingAppInfo = callingAppInfo; mCredentialProviderPackageName = servicePackageName; diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index a8b9bf6b1fd6..03e2a3264a9e 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -31,7 +31,7 @@ import android.os.ICancellationSignal; import android.os.RemoteException; import android.util.Log; -import com.android.server.credentials.metrics.CandidateProviderMetric; +import com.android.server.credentials.metrics.CandidatePhaseMetric; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.UUID; @@ -59,7 +59,7 @@ public abstract class ProviderSession<T, R> @Nullable protected R mProviderResponse; @NonNull protected Boolean mProviderResponseSet = false; // Specific candidate provider metric for the provider this session handles - @Nullable protected CandidateProviderMetric mCandidateProviderMetric; + @Nullable protected CandidatePhaseMetric mCandidateProviderMetric; @NonNull private int mProviderSessionUid; /** @@ -114,19 +114,20 @@ public abstract class ProviderSession<T, R> @Nullable String message); } - protected ProviderSession(@NonNull Context context, @Nullable CredentialProviderInfo info, + protected ProviderSession(@NonNull Context context, @NonNull T providerRequest, @Nullable ProviderInternalCallback callbacks, + @NonNull ComponentName componentName, @NonNull int userId, @Nullable RemoteCredentialService remoteCredentialService) { mContext = context; - mProviderInfo = info; + mProviderInfo = null; mProviderRequest = providerRequest; mCallbacks = callbacks; mUserId = userId; - mComponentName = info.getServiceInfo().getComponentName(); + mComponentName = componentName; mRemoteCredentialService = remoteCredentialService; - mCandidateProviderMetric = new CandidateProviderMetric(); + mCandidateProviderMetric = new CandidatePhaseMetric(); mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName); } diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index c1f35d0f8195..42ec42b170ca 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -36,7 +36,7 @@ import android.util.Log; import com.android.internal.R; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; -import com.android.server.credentials.metrics.CandidateProviderMetric; +import com.android.server.credentials.metrics.CandidatePhaseMetric; import com.android.server.credentials.metrics.ChosenProviderMetric; import java.util.ArrayList; @@ -218,7 +218,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan * @param componentName the componentName to associate with a provider */ protected void setChosenMetric(ComponentName componentName) { - CandidateProviderMetric metric = this.mProviders.get(componentName.flattenToString()) + CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString()) .mCandidateProviderMetric; mChosenProviderMetric.setChosenUid(metric.getCandidateUid()); mChosenProviderMetric.setFinalFinishTimeNanoseconds(System.nanoTime()); diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java new file mode 100644 index 000000000000..1c7fb69548fc --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.credentials.metrics; + +import android.util.Log; + +import com.android.server.credentials.MetricUtilities; + +/** + * The central candidate provider metric object that mimics our defined metric setup. + * Some types are redundant across these metric collectors, but that has debug use-cases as + * these data-types are available at different moments of the flow (and typically, one can feed + * into the next). + * TODO(b/270403549) - iterate on this in V3+ + */ +public class CandidatePhaseMetric { + + private static final String TAG = "CandidateProviderMetric"; + // Since this will always be the second in the split sequence, this is statically 2 + private static final int SESSION_ID = 2; + // The sequence number of this emit of the API call, default -1, equal for all candidates + private int mSequenceId = -1; + // Indicates if this provider returned from the query phase, default false + private boolean mQueryReturned = false; + + // The candidate provider uid + private int mCandidateUid = -1; + + // Raw timestamp in nanoseconds, will be converted to microseconds for logging + + //For reference, the initial log timestamp when the service started running the API call + private long mServiceBeganTimeNanoseconds = -1; + // The moment when the query phase began + private long mStartQueryTimeNanoseconds = -1; + // The moment when the query phase ended + private long mQueryFinishTimeNanoseconds = -1; + + // The status of this particular provider + private int mProviderQueryStatus = -1; + // Indicates if an exception was thrown by this provider, false by default + private boolean mHasException = false; + // Indicates the number of total entries available. We can also locally store the entries, but + // cannot emit them in the current split form. TODO(b/271135048) - possibly readjust candidate + // entries. Also, it may be okay to remove this and instead aggregate from inner counts. + // Defaults to -1 + private int mNumEntriesTotal = -1; + // The count of action entries from this provider, defaults to -1 + private int mActionEntryCount = -1; + // The count of credential entries from this provider, defaults to -1 + private int mCredentialEntryCount = -1; + // The *type-count* of the credential entries, defaults to -1 + private int mCredentialEntryTypeCount = -1; + // The count of remote entries from this provider, defaults to -1 + private int mRemoteEntryCount = -1; + // The count of authentication entries from this provider, defaults to -1 + private int mAuthenticationEntryCount = -1; + + public CandidatePhaseMetric() { + } + + /* ---------- Latencies ---------- */ + + /* -- Timestamps -- */ + + public void setServiceBeganTimeNanoseconds(long serviceBeganTimeNanoseconds) { + this.mServiceBeganTimeNanoseconds = serviceBeganTimeNanoseconds; + } + + public void setStartQueryTimeNanoseconds(long startQueryTimeNanoseconds) { + this.mStartQueryTimeNanoseconds = startQueryTimeNanoseconds; + } + + public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) { + this.mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds; + } + + public long getServiceBeganTimeNanoseconds() { + return this.mServiceBeganTimeNanoseconds; + } + + public long getStartQueryTimeNanoseconds() { + return this.mStartQueryTimeNanoseconds; + } + + public long getQueryFinishTimeNanoseconds() { + return this.mQueryFinishTimeNanoseconds; + } + + /* -- Actual time delta latencies (for local utility) -- */ + + /** + * Returns the latency in microseconds for the query phase. + */ + public int getQueryLatencyMicroseconds() { + return (int) ((this.getQueryFinishTimeNanoseconds() + - this.getStartQueryTimeNanoseconds()) / 1000); + } + + /* --- Time Stamp Conversion to Microseconds from Reference --- */ + + /** + * We collect raw timestamps in nanoseconds for ease of collection. However, given the scope + * of our logging timeframe, and size considerations of the metric, we require these to give us + * the microsecond timestamps from the start reference point. + * + * @param specificTimestamp the timestamp to consider, must be greater than the reference + * @return the microsecond integer timestamp from service start to query began + */ + public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) { + if (specificTimestamp < this.mServiceBeganTimeNanoseconds) { + Log.i(TAG, "The timestamp is before service started, falling back to default int"); + return MetricUtilities.DEFAULT_INT_32; + } + return (int) ((specificTimestamp + - this.mServiceBeganTimeNanoseconds) / 1000); + } + + /* ------------- Provider Query Status ------------ */ + + public void setProviderQueryStatus(int providerQueryStatus) { + this.mProviderQueryStatus = providerQueryStatus; + } + + public int getProviderQueryStatus() { + return this.mProviderQueryStatus; + } + + /* -------------- Candidate Uid ---------------- */ + + public void setCandidateUid(int candidateUid) { + this.mCandidateUid = candidateUid; + } + + public int getCandidateUid() { + return this.mCandidateUid; + } + + /* -------------- Session Id ---------------- */ + public int getSessionId() { + return SESSION_ID; + } + + /* -------------- Sequence Id ---------------- */ + + public void setSequenceId(int sequenceId) { + mSequenceId = sequenceId; + } + + public int getSequenceId() { + return mSequenceId; + } + + /* -------------- Query Returned Status ---------------- */ + + public void setQueryReturned(boolean queryReturned) { + mQueryReturned = queryReturned; + } + + public boolean isQueryReturned() { + return mQueryReturned; + } + + /* -------------- Has Exception Status ---------------- */ + + public void setHasException(boolean hasException) { + mHasException = hasException; + } + + public boolean isHasException() { + return mHasException; + } + + /* -------------- Number of Entries ---------------- */ + + public void setNumEntriesTotal(int numEntriesTotal) { + mNumEntriesTotal = numEntriesTotal; + } + + public int getNumEntriesTotal() { + return mNumEntriesTotal; + } + + /* -------------- Count of Action Entries ---------------- */ + + public void setActionEntryCount(int actionEntryCount) { + mActionEntryCount = actionEntryCount; + } + + public int getActionEntryCount() { + return mActionEntryCount; + } + + /* -------------- Count of Credential Entries ---------------- */ + + public void setCredentialEntryCount(int credentialEntryCount) { + mCredentialEntryCount = credentialEntryCount; + } + + public int getCredentialEntryCount() { + return mCredentialEntryCount; + } + + /* -------------- Count of Credential Entry Types ---------------- */ + + public void setCredentialEntryTypeCount(int credentialEntryTypeCount) { + mCredentialEntryTypeCount = credentialEntryTypeCount; + } + + public int getCredentialEntryTypeCount() { + return mCredentialEntryTypeCount; + } + + /* -------------- Count of Remote Entries ---------------- */ + + public void setRemoteEntryCount(int remoteEntryCount) { + mRemoteEntryCount = remoteEntryCount; + } + + public int getRemoteEntryCount() { + return mRemoteEntryCount; + } + + /* -------------- Count of Authentication Entries ---------------- */ + + public void setAuthenticationEntryCount(int authenticationEntryCount) { + mAuthenticationEntryCount = authenticationEntryCount; + } + + public int getAuthenticationEntryCount() { + return mAuthenticationEntryCount; + } +} diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java deleted file mode 100644 index 9f438ecc1146..000000000000 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.credentials.metrics; - -/** - * The central candidate provider metric object that mimics our defined metric setup. - * Some types are redundant across these metric collectors, but that has debug use-cases as - * these data-types are available at different moments of the flow (and typically, one can feed - * into the next). - * TODO(b/270403549) - iterate on this in V3+ - */ -public class CandidateProviderMetric { - - private static final String TAG = "CandidateProviderMetric"; - private int mCandidateUid = -1; - - // Raw timestamp in nanoseconds, will be converted to microseconds for logging - - private long mStartQueryTimeNanoseconds = -1; - private long mQueryFinishTimeNanoseconds = -1; - - private int mProviderQueryStatus = -1; - - public CandidateProviderMetric() { - } - - /* ---------- Latencies ---------- */ - - public void setStartQueryTimeNanoseconds(long startQueryTimeNanoseconds) { - this.mStartQueryTimeNanoseconds = startQueryTimeNanoseconds; - } - - public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) { - this.mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds; - } - - public long getStartQueryTimeNanoseconds() { - return this.mStartQueryTimeNanoseconds; - } - - public long getQueryFinishTimeNanoseconds() { - return this.mQueryFinishTimeNanoseconds; - } - - /** - * Returns the latency in microseconds for the query phase. - */ - public int getQueryLatencyMicroseconds() { - return (int) ((this.getQueryFinishTimeNanoseconds() - - this.getStartQueryTimeNanoseconds()) / 1000); - } - - // TODO (in direct next dependent CL, so this is transient) - add reference timestamp in micro - // seconds for this too. - - /* ------------- Provider Query Status ------------ */ - - public void setProviderQueryStatus(int providerQueryStatus) { - this.mProviderQueryStatus = providerQueryStatus; - } - - public int getProviderQueryStatus() { - return this.mProviderQueryStatus; - } - - /* -------------- Candidate Uid ---------------- */ - - public void setCandidateUid(int candidateUid) { - this.mCandidateUid = candidateUid; - } - - public int getCandidateUid() { - return this.mCandidateUid; - } -} diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java index 03102558d21b..1a6109116d38 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java @@ -64,7 +64,7 @@ public class ChosenProviderMetric { /* ---------------- Latencies ------------------ */ - /* ----- Direct Latencies ------- */ + /* ----- Direct Delta Latencies for Local Utility ------- */ /** * In order for a chosen provider to be selected, the call must have successfully begun. @@ -85,7 +85,7 @@ public class ChosenProviderMetric { * metric. * * @param queryPhaseLatencyMicroseconds the millisecond latency for the query phase, typically - * passed in through the {@link CandidateProviderMetric} + * passed in through the {@link CandidatePhaseMetric} */ public void setQueryPhaseLatencyMicroseconds(int queryPhaseLatencyMicroseconds) { mQueryPhaseLatencyMicroseconds = queryPhaseLatencyMicroseconds; @@ -106,7 +106,7 @@ public class ChosenProviderMetric { /** * Returns the full provider (invocation to response) latency in microseconds. Expects the - * start time to be provided, such as from {@link CandidateProviderMetric}. + * start time to be provided, such as from {@link CandidatePhaseMetric}. */ public int getEntireProviderLatencyMicroseconds() { return (int) ((this.mFinalFinishTimeNanoseconds @@ -172,7 +172,7 @@ public class ChosenProviderMetric { return mFinalFinishTimeNanoseconds; } - /* --- Time Stamp Conversion to Microseconds --- */ + /* --- Time Stamp Conversion to Microseconds from Reference Point --- */ /** * We collect raw timestamps in nanoseconds for ease of collection. However, given the scope diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a4e563b21bec..a220ad151050 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11219,7 +11219,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else { Objects.requireNonNull(admin, "ComponentName is null"); Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) - || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller))); + || isProfileOwnerOfOrganizationOwnedDevice(caller)); } if (parent) { userId = getProfileParentId(mInjector.userHandleGetCallingUserId()); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index 9501b9604fa0..7cd55f326366 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -65,6 +65,7 @@ import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @@ -121,6 +122,14 @@ public class InputMethodManagerServiceTestBase { protected InputMethodManagerService mInputMethodManagerService; protected ServiceThread mServiceThread; + @BeforeClass + public static void setupClass() { + // Make sure DeviceConfig's lazy-initialized ContentProvider gets + // a real instance before we stub out all system services below. + // TODO(b/272229177): remove dependency on real ContentProvider + new InputMethodDeviceConfigs().destroy(); + } + @Before public void setUp() throws RemoteException { mMockingSession = @@ -231,6 +240,8 @@ public class InputMethodManagerServiceTestBase { @After public void tearDown() { + mInputMethodManagerService.mInputMethodDeviceConfigs.destroy(); + if (mServiceThread != null) { mServiceThread.quitSafely(); } diff --git a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java index e68a8a0f3af8..01563e27a787 100644 --- a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java +++ b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java @@ -106,6 +106,64 @@ public class DropboxRateLimiterTest { mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); } + @Test + public void testStrictRepeatedLimiting() throws Exception { + // The first 6 entries should not be rate limited. + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + // The 7th entry of the same process should be rate limited. + assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + + // After 11 minutes there should be nothing left in the buffer and the same type of entry + // should not get rate limited anymore. + mClock.setOffsetMillis(11 * 60 * 1000); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + // The first 6 entries should not be rate limited again. + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + + // The 7th entry of the same process should be rate limited. + assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + + // After 11 more minutes there should be nothing left in the buffer and the same type of + // entry should not get rate limited anymore. + mClock.setOffsetMillis(22 * 60 * 1000); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + + // Repeated crashes after the last reset being rate limited should be restricted faster. + assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + + // We now need to wait 61 minutes for the buffer should be empty again. + mClock.setOffsetMillis(83 * 60 * 1000); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + + // After yet another 61 minutes, this time without triggering rate limiting, the strict + // limiting should be turnd off. + mClock.setOffsetMillis(144 * 60 * 1000); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + + // As rate limiting was not triggered in the last reset, after another 11 minutes the + // buffer should still act as normal. + mClock.setOffsetMillis(155 * 60 * 1000); + // The first 6 entries should not be rate limited. + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + // The 7th entry of the same process should be rate limited. + assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + } + private static class TestClock implements DropboxRateLimiter.Clock { long mOffsetMillis = 0L; diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java index b7085f152fa0..169210f627b8 100644 --- a/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java +++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java @@ -52,8 +52,10 @@ public class CredentialDescriptionRegistryTest { private static final String CALLING_PACKAGE_NAME_2 = "com.credman.app2"; private static final String MDOC_CREDENTIAL_TYPE = "MDOC"; private static final String PASSKEY_CREDENTIAL_TYPE = "PASSKEY"; - private static final String FLATTENED_REQUEST = "FLATTENED_REQ"; - private static final String FLATTENED_REQUEST_2 = "FLATTENED_REQ_2"; + private static final String FLATTENED_REGISTRY = + "FLATTENED_REQ;FLATTENED_REQ123;FLATTENED_REQa"; + private static final String FLATTENED_REGISTRY_2 = "FLATTENED_REQ_2"; + private static final String FLATTENED_REQUEST = "FLATTENED_REQ;FLATTENED_REQ123"; private CredentialDescriptionRegistry mCredentialDescriptionRegistry; private CredentialEntry mEntry; @@ -104,12 +106,12 @@ public class CredentialDescriptionRegistryTest { @Test public void testEvictProvider_existingProviders_succeeds() { final CredentialDescription credentialDescription = - new CredentialDescription(MDOC_CREDENTIAL_TYPE, FLATTENED_REQUEST, + new CredentialDescription(MDOC_CREDENTIAL_TYPE, FLATTENED_REGISTRY, Collections.emptyList()); final RegisterCredentialDescriptionRequest registerCredentialDescriptionRequest = new RegisterCredentialDescriptionRequest(credentialDescription); final CredentialDescription credentialDescription2 = - new CredentialDescription(MDOC_CREDENTIAL_TYPE, FLATTENED_REQUEST_2, + new CredentialDescription(MDOC_CREDENTIAL_TYPE, FLATTENED_REGISTRY_2, Collections.emptyList()); final RegisterCredentialDescriptionRequest registerCredentialDescriptionRequest2 = new RegisterCredentialDescriptionRequest(credentialDescription2); @@ -130,12 +132,12 @@ public class CredentialDescriptionRegistryTest { @Test public void testGetMatchingProviders_existingProviders_succeeds() { final CredentialDescription credentialDescription = - new CredentialDescription(MDOC_CREDENTIAL_TYPE, FLATTENED_REQUEST, + new CredentialDescription(MDOC_CREDENTIAL_TYPE, FLATTENED_REGISTRY, Collections.emptyList()); final RegisterCredentialDescriptionRequest registerCredentialDescriptionRequest = new RegisterCredentialDescriptionRequest(credentialDescription); final CredentialDescription credentialDescription2 = - new CredentialDescription(MDOC_CREDENTIAL_TYPE, FLATTENED_REQUEST, + new CredentialDescription(MDOC_CREDENTIAL_TYPE, FLATTENED_REGISTRY, Collections.emptyList()); final RegisterCredentialDescriptionRequest registerCredentialDescriptionRequest2 = new RegisterCredentialDescriptionRequest(credentialDescription2); @@ -171,11 +173,11 @@ public class CredentialDescriptionRegistryTest { public void testExecuteRegisterRequest_existingProviders_filterSucceeds() { final CredentialDescription credentialDescription = new CredentialDescription(MDOC_CREDENTIAL_TYPE, - FLATTENED_REQUEST, + FLATTENED_REGISTRY, List.of(mEntry, mEntry2)); final CredentialDescription credentialDescription2 = new CredentialDescription(PASSKEY_CREDENTIAL_TYPE, - FLATTENED_REQUEST_2, + FLATTENED_REGISTRY_2, List.of(mEntry3)); final RegisterCredentialDescriptionRequest registerCredentialDescriptionRequest = new RegisterCredentialDescriptionRequest(Set.of(credentialDescription, diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java index 3d52ac558605..4c8e70ae5109 100644 --- a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java @@ -36,7 +36,6 @@ import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; import android.credentials.Credential; import android.credentials.CredentialOption; -import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; import android.credentials.GetCredentialResponse; import android.credentials.ui.GetCredentialProviderData; @@ -45,7 +44,6 @@ import android.net.Uri; import android.os.Bundle; import android.service.credentials.CallingAppInfo; import android.service.credentials.CredentialEntry; -import android.service.credentials.CredentialProviderInfoFactory; import android.service.credentials.CredentialProviderService; import android.service.credentials.GetCredentialRequest; @@ -61,7 +59,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.security.cert.CertificateException; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -89,7 +86,6 @@ public class ProviderRegistryGetSessionTest { @Mock private GetRequestSession mGetRequestSession; private CredentialOption mGetCredentialOption; @Mock private ServiceInfo mServiceInfo; - private CredentialProviderInfo mCredentialProviderInfo; private CallingAppInfo mCallingAppInfo; @Mock private CredentialDescriptionRegistry mCredentialDescriptionRegistry; private Bundle mRetrievalData; @@ -111,12 +107,6 @@ public class ProviderRegistryGetSessionTest { mGetCredentialOption = new CredentialOption(CREDENTIAL_TYPE, mRetrievalData, new Bundle(), false); when(mServiceInfo.getComponentName()).thenReturn(CREDENTIAL_PROVIDER_COMPONENT); - mCredentialProviderInfo = CredentialProviderInfoFactory - .createForTests(mServiceInfo, - /* overrideLabel= */ "test", - /* isSystemProvider= */ false, - /* isEnabled= */ true, - /* capabilities= */ Collections.EMPTY_LIST); CredentialDescriptionRegistry.setSession(USER_ID_1, mCredentialDescriptionRegistry); mResponse = new HashSet<>(); mSlice = createSlice(); @@ -130,7 +120,7 @@ public class ProviderRegistryGetSessionTest { when(mCredentialDescriptionRegistry.getFilteredResultForProvider(anyString(), anyString())) .thenReturn(mResponse); mProviderRegistryGetSession = ProviderRegistryGetSession - .createNewSession(context, USER_ID_1, mGetRequestSession, mCredentialProviderInfo, + .createNewSession(context, USER_ID_1, mGetRequestSession, mCallingAppInfo, CALLING_PACKAGE_NAME, mGetCredentialOption); diff --git a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java index dd0cd965b8a0..fd1889ca0982 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java +++ b/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java @@ -18,6 +18,7 @@ package com.android.server.display.mode; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import android.hardware.display.DisplayManager; @@ -49,6 +50,7 @@ public class SkinThermalStatusObserverTest { private static final float FLOAT_TOLERANCE = 0.01f; private static final int DISPLAY_ID = 1; private static final int DISPLAY_ID_OTHER = 2; + private static final int DISPLAY_ID_ADDED = 3; SkinThermalStatusObserver mObserver; @@ -167,6 +169,42 @@ public class SkinThermalStatusObserverTest { assertEquals(120, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); } + @Test + public void testDisplayAdded() { + // GIVEN 2 displays with no thermalThrottling config AND temperature level CRITICAL + mObserver.observe(); + mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_CRITICAL)); + // WHEN new display is added + mObserver.onDisplayAdded(DISPLAY_ID_ADDED); + mHandler.flush(); + // THEN 3rd vote is added to storage with (0,60) render refresh rate(default behaviour) + assertEquals(3, mStorage.mVoteRegistry.size()); + + SparseArray<DisplayModeDirector.Vote> displayVotes = mStorage.mVoteRegistry.get( + DISPLAY_ID_ADDED); + assertEquals(1, displayVotes.size()); + + DisplayModeDirector.Vote vote = displayVotes.get( + DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE); + assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE); + assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); + } + + @Test + public void testDisplayAddedAndThenImmediatelyRemoved() { + // GIVEN 2 displays with no thermalThrottling config AND temperature level CRITICAL + mObserver.observe(); + mObserver.notifyThrottling(createTemperature(Temperature.THROTTLING_CRITICAL)); + // WHEN new display is added and immediately removed + mObserver.onDisplayAdded(DISPLAY_ID_ADDED); + mObserver.onDisplayRemoved(DISPLAY_ID_ADDED); + mHandler.flush(); + // THEN there are 2 votes in registry + assertEquals(2, mStorage.mVoteRegistry.size()); + assertNotNull(mStorage.mVoteRegistry.get(DISPLAY_ID)); + assertNotNull(mStorage.mVoteRegistry.get(DISPLAY_ID_OTHER)); + } + private static Temperature createTemperature(@Temperature.ThrottlingStatus int status) { return new Temperature(40.0f, Temperature.TYPE_SKIN, "test_temp", status); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 354420f46c2f..41a9504fba97 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -416,7 +416,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Shell permisssions will override permissions of our app, so add all necessary permissions // for this test here: InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( - "android.permission.WRITE_DEVICE_CONFIG", + "android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG", "android.permission.READ_DEVICE_CONFIG", "android.permission.READ_CONTACTS"); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 49f215a83c2b..6147633f624a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2377,7 +2377,7 @@ public class ActivityRecordTests extends WindowTestsBase { .setScreenOrientation(SCREEN_ORIENTATION_BEHIND) .build(); final int topOrientation = activityTop.getRequestedConfigurationOrientation(); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, topOrientation); + assertEquals(ORIENTATION_PORTRAIT, topOrientation); } private void verifyProcessInfoUpdate(ActivityRecord activity, State state, diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 65c71255cd2f..753cc623cf25 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -384,7 +384,25 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testTranslucentActivitiesDontGoInSizeCompactMode() { + public void testNotApplyStrategyToTranslucentActivitiesOverEmbeddedActivities() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT; + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Mock the activity as embedded without additional TaskFragment layer in the task for + // simplicity. + doReturn(true).when(mActivity).isEmbedded(); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).build(); + doReturn(false).when(translucentActivity).matchParentBounds(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Check the strategy has not being applied + assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + } + + @Test + public void testTranslucentActivitiesDontGoInSizeCompatMode() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -2472,11 +2490,11 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mActivity.inSizeCompatMode()); mActivity.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); - - assertTrue(mActivity.inSizeCompatMode()); - // We should remember the original orientation. + // Activity is not in size compat mode because the orientation change request came from the + // app itself + assertFalse(mActivity.inSizeCompatMode()); assertEquals(mActivity.getResolvedOverrideConfiguration().orientation, - Configuration.ORIENTATION_PORTRAIT); + Configuration.ORIENTATION_UNDEFINED); } @Test @@ -3051,6 +3069,25 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testAppRequestsOrientationChange_notInSizeCompat() { + setUpDisplaySizeWithApp(2200, 1800); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + + mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + + // Activity is not in size compat mode because the orientation change request came from the + // app itself + assertFalse(mActivity.inSizeCompatMode()); + + rotateDisplay(mActivity.mDisplayContent, ROTATION_270); + // Activity should go into size compat mode now because the orientation change came from the + // system (device rotation) + assertTrue(mActivity.inSizeCompatMode()); + } + + @Test public void testLetterboxDetailsForStatusBar_noLetterbox() { setUpDisplaySizeWithApp(2800, 1000); addStatusBar(mActivity.mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java index c5387272f0bf..e30206ee0a64 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java @@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit; @Presubmit public class SurfaceSyncGroupTest { private static final String TAG = "SurfaceSyncGroupTest"; + private static final int TIMEOUT_MS = 100; private final Executor mExecutor = Runnable::run; @@ -86,7 +87,7 @@ public class SurfaceSyncGroupTest { syncTarget2.markSyncReady(); - finishedLatch.await(5, TimeUnit.SECONDS); + finishedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch.getCount()); } @@ -124,13 +125,13 @@ public class SurfaceSyncGroupTest { syncTarget1.markSyncReady(); - finishedLatch1.await(5, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); assertNotEquals(0, finishedLatch2.getCount()); syncTarget2.markSyncReady(); - finishedLatch2.await(5, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch2.getCount()); } @@ -156,17 +157,17 @@ public class SurfaceSyncGroupTest { // Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync // is also done. syncTarget2.markSyncReady(); - finishedLatch2.await(1, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); // Sync did not complete yet assertNotEquals(0, finishedLatch2.getCount()); syncTarget1.markSyncReady(); // The first sync will still get a callback when it's sync requirements are done. - finishedLatch1.await(5, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); - finishedLatch2.await(5, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch2.getCount()); } @@ -189,7 +190,7 @@ public class SurfaceSyncGroupTest { syncTarget1.markSyncReady(); // The first sync will still get a callback when it's sync requirements are done. - finishedLatch1.await(5, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); syncGroup2.add(syncGroup1, null /* runnable */); @@ -198,7 +199,7 @@ public class SurfaceSyncGroupTest { // Verify that the second sync will receive complete since the merged sync was already // completed before the merge. - finishedLatch2.await(5, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch2.getCount()); } @@ -232,8 +233,8 @@ public class SurfaceSyncGroupTest { syncTarget3.markSyncReady(); // Neither SyncGroup will be ready. - finishedLatch1.await(1, TimeUnit.SECONDS); - finishedLatch2.await(1, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(1, finishedLatch1.getCount()); assertEquals(1, finishedLatch2.getCount()); @@ -241,8 +242,8 @@ public class SurfaceSyncGroupTest { syncTarget2.markSyncReady(); // Both sync groups should be ready after target2 completed. - finishedLatch1.await(5, TimeUnit.SECONDS); - finishedLatch2.await(5, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); assertEquals(0, finishedLatch2.getCount()); } @@ -275,8 +276,8 @@ public class SurfaceSyncGroupTest { syncTarget1.markSyncReady(); // Only SyncGroup1 will be ready, but SyncGroup2 still needs its own targets to be ready. - finishedLatch1.await(1, TimeUnit.SECONDS); - finishedLatch2.await(1, TimeUnit.SECONDS); + finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch1.getCount()); assertEquals(1, finishedLatch2.getCount()); @@ -284,7 +285,7 @@ public class SurfaceSyncGroupTest { syncTarget3.markSyncReady(); // SyncGroup2 is finished after target3 completed. - finishedLatch2.await(1, TimeUnit.SECONDS); + finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch2.getCount()); } @@ -357,6 +358,27 @@ public class SurfaceSyncGroupTest { } catch (InterruptedException e) { throw new RuntimeException(e); } + + assertEquals(0, finishedLatch.getCount()); + } + + public void testSurfaceSyncGroupTimeout() throws InterruptedException { + final CountDownLatch finishedLatch = new CountDownLatch(1); + SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG); + syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown); + SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1"); + SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2"); + + syncGroup.add(syncTarget1, null /* runnable */); + syncGroup.add(syncTarget2, null /* runnable */); + syncGroup.markSyncReady(); + + syncTarget1.markSyncReady(); + assertNotEquals(0, finishedLatch.getCount()); + + // Never finish syncTarget2 so it forces the timeout. Timeout is 1 second so wait a little + // over 1 second to make sure it completes. + finishedLatch.await(1100, TimeUnit.MILLISECONDS); assertEquals(0, finishedLatch.getCount()); } } diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index cbac34f50a96..0325ba648af6 100644 --- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -31,6 +31,7 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.DeviceConfig; import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyManager; @@ -258,6 +259,9 @@ public final class TelephonyUtils { */ public static void showErrorIfSubscriptionAssociatedWithManagedProfile(Context context, int subId) { + if (!isSwitchToManagedProfileDialogFlagEnabled()) { + return; + } final long token = Binder.clearCallingIdentity(); try { SubscriptionManager subscriptionManager = context.getSystemService( @@ -286,6 +290,11 @@ public final class TelephonyUtils { } } + public static boolean isSwitchToManagedProfileDialogFlagEnabled() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, + "enable_switch_to_managed_profile_dialog", false); + } + /** * Check if the process with given uid is foreground. * diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index 604641520252..15fd817ba73b 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -161,9 +161,9 @@ public class SharedConnectivityManager { Resources resources = context.getResources(); try { String servicePackageName = resources.getString( - R.string.shared_connectivity_service_package); + R.string.config_sharedConnectivityServicePackage); String serviceIntentAction = resources.getString( - R.string.shared_connectivity_service_intent_action); + R.string.config_sharedConnectivityServiceIntentAction); return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction); } catch (Resources.NotFoundException e) { Log.e(TAG, "To support shared connectivity service on this device, the service's" diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java index c53da9c15d4d..57108e4aa227 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -25,6 +25,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.wifi.sharedconnectivity.app.HotspotNetwork; @@ -40,8 +41,11 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; +import com.android.internal.R; + import java.util.Collections; import java.util.List; +import java.util.Objects; /** @@ -382,6 +386,30 @@ public abstract class SharedConnectivityService extends Service { } /** + * System and settings UI support on the device for instant tether. + * @return True if the UI can display Instant Tether network data. False otherwise. + */ + public static boolean areHotspotNetworksEnabledForService(@NonNull Context context) { + String servicePackage = context.getResources() + .getString(R.string.config_sharedConnectivityServicePackage); + return Objects.equals(context.getPackageName(), servicePackage) + && context.getResources() + .getBoolean(R.bool.config_hotspotNetworksEnabledForService); + } + + /** + * System and settings UI support on the device for known networks. + * @return True if the UI can display known networks data. False otherwise. + */ + public static boolean areKnownNetworksEnabledForService(@NonNull Context context) { + String servicePackage = context.getResources() + .getString(R.string.config_sharedConnectivityServicePackage); + return Objects.equals(context.getPackageName(), servicePackage) + && context.getResources() + .getBoolean(R.bool.config_knownNetworksEnabledForService); + } + + /** * Implementing application should implement this method. * * Implementation should initiate a connection to the Hotspot Network indicated. diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java index 19effe5d6f14..b8b6b767eed3 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java @@ -26,10 +26,12 @@ import static android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.DEVICE import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.net.wifi.sharedconnectivity.app.HotspotNetwork; import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus; import android.net.wifi.sharedconnectivity.app.KnownNetwork; @@ -86,6 +88,9 @@ public class SharedConnectivityServiceTest { @Mock Context mContext; + @Mock + Resources mResources; + static class FakeSharedConnectivityService extends SharedConnectivityService { public void attachBaseContext(Context context) { super.attachBaseContext(context); @@ -180,6 +185,48 @@ public class SharedConnectivityServiceTest { .isEqualTo(KNOWN_NETWORK_CONNECTION_STATUS); } + @Test + public void areHotspotNetworksEnabledForService() { + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageName()).thenReturn("package"); + when(mResources.getString(anyInt())).thenReturn("package"); + when(mResources.getBoolean(anyInt())).thenReturn(true); + + assertThat(SharedConnectivityService.areHotspotNetworksEnabledForService(mContext)) + .isTrue(); + } + + @Test + public void areHotspotNetworksEnabledForService_notSamePackage_shouldReturnFalse() { + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageName()).thenReturn("package"); + when(mResources.getString(anyInt())).thenReturn("other_package"); + when(mResources.getBoolean(anyInt())).thenReturn(true); + + assertThat(SharedConnectivityService.areHotspotNetworksEnabledForService(mContext)) + .isFalse(); + } + + @Test + public void areKnownNetworksEnabledForService() { + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageName()).thenReturn("package"); + when(mResources.getString(anyInt())).thenReturn("package"); + when(mResources.getBoolean(anyInt())).thenReturn(true); + + assertThat(SharedConnectivityService.areKnownNetworksEnabledForService(mContext)).isTrue(); + } + + @Test + public void areKnownNetworksEnabledForService_notSamePackage_shouldReturnFalse() { + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageName()).thenReturn("package"); + when(mResources.getString(anyInt())).thenReturn("other_package"); + when(mResources.getBoolean(anyInt())).thenReturn(true); + + assertThat(SharedConnectivityService.areKnownNetworksEnabledForService(mContext)).isFalse(); + } + private SharedConnectivityService createService() { FakeSharedConnectivityService service = new FakeSharedConnectivityService(); service.attachBaseContext(mContext); |