diff --git a/Android.bp b/Android.bp index 9d48cf81e998761fd6020fa4478dcd8d2ed57f89..80b4c76e9c6ba3666c89adf500b231540c831b30 100644 --- a/Android.bp +++ b/Android.bp @@ -110,6 +110,7 @@ filegroup { ":framework_native_aidl", ":gatekeeper_aidl", ":gsiservice_aidl", + ":guiconstants_aidl", ":idmap2_aidl", ":idmap2_core_aidl", ":incidentcompanion_aidl", @@ -319,6 +320,8 @@ java_defaults { "modules-utils-synchronous-result-receiver", "modules-utils-os", "framework-permission-aidl-java", + "spatializer-aidl-java", + "audiopolicy-types-aidl-java", ], } diff --git a/apct-tests/perftests/autofill/AndroidManifest.xml b/apct-tests/perftests/autofill/AndroidManifest.xml index 57595a213d2019260ab88cc39a64d3f773a02024..de2a3f2c4bfceb999e210592c40dd679c8ecc703 100644 --- a/apct-tests/perftests/autofill/AndroidManifest.xml +++ b/apct-tests/perftests/autofill/AndroidManifest.xml @@ -16,6 +16,16 @@ + + + + + + + + + + CREATOR; @@ -16747,6 +16754,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key EDGE_AVAILABLE_EDGE_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key FLASH_INFO_AVAILABLE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key INFO_SUPPORTED_HARDWARE_LEVEL; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key INFO_VERSION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key JPEG_AVAILABLE_THUMBNAIL_SIZES; @@ -17441,6 +17449,12 @@ package android.hardware.camera2.params { method public android.util.Rational getElement(int, int); } + public final class DeviceStateSensorOrientationMap { + method public int getSensorOrientation(long); + field public static final long FOLDED = 4L; // 0x4L + field public static final long NORMAL = 0L; // 0x0L + } + public final class ExtensionSessionConfiguration { ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback); method @NonNull public java.util.concurrent.Executor getExecutor(); @@ -18982,8 +18996,10 @@ package android.media { method public int getAllowedCapturePolicy(); method public int getContentType(); method public int getFlags(); + method public int getSpatializationBehavior(); method public int getUsage(); method public int getVolumeControlStream(); + method public boolean isContentSpatialized(); method public void writeToParcel(android.os.Parcel, int); field public static final int ALLOW_CAPTURE_BY_ALL = 1; // 0x1 field public static final int ALLOW_CAPTURE_BY_NONE = 3; // 0x3 @@ -18997,6 +19013,8 @@ package android.media { field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1 field public static final int FLAG_HW_AV_SYNC = 16; // 0x10 field @Deprecated public static final int FLAG_LOW_LATENCY = 256; // 0x100 + field public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0; // 0x0 + field public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1; // 0x1 field public static final int USAGE_ALARM = 4; // 0x4 field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc @@ -19023,7 +19041,9 @@ package android.media { method public android.media.AudioAttributes.Builder setContentType(int); method public android.media.AudioAttributes.Builder setFlags(int); method @NonNull public android.media.AudioAttributes.Builder setHapticChannelsMuted(boolean); + method @NonNull public android.media.AudioAttributes.Builder setIsContentSpatialized(boolean); method public android.media.AudioAttributes.Builder setLegacyStreamType(int); + method @NonNull public android.media.AudioAttributes.Builder setSpatializationBehavior(int); method public android.media.AudioAttributes.Builder setUsage(int); } @@ -19141,24 +19161,45 @@ package android.media { field public static final int CHANNEL_IN_Y_AXIS = 4096; // 0x1000 field public static final int CHANNEL_IN_Z_AXIS = 8192; // 0x2000 field public static final int CHANNEL_OUT_5POINT1 = 252; // 0xfc + field public static final int CHANNEL_OUT_5POINT1POINT2 = 3145980; // 0x3000fc + field public static final int CHANNEL_OUT_5POINT1POINT4 = 737532; // 0xb40fc field @Deprecated public static final int CHANNEL_OUT_7POINT1 = 1020; // 0x3fc + field public static final int CHANNEL_OUT_7POINT1POINT2 = 3152124; // 0x3018fc + field public static final int CHANNEL_OUT_7POINT1POINT4 = 743676; // 0xb58fc field public static final int CHANNEL_OUT_7POINT1_SURROUND = 6396; // 0x18fc + field public static final int CHANNEL_OUT_9POINT1POINT4 = 202070268; // 0xc0b58fc + field public static final int CHANNEL_OUT_9POINT1POINT6 = 205215996; // 0xc3b58fc field public static final int CHANNEL_OUT_BACK_CENTER = 1024; // 0x400 field public static final int CHANNEL_OUT_BACK_LEFT = 64; // 0x40 field public static final int CHANNEL_OUT_BACK_RIGHT = 128; // 0x80 + field public static final int CHANNEL_OUT_BOTTOM_FRONT_CENTER = 8388608; // 0x800000 + field public static final int CHANNEL_OUT_BOTTOM_FRONT_LEFT = 4194304; // 0x400000 + field public static final int CHANNEL_OUT_BOTTOM_FRONT_RIGHT = 16777216; // 0x1000000 field public static final int CHANNEL_OUT_DEFAULT = 1; // 0x1 field public static final int CHANNEL_OUT_FRONT_CENTER = 16; // 0x10 field public static final int CHANNEL_OUT_FRONT_LEFT = 4; // 0x4 field public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 256; // 0x100 field public static final int CHANNEL_OUT_FRONT_RIGHT = 8; // 0x8 field public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 512; // 0x200 + field public static final int CHANNEL_OUT_FRONT_WIDE_LEFT = 67108864; // 0x4000000 + field public static final int CHANNEL_OUT_FRONT_WIDE_RIGHT = 134217728; // 0x8000000 field public static final int CHANNEL_OUT_LOW_FREQUENCY = 32; // 0x20 + field public static final int CHANNEL_OUT_LOW_FREQUENCY_2 = 33554432; // 0x2000000 field public static final int CHANNEL_OUT_MONO = 4; // 0x4 field public static final int CHANNEL_OUT_QUAD = 204; // 0xcc field public static final int CHANNEL_OUT_SIDE_LEFT = 2048; // 0x800 field public static final int CHANNEL_OUT_SIDE_RIGHT = 4096; // 0x1000 field public static final int CHANNEL_OUT_STEREO = 12; // 0xc field public static final int CHANNEL_OUT_SURROUND = 1052; // 0x41c + field public static final int CHANNEL_OUT_TOP_BACK_CENTER = 262144; // 0x40000 + field public static final int CHANNEL_OUT_TOP_BACK_LEFT = 131072; // 0x20000 + field public static final int CHANNEL_OUT_TOP_BACK_RIGHT = 524288; // 0x80000 + field public static final int CHANNEL_OUT_TOP_CENTER = 8192; // 0x2000 + field public static final int CHANNEL_OUT_TOP_FRONT_CENTER = 32768; // 0x8000 + field public static final int CHANNEL_OUT_TOP_FRONT_LEFT = 16384; // 0x4000 + field public static final int CHANNEL_OUT_TOP_FRONT_RIGHT = 65536; // 0x10000 + field public static final int CHANNEL_OUT_TOP_SIDE_LEFT = 1048576; // 0x100000 + field public static final int CHANNEL_OUT_TOP_SIDE_RIGHT = 2097152; // 0x200000 field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int ENCODING_AAC_ELD = 15; // 0xf field public static final int ENCODING_AAC_HE_V1 = 11; // 0xb @@ -19228,6 +19269,7 @@ package android.media { method public String getProperty(String); method public int getRingerMode(); method @Deprecated public int getRouting(int); + method @NonNull public android.media.Spatializer getSpatializer(); method public int getStreamMaxVolume(int); method public int getStreamMinVolume(int); method public int getStreamVolume(int); @@ -21353,6 +21395,7 @@ package android.media { field public static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder"; field public static final String KEY_MAX_HEIGHT = "max-height"; field public static final String KEY_MAX_INPUT_SIZE = "max-input-size"; + field public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = "max-output-channel-count"; field public static final String KEY_MAX_PTS_GAP_TO_ENCODER = "max-pts-gap-to-encoder"; field public static final String KEY_MAX_WIDTH = "max-width"; field public static final String KEY_MIME = "mime"; @@ -22641,6 +22684,23 @@ package android.media { method public void onLoadComplete(android.media.SoundPool, int, int); } + public class Spatializer { + method public void addOnSpatializerStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerStateChangedListener); + method public boolean canBeSpatialized(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioFormat); + method public int getImmersiveAudioLevel(); + method public boolean isAvailable(); + method public boolean isEnabled(); + method public void removeOnSpatializerStateChangedListener(@NonNull android.media.Spatializer.OnSpatializerStateChangedListener); + field public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; // 0x1 + field public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; // 0x0 + field public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; // 0xffffffff + } + + public static interface Spatializer.OnSpatializerStateChangedListener { + method public void onSpatializerAvailableChanged(@NonNull android.media.Spatializer, boolean); + method public void onSpatializerEnabledChanged(@NonNull android.media.Spatializer, boolean); + } + public final class SubtitleData { ctor public SubtitleData(int, long, long, @NonNull byte[]); method @NonNull public byte[] getData(); @@ -29815,8 +29875,8 @@ package android.os { ctor public Environment(); method public static java.io.File getDataDirectory(); method public static java.io.File getDownloadCacheDirectory(); - method @Deprecated public static java.io.File getExternalStorageDirectory(); - method @Deprecated public static java.io.File getExternalStoragePublicDirectory(String); + method public static java.io.File getExternalStorageDirectory(); + method public static java.io.File getExternalStoragePublicDirectory(String); method public static String getExternalStorageState(); method public static String getExternalStorageState(java.io.File); method @NonNull public static java.io.File getRootDirectory(); @@ -33958,6 +34018,7 @@ package android.provider { field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS"; field public static final String ACTION_SETTINGS = "android.settings.SETTINGS"; + field public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY"; field public static final String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO"; field public static final String ACTION_SHOW_WORK_POLICY_INFO = "android.settings.SHOW_WORK_POLICY_INFO"; field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS"; @@ -33998,6 +34059,8 @@ package android.provider { field public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE"; field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; field public static final String EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME = "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME"; + field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY"; + field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI"; field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID"; field public static final String EXTRA_WIFI_NETWORK_LIST = "android.provider.extra.WIFI_NETWORK_LIST"; field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST"; @@ -37800,6 +37863,13 @@ package android.service.textservice { package android.service.voice { + public final class VisibleActivityInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.service.voice.VoiceInteractionSession.ActivityId getActivityId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); method public int getDisabledShowContext(); @@ -37861,6 +37931,7 @@ package android.service.voice { method public void onTaskStarted(android.content.Intent, int); method public void onTrimMemory(int); method public final void performDirectAction(@NonNull android.app.DirectAction, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method public final void registerVisibleActivityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback); method public final void requestDirectActions(@NonNull android.service.voice.VoiceInteractionSession.ActivityId, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer>); method public void setContentView(android.view.View); method public void setDisabledShowContext(int); @@ -37870,6 +37941,7 @@ package android.service.voice { method public void show(android.os.Bundle, int); method public void startAssistantActivity(android.content.Intent); method public void startVoiceActivity(android.content.Intent); + method public final void unregisterVisibleActivityCallback(@NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback); field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10 field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8 field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4 @@ -37943,6 +38015,11 @@ package android.service.voice { method public boolean isActive(); } + public static interface VoiceInteractionSession.VisibleActivityCallback { + method public default void onInvisible(@NonNull android.service.voice.VoiceInteractionSession.ActivityId); + method public default void onVisible(@NonNull android.service.voice.VisibleActivityInfo); + } + public abstract class VoiceInteractionSessionService extends android.app.Service { ctor public VoiceInteractionSessionService(); method public android.os.IBinder onBind(android.content.Intent); @@ -45830,8 +45907,15 @@ package android.view { } @UiThread public interface AttachedSurfaceControl { + method public default void addOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener); method public boolean applyTransactionOnDraw(@NonNull android.view.SurfaceControl.Transaction); method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl); + method public default int getBufferTransformHint(); + method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener); + } + + @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener { + method public void onBufferTransformHintChanged(int); } public final class Choreographer { @@ -47337,6 +47421,12 @@ package android.view { method public void readFromParcel(android.os.Parcel); method public void release(); method public void writeToParcel(android.os.Parcel, int); + field public static final int BUFFER_TRANSFORM_IDENTITY = 0; // 0x0 + field public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 1; // 0x1 + field public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 2; // 0x2 + field public static final int BUFFER_TRANSFORM_ROTATE_180 = 3; // 0x3 + field public static final int BUFFER_TRANSFORM_ROTATE_270 = 7; // 0x7 + field public static final int BUFFER_TRANSFORM_ROTATE_90 = 4; // 0x4 field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -48199,6 +48289,7 @@ package android.view { field public static final int AUTOFILL_TYPE_NONE = 0; // 0x0 field public static final int AUTOFILL_TYPE_TEXT = 1; // 0x1 field public static final int AUTOFILL_TYPE_TOGGLE = 2; // 0x2 + field public static final int DRAG_FLAG_ACCESSIBILITY_ACTION = 1024; // 0x400 field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100 field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40 field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80 @@ -49625,6 +49716,9 @@ package android.view.accessibility { method public void setPackageName(CharSequence); method public void writeToParcel(android.os.Parcel, int); field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4 + field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200 + field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100 + field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80 field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10 field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20 field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8 @@ -49923,6 +50017,9 @@ package android.view.accessibility { field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_COPY; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_CUT; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DISMISS; + field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_CANCEL; + field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_DROP; + field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_START; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS; field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 9db26bd1e373ea05afa56e02e48f375652108635..80ee1a583aa925465309c4e80c98c881a6d5359f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -26,6 +26,7 @@ package android { 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 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 AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES"; @@ -442,6 +443,11 @@ package android.app { method public void onUidImportance(int, int); } + public class ActivityOptions { + method public int getLaunchTaskId(); + method @RequiresPermission("android.permission.START_TASKS_FROM_RECENTS") public void setLaunchTaskId(int); + } + public class AlarmManager { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource); @@ -2931,11 +2937,13 @@ package android.hardware.display { public final class DisplayManager { method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS) public java.util.List getAmbientBrightnessStats(); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration(); + method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfigurationForDisplay(@NonNull String); method @RequiresPermission(android.Manifest.permission.BRIGHTNESS_SLIDER_USAGE) public java.util.List getBrightnessEvents(); method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration(); method public android.util.Pair getMinimumBrightnessCurve(); method public android.graphics.Point getStableDisplaySize(); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); + method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float); } @@ -5041,6 +5049,46 @@ package android.media { field public static final android.media.RouteDiscoveryPreference EMPTY; } + public class Spatializer { + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addOnHeadTrackingModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnHeadToSoundstagePoseUpdatedListener(); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnSpatializerOutputChangedListener(); + method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List getCompatibleAudioDevices(); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getDesiredHeadTrackingMode(); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void getEffectParameter(int, @NonNull byte[]); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode(); + method @IntRange(from=0) @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getOutput(); + method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List getSupportedHeadTrackingModes(); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker(); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeOnHeadTrackingModeChangedListener(@NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setDesiredHeadTrackingMode(int); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEffectParameter(int, @NonNull byte[]); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnSpatializerOutputChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerOutputChangedListener); + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_DISABLED = -1; // 0xffffffff + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_OTHER = 0; // 0x0 + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; // 0x2 + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1; // 0x1 + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2; // 0xfffffffe + } + + public static interface Spatializer.OnHeadToSoundstagePoseUpdatedListener { + method public void onHeadToSoundstagePoseUpdated(@NonNull android.media.Spatializer, @NonNull float[]); + } + + public static interface Spatializer.OnHeadTrackingModeChangedListener { + method public void onDesiredHeadTrackingModeChanged(@NonNull android.media.Spatializer, int); + method public void onHeadTrackingModeChanged(@NonNull android.media.Spatializer, int); + } + + public static interface Spatializer.OnSpatializerOutputChangedListener { + method public void onSpatializerOutputChanged(@NonNull android.media.Spatializer, @IntRange(from=0) int); + } + } package android.media.audiofx { @@ -14102,6 +14150,7 @@ package android.view.contentcapture { method public int getFlags(); method @Nullable public android.view.contentcapture.ContentCaptureSessionId getParentSessionId(); method public int getTaskId(); + method @Nullable public android.os.IBinder getWindowToken(); field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1 field public static final int FLAG_DISABLED_BY_FLAG_SECURE = 2; // 0x2 field public static final int FLAG_RECONNECTED = 4; // 0x4 @@ -14109,6 +14158,7 @@ package android.view.contentcapture { public final class ContentCaptureEvent implements android.os.Parcelable { method public int describeContents(); + method @Nullable public android.graphics.Rect getBounds(); method @Nullable public android.view.contentcapture.ContentCaptureContext getContentCaptureContext(); method public long getEventTime(); method @Nullable public android.view.autofill.AutofillId getId(); @@ -14128,6 +14178,7 @@ package android.view.contentcapture { field public static final int TYPE_VIEW_TEXT_CHANGED = 3; // 0x3 field public static final int TYPE_VIEW_TREE_APPEARED = 5; // 0x5 field public static final int TYPE_VIEW_TREE_APPEARING = 4; // 0x4 + field public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10; // 0xa } public final class ContentCaptureManager { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 73caa5d5f0d43ad2634e2fefacc82c53522bb920..00dcb6fd0543d3753ebaf1624db3a957b4d5c9c8 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -113,6 +113,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors(); method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List, int); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle(); @@ -127,6 +128,9 @@ package android.app { field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0 field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4 field public static final int PROCESS_STATE_TOP = 2; // 0x2 + field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff + field public static final int STOP_USER_ON_SWITCH_FALSE = 0; // 0x0 + field public static final int STOP_USER_ON_SWITCH_TRUE = 1; // 0x1 } public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable { @@ -144,7 +148,6 @@ package android.app { method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener); method public static void setExitTransitionTimeout(long); method public void setLaunchActivityType(int); - method public void setLaunchTaskId(int); method public void setLaunchWindowingMode(int); method public void setLaunchedFromBubble(boolean); method public void setTaskAlwaysOnTop(boolean); @@ -431,6 +434,7 @@ package android.app.admin { public class DevicePolicyManager { method public int checkProvisioningPreCondition(@Nullable String, @NonNull String); + method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void clearOrganizationId(); method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord(); method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs(); @@ -453,6 +457,7 @@ package android.app.admin { method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int); method @RequiresPermission(allOf={"android.permission.MANAGE_DEVICE_ADMINS", android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int); method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int); + method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int); method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int); field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED"; @@ -774,6 +779,7 @@ package android.content.pm { field public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 1.7777778f; field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f; + field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2 } @@ -1127,10 +1133,10 @@ package android.hardware.camera2 { package android.hardware.devicestate { public final class DeviceStateManager { - method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest); + method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest); method @NonNull public int[] getSupportedStates(); method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback); - method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); + method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback); field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0 @@ -1275,6 +1281,12 @@ package android.hardware.soundtrigger { package android.inputmethodservice { + public abstract class AbstractInputMethodService extends android.window.WindowProviderService implements android.view.KeyEvent.Callback { + method public final int getInitialDisplayId(); + method @Nullable public final android.os.Bundle getWindowContextOptions(); + method public final int getWindowType(); + } + @UiContext public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService { field public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // 0x94fa793L } @@ -2103,6 +2115,7 @@ package android.provider { public final class DeviceConfig { field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager"; field public static final String NAMESPACE_ANDROID = "android"; + field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides"; field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis"; field public static final String NAMESPACE_DEVICE_IDLE = "device_idle"; field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler"; @@ -2365,6 +2378,10 @@ package android.service.voice { method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[]); } + public final class VisibleActivityInfo implements android.os.Parcelable { + ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder); + } + } package android.service.watchdog { @@ -3159,13 +3176,6 @@ package android.window { method @Nullable public android.view.View getBrandingView(); } - public final class StartingWindowInfo implements android.os.Parcelable { - ctor public StartingWindowInfo(); - method public int describeContents(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - public final class TaskAppearedInfo implements android.os.Parcelable { ctor public TaskAppearedInfo(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl); method public int describeContents(); @@ -3175,9 +3185,57 @@ package android.window { field @NonNull public static final android.os.Parcelable.Creator CREATOR; } + public final class TaskFragmentCreationParams implements android.os.Parcelable { + method @NonNull public android.os.IBinder getFragmentToken(); + method @NonNull public android.graphics.Rect getInitialBounds(); + method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer(); + method @NonNull public android.os.IBinder getOwnerToken(); + method public int getWindowingMode(); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class TaskFragmentCreationParams.Builder { + ctor public TaskFragmentCreationParams.Builder(@NonNull android.window.TaskFragmentOrganizerToken, @NonNull android.os.IBinder, @NonNull android.os.IBinder); + method @NonNull public android.window.TaskFragmentCreationParams build(); + method @NonNull public android.window.TaskFragmentCreationParams.Builder setInitialBounds(@NonNull android.graphics.Rect); + method @NonNull public android.window.TaskFragmentCreationParams.Builder setWindowingMode(int); + } + + public final class TaskFragmentInfo implements android.os.Parcelable { + method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo); + method @NonNull public java.util.List getActivities(); + method @NonNull public android.content.res.Configuration getConfiguration(); + method @NonNull public android.os.IBinder getFragmentToken(); + method @NonNull public android.graphics.Point getPositionInParent(); + method public int getRunningActivityCount(); + method @NonNull public android.window.WindowContainerToken getToken(); + method public int getWindowingMode(); + method public boolean hasRunningActivity(); + method public boolean isEmpty(); + method public boolean isTaskClearedForReuse(); + method public boolean isVisible(); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public class TaskFragmentOrganizer extends android.window.WindowOrganizer { + ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor); + method @NonNull public java.util.concurrent.Executor getExecutor(); + method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken(); + method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo); + method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable); + method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo); + method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration); + method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo); + method @CallSuper public void registerOrganizer(); + method @CallSuper public void unregisterOrganizer(); + } + + public final class TaskFragmentOrganizerToken implements android.os.Parcelable { + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public class TaskOrganizer extends android.window.WindowOrganizer { ctor public TaskOrganizer(); - method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder); method @BinderThread public void copySplashScreenView(int); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken); @@ -3190,7 +3248,6 @@ package android.window { method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo); method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo); method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List registerOrganizer(); - method @BinderThread public void removeStartingWindow(int, @Nullable android.view.SurfaceControl, @Nullable android.graphics.Rect, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean); method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer(); } @@ -3203,26 +3260,42 @@ package android.window { public final class WindowContainerTransaction implements android.os.Parcelable { ctor public WindowContainerTransaction(); + method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams); + method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken); method public int describeContents(); method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean); method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean); + method @NonNull public android.window.WindowContainerTransaction reparentActivityToTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder); + method @NonNull public android.window.WindowContainerTransaction reparentChildren(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken); method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean); method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int); - method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken); + method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken, boolean); + method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @Nullable android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams); method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction); + method @NonNull public android.window.WindowContainerTransaction setErrorCallbackToken(@NonNull android.os.IBinder); method @NonNull public android.window.WindowContainerTransaction setFocusable(@NonNull android.window.WindowContainerToken, boolean); method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean); method @NonNull public android.window.WindowContainerTransaction setLaunchRoot(@NonNull android.window.WindowContainerToken, @Nullable int[], @Nullable int[]); method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int); method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int); method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int); + method @NonNull public android.window.WindowContainerTransaction startActivityInTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder, @NonNull android.content.Intent, @Nullable android.os.Bundle); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } + public static class WindowContainerTransaction.TaskFragmentAdjacentParams { + ctor public WindowContainerTransaction.TaskFragmentAdjacentParams(); + ctor public WindowContainerTransaction.TaskFragmentAdjacentParams(@NonNull android.os.Bundle); + method public void setShouldDelayPrimaryLastActivityRemoval(boolean); + method public void setShouldDelaySecondaryLastActivityRemoval(boolean); + method public boolean shouldDelayPrimaryLastActivityRemoval(); + method public boolean shouldDelaySecondaryLastActivityRemoval(); + } + public abstract class WindowContainerTransactionCallback { ctor public WindowContainerTransactionCallback(); method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction); @@ -3230,14 +3303,15 @@ package android.window { public class WindowOrganizer { ctor public WindowOrganizer(); - method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); - method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void applyTransaction(@NonNull android.window.WindowContainerTransaction); + method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); + method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public void applyTransaction(@NonNull android.window.WindowContainerTransaction); } @UiContext public abstract class WindowProviderService extends android.app.Service { ctor public WindowProviderService(); method public final void attachToWindowToken(@NonNull android.os.IBinder); - method @Nullable public android.os.Bundle getWindowContextOptions(); + method @NonNull public int getInitialDisplayId(); + method @CallSuper @Nullable public android.os.Bundle getWindowContextOptions(); method public abstract int getWindowType(); } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index b31d8f70a7d0f568d7c027a9df03c587dde71dc7..325be600e45446baa9d62f53cf256b28c7332174 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -16,6 +16,8 @@ package android.accessibilityservice; +import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; + import android.accessibilityservice.GestureDescription.MotionEventGenerator; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; @@ -27,6 +29,7 @@ import android.annotation.TestApi; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; @@ -36,6 +39,7 @@ import android.graphics.Region; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -514,7 +518,9 @@ public abstract class AccessibilityService extends Service { public static final int GLOBAL_ACTION_POWER_DIALOG = 6; /** - * Action to toggle docking the current app's window + * Action to toggle docking the current app's window. + *

+ * Note: It is effective only if it appears in {@link #getSystemActions()}. */ public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; @@ -961,30 +967,31 @@ public abstract class AccessibilityService extends Service { } } + @NonNull @Override public Context createDisplayContext(Display display) { - final Context context = super.createDisplayContext(display); - final int displayId = display.getDisplayId(); - setDefaultTokenInternal(context, displayId); - return context; + return new AccessibilityContext(super.createDisplayContext(display), mConnectionId); } - private void setDefaultTokenInternal(Context context, int displayId) { - final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(WINDOW_SERVICE); - final IAccessibilityServiceConnection connection = - AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId); - IBinder token = null; - if (connection != null) { - synchronized (mLock) { - try { - token = connection.getOverlayWindowToken(displayId); - } catch (RemoteException re) { - Log.w(LOG_TAG, "Failed to get window token", re); - re.rethrowFromSystemServer(); - } - } - wm.setDefaultToken(token); + @NonNull + @Override + public Context createWindowContext(int type, @Nullable Bundle options) { + final Context context = super.createWindowContext(type, options); + if (type != TYPE_ACCESSIBILITY_OVERLAY) { + return context; } + return new AccessibilityContext(context, mConnectionId); + } + + @NonNull + @Override + public Context createWindowContext(@NonNull Display display, int type, + @Nullable Bundle options) { + final Context context = super.createWindowContext(display, type, options); + if (type != TYPE_ACCESSIBILITY_OVERLAY) { + return context; + } + return new AccessibilityContext(context, mConnectionId); } /** @@ -2069,6 +2076,10 @@ public abstract class AccessibilityService extends Service { if (WINDOW_SERVICE.equals(name)) { if (mWindowManager == null) { mWindowManager = (WindowManager) getBaseContext().getSystemService(name); + final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager; + // Set e default token obtained from the connection to ensure client could use + // accessibility overlay. + wm.setDefaultToken(mWindowToken); } return mWindowManager; } @@ -2177,8 +2188,10 @@ public abstract class AccessibilityService extends Service { // The client may have already obtained the window manager, so // update the default token on whatever manager we gave them. - final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE); - wm.setDefaultToken(windowToken); + if (mWindowManager != null) { + final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager; + wm.setDefaultToken(mWindowToken); + } } @Override @@ -2675,4 +2688,58 @@ public abstract class AccessibilityService extends Service { } } } + + private static class AccessibilityContext extends ContextWrapper { + private final int mConnectionId; + + private AccessibilityContext(Context base, int connectionId) { + super(base); + mConnectionId = connectionId; + setDefaultTokenInternal(this, getDisplayId()); + } + + @NonNull + @Override + public Context createDisplayContext(Display display) { + return new AccessibilityContext(super.createDisplayContext(display), mConnectionId); + } + + @NonNull + @Override + public Context createWindowContext(int type, @Nullable Bundle options) { + final Context context = super.createWindowContext(type, options); + if (type != TYPE_ACCESSIBILITY_OVERLAY) { + return context; + } + return new AccessibilityContext(context, mConnectionId); + } + + @NonNull + @Override + public Context createWindowContext(@NonNull Display display, int type, + @Nullable Bundle options) { + final Context context = super.createWindowContext(display, type, options); + if (type != TYPE_ACCESSIBILITY_OVERLAY) { + return context; + } + return new AccessibilityContext(context, mConnectionId); + } + + private void setDefaultTokenInternal(Context context, int displayId) { + final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService( + WINDOW_SERVICE); + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getConnection(mConnectionId); + IBinder token = null; + if (connection != null) { + try { + token = connection.getOverlayWindowToken(displayId); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to get window token", re); + re.rethrowFromSystemServer(); + } + wm.setDefaultToken(token); + } + } + } } diff --git a/core/java/android/accessibilityservice/AccessibilityTrace.java b/core/java/android/accessibilityservice/AccessibilityTrace.java new file mode 100644 index 0000000000000000000000000000000000000000..f28015ab46851a15fe8ec1e6758ce2f40ce8df72 --- /dev/null +++ b/core/java/android/accessibilityservice/AccessibilityTrace.java @@ -0,0 +1,216 @@ +/** + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.accessibilityservice; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Interface to log accessibility trace. + * + * @hide + */ +public interface AccessibilityTrace { + String NAME_ACCESSIBILITY_SERVICE_CONNECTION = "IAccessibilityServiceConnection"; + String NAME_ACCESSIBILITY_SERVICE_CLIENT = "IAccessibilityServiceClient"; + String NAME_ACCESSIBILITY_MANAGER = "IAccessibilityManager"; + String NAME_ACCESSIBILITY_MANAGER_CLIENT = "IAccessibilityManagerClient"; + String NAME_ACCESSIBILITY_INTERACTION_CONNECTION = "IAccessibilityInteractionConnection"; + String NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = + "IAccessibilityInteractionConnectionCallback"; + String NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = "IRemoteMagnificationAnimationCallback"; + String NAME_WINDOW_MAGNIFICATION_CONNECTION = "IWindowMagnificationConnection"; + String NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = "IWindowMagnificationConnectionCallback"; + String NAME_WINDOW_MANAGER_INTERNAL = "WindowManagerInternal"; + String NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = "WindowsForAccessibilityCallback"; + String NAME_MAGNIFICATION_CALLBACK = "MagnificationCallbacks"; + String NAME_INPUT_FILTER = "InputFilter"; + String NAME_GESTURE = "Gesture"; + String NAME_ACCESSIBILITY_SERVICE = "AccessibilityService"; + String NAME_PACKAGE_BROADCAST_RECEIVER = "PMBroadcastReceiver"; + String NAME_USER_BROADCAST_RECEIVER = "UserBroadcastReceiver"; + String NAME_FINGERPRINT = "FingerprintGesture"; + String NAME_ACCESSIBILITY_INTERACTION_CLIENT = "AccessibilityInteractionClient"; + + String NAME_ALL_LOGGINGS = "AllLoggings"; + String NAME_NONE = "None"; + + long FLAGS_ACCESSIBILITY_SERVICE_CONNECTION = 0x0000000000000001L; + long FLAGS_ACCESSIBILITY_SERVICE_CLIENT = 0x0000000000000002L; + long FLAGS_ACCESSIBILITY_MANAGER = 0x0000000000000004L; + long FLAGS_ACCESSIBILITY_MANAGER_CLIENT = 0x0000000000000008L; + long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION = 0x0000000000000010L; + long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = 0x0000000000000020L; + long FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = 0x0000000000000040L; + long FLAGS_WINDOW_MAGNIFICATION_CONNECTION = 0x0000000000000080L; + long FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L; + long FLAGS_WINDOW_MANAGER_INTERNAL = 0x0000000000000200L; + long FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = 0x0000000000000400L; + long FLAGS_MAGNIFICATION_CALLBACK = 0x0000000000000800L; + long FLAGS_INPUT_FILTER = 0x0000000000001000L; + long FLAGS_GESTURE = 0x0000000000002000L; + long FLAGS_ACCESSIBILITY_SERVICE = 0x0000000000004000L; + long FLAGS_PACKAGE_BROADCAST_RECEIVER = 0x0000000000008000L; + long FLAGS_USER_BROADCAST_RECEIVER = 0x0000000000010000L; + long FLAGS_FINGERPRINT = 0x0000000000020000L; + long FLAGS_ACCESSIBILITY_INTERACTION_CLIENT = 0x0000000000040000L; + + long FLAGS_LOGGING_NONE = 0x0000000000000000L; + long FLAGS_LOGGING_ALL = 0xFFFFFFFFFFFFFFFFL; + + long FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES = FLAGS_ACCESSIBILITY_INTERACTION_CLIENT + | FLAGS_ACCESSIBILITY_SERVICE + | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION + | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK; + + Map sNamesToFlags = Map.ofEntries( + new AbstractMap.SimpleEntry( + NAME_ACCESSIBILITY_SERVICE_CONNECTION, FLAGS_ACCESSIBILITY_SERVICE_CONNECTION), + new AbstractMap.SimpleEntry( + NAME_ACCESSIBILITY_SERVICE_CLIENT, FLAGS_ACCESSIBILITY_SERVICE_CLIENT), + new AbstractMap.SimpleEntry( + NAME_ACCESSIBILITY_MANAGER, FLAGS_ACCESSIBILITY_MANAGER), + new AbstractMap.SimpleEntry( + NAME_ACCESSIBILITY_MANAGER_CLIENT, FLAGS_ACCESSIBILITY_MANAGER_CLIENT), + new AbstractMap.SimpleEntry( + NAME_ACCESSIBILITY_INTERACTION_CONNECTION, + FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION), + new AbstractMap.SimpleEntry( + NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK, + FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK), + new AbstractMap.SimpleEntry( + NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK, + FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK), + new AbstractMap.SimpleEntry( + NAME_WINDOW_MAGNIFICATION_CONNECTION, FLAGS_WINDOW_MAGNIFICATION_CONNECTION), + new AbstractMap.SimpleEntry( + NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK), + new AbstractMap.SimpleEntry( + NAME_WINDOW_MANAGER_INTERNAL, FLAGS_WINDOW_MANAGER_INTERNAL), + new AbstractMap.SimpleEntry( + NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, + FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK), + new AbstractMap.SimpleEntry( + NAME_MAGNIFICATION_CALLBACK, FLAGS_MAGNIFICATION_CALLBACK), + new AbstractMap.SimpleEntry(NAME_INPUT_FILTER, FLAGS_INPUT_FILTER), + new AbstractMap.SimpleEntry(NAME_GESTURE, FLAGS_GESTURE), + new AbstractMap.SimpleEntry( + NAME_ACCESSIBILITY_SERVICE, FLAGS_ACCESSIBILITY_SERVICE), + new AbstractMap.SimpleEntry( + NAME_PACKAGE_BROADCAST_RECEIVER, FLAGS_PACKAGE_BROADCAST_RECEIVER), + new AbstractMap.SimpleEntry( + NAME_USER_BROADCAST_RECEIVER, FLAGS_USER_BROADCAST_RECEIVER), + new AbstractMap.SimpleEntry(NAME_FINGERPRINT, FLAGS_FINGERPRINT), + new AbstractMap.SimpleEntry( + NAME_ACCESSIBILITY_INTERACTION_CLIENT, FLAGS_ACCESSIBILITY_INTERACTION_CLIENT), + new AbstractMap.SimpleEntry(NAME_NONE, FLAGS_LOGGING_NONE), + new AbstractMap.SimpleEntry(NAME_ALL_LOGGINGS, FLAGS_LOGGING_ALL)); + + /** + * Get the flags of the logging types by the given names. + * The names list contains logging type names in lower case. + */ + static long getLoggingFlagsFromNames(List names) { + long types = FLAGS_LOGGING_NONE; + for (String name : names) { + long flag = sNamesToFlags.get(name); + types |= flag; + } + return types; + } + + /** + * Get the list of the names of logging types by the given flags. + */ + static List getNamesOfLoggingTypes(long flags) { + List list = new ArrayList(); + + for (Map.Entry entry : sNamesToFlags.entrySet()) { + if ((entry.getValue() & flags) != FLAGS_LOGGING_NONE) { + list.add(entry.getKey()); + } + } + + return list; + } + + /** + * Whether the trace is enabled for any logging type. + */ + boolean isA11yTracingEnabled(); + + /** + * Whether the trace is enabled for any of the given logging type. + */ + boolean isA11yTracingEnabledForTypes(long typeIdFlags); + + /** + * Get trace state to be sent to AccessibilityManager. + */ + int getTraceStateForAccessibilityManagerClientState(); + + /** + * Start tracing for the given logging types. + */ + void startTrace(long flagss); + + /** + * Stop tracing. + */ + void stopTrace(); + + /** + * Log one trace entry. + * @param where A string to identify this log entry, which can be used to search through the + * tracing file. + * @param loggingFlags Flags to identify which logging types this entry belongs to. This + * can be used to filter the log entries when generating tracing file. + */ + void logTrace(String where, long loggingFlags); + + /** + * Log one trace entry. + * @param where A string to identify this log entry, which can be used to filter/search + * through the tracing file. + * @param loggingFlags Flags to identify which logging types this entry belongs to. This + * can be used to filter the log entries when generating tracing file. + * @param callingParams The parameters for the method to be logged. + */ + void logTrace(String where, long loggingFlags, String callingParams); + + /** + * Log one trace entry. Accessibility services using AccessibilityInteractionClient to + * make screen content related requests use this API to log entry when receive callback. + * @param timestamp The timestamp when a callback is received. + * @param where A string to identify this log entry, which can be used to filter/search + * through the tracing file. + * @param loggingFlags Flags to identify which logging types this entry belongs to. This + * can be used to filter the log entries when generating tracing file. + * @param callingParams The parameters for the callback. + * @param processId The process id of the calling component. + * @param threadId The threadId of the calling component. + * @param callingUid The calling uid of the callback. + * @param callStack The call stack of the callback. + * @param ignoreStackElements ignore these call stack element + */ + void logTrace(long timestamp, String where, long loggingFlags, String callingParams, + int processId, long threadId, int callingUid, StackTraceElement[] callStack, + Set ignoreStackElements); +} diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 923b6f41414a949144f54b021bbdf16fbb2fb200..1e76bbf433709aa189b0dcab56e966c6bdd4d7d5 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -118,6 +118,6 @@ interface IAccessibilityServiceConnection { void setFocusAppearance(int strokeWidth, int color); - oneway void logTrace(long timestamp, String where, String callingParams, int processId, - long threadId, int callingUid, in Bundle serializedCallingStackInBundle); + oneway void logTrace(long timestamp, String where, long loggingTypes, String callingParams, + int processId, long threadId, int callingUid, in Bundle serializedCallingStackInBundle); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index af59ea1d22ff4e907fe775dc1835547e338633c8..f453ba16043c02bfd08e6b7c8b3856b82c023cd9 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -140,7 +140,6 @@ import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toolbar; import android.window.SplashScreen; -import android.window.SplashScreenView; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -969,7 +968,6 @@ public class Activity extends ContextThemeWrapper private UiTranslationController mUiTranslationController; private SplashScreen mSplashScreen; - private SplashScreenView mSplashScreenView; private final WindowControllerCallback mWindowControllerCallback = new WindowControllerCallback() { @@ -1538,6 +1536,17 @@ public class Activity extends ContextThemeWrapper getApplication().dispatchActivityPostDestroyed(this); } + private void dispatchActivityConfigurationChanged() { + getApplication().dispatchActivityConfigurationChanged(this); + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((Application.ActivityLifecycleCallbacks) callbacks[i]) + .onActivityConfigurationChanged(this); + } + } + } + private Object[] collectActivityLifecycleCallbacks() { Object[] callbacks = null; synchronized (mActivityLifecycleCallbacks) { @@ -1629,16 +1638,6 @@ public class Activity extends ContextThemeWrapper } } - /** @hide */ - public void setSplashScreenView(SplashScreenView v) { - mSplashScreenView = v; - } - - /** @hide */ - SplashScreenView getSplashScreenView() { - return mSplashScreenView; - } - /** * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with * the attribute {@link android.R.attr#persistableMode} set to @@ -1922,10 +1921,14 @@ public class Activity extends ContextThemeWrapper } /** - * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or - * {@link #onPause}, for your activity to start interacting with the user. This is an indicator - * that the activity became active and ready to receive input. It is on top of an activity stack - * and visible to user. + * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or {@link #onPause}. This + * is usually a hint for your activity to start interacting with the user, which is a good + * indicator that the activity became active and ready to receive input. This sometimes could + * also be a transit state toward another resting state. For instance, an activity may be + * relaunched to {@link #onPause} due to configuration changes and the activity was visible, + * but wasn’t the top-most activity of an activity task. {@link #onResume} is guaranteed to be + * called before {@link #onPause} in this case which honors the activity lifecycle policy and + * the activity eventually rests in {@link #onPause}. * *

On platform versions prior to {@link android.os.Build.VERSION_CODES#Q} this is also a good * place to try to open exclusive-access devices or to get access to singleton resources. @@ -2491,12 +2494,11 @@ public class Activity extends ContextThemeWrapper * *

To get the voice interactor you need to call {@link #getVoiceInteractor()} * which would return non null only if there is an ongoing voice - * interaction session. You an also detect when the voice interactor is no + * interaction session. You can also detect when the voice interactor is no * longer valid because the voice interaction session that is backing is finished * by calling {@link VoiceInteractor#registerOnDestroyedCallback(Executor, Runnable)}. * - *

This method will be called only after {@link #onStart()} is being called and - * before {@link #onStop()} is being called. + *

This method will be called only after {@link #onStart()} and before {@link #onStop()}. * *

You should pass to the callback the currently supported direct actions which * cannot be null or contain null elements. @@ -3027,6 +3029,8 @@ public class Activity extends ContextThemeWrapper // view changes from above. mActionBar.onConfigurationChanged(newConfig); } + + dispatchActivityConfigurationChanged(); } /** diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index bd4386885dd6572bcd0c81d965580c55ff4a26bc..db7ab1a6f37994d1eb67aa4c396abad1597409f6 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.content.res.Configuration; @@ -205,6 +206,19 @@ public class ActivityClient { } } + /** + * Returns the non-finishing activity token below in the same task if it belongs to the same + * process. + */ + @Nullable + public IBinder getActivityTokenBelow(IBinder activityToken) { + try { + return getActivityClientController().getActivityTokenBelow(activityToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + ComponentName getCallingActivity(IBinder token) { try { return getActivityClientController().getCallingActivity(token); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index f53c5b6c974824d2a6152457de586ec748b22605..db45466d98d251e1ba075fb2c42cce2abcbf40b0 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4075,6 +4075,85 @@ public class ActivityManager { return switchUser(user.getIdentifier()); } + /** + * Gets the message that is shown when a user is switched from. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USERS) + public @Nullable String getSwitchingFromUserMessage() { + try { + return getService().getSwitchingFromUserMessage(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Gets the message that is shown when a user is switched to. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USERS) + public @Nullable String getSwitchingToUserMessage() { + try { + return getService().getSwitchingToUserMessage(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Uses the value defined by the platform. + * + * @hide + */ + @TestApi + public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; + + /** + * Overrides value defined by the platform and stop user on switch. + * + * @hide + */ + @TestApi + public static final int STOP_USER_ON_SWITCH_TRUE = 1; + + /** + * Overrides value defined by the platform and don't stop user on switch. + * + * @hide + */ + @TestApi + public static final int STOP_USER_ON_SWITCH_FALSE = 0; + + /** @hide */ + @IntDef(prefix = { "STOP_USER_ON_SWITCH_" }, value = { + STOP_USER_ON_SWITCH_DEFAULT, + STOP_USER_ON_SWITCH_TRUE, + STOP_USER_ON_SWITCH_FALSE + }) + public @interface StopUserOnSwitch {} + + /** + * Sets whether the current foreground user (and its profiles) should be stopped after switched + * out. + * + *

Should only be used on tests. Doesn't apply to {@link UserHandle#SYSTEM system user}. + * + * @hide + */ + @TestApi + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) + public void setStopUserOnSwitch(@StopUserOnSwitch int value) { + try { + getService().setStopUserOnSwitch(value); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** * Starts a profile. * To be used with non-managed profiles, managed profiles should use diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 4e8480c4e113fc0ece83b2d4bf2985d7e443d191..7be4c3e1465b8c2f473a12cb1ed3e3cb4934f7e1 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -16,6 +16,8 @@ package android.app; +import static android.app.ActivityManager.StopUserOnSwitch; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -647,4 +649,34 @@ public abstract class ActivityManagerInternal { */ @Nullable public abstract List getIsolatedProcesses(int uid); + + /** @see ActivityManagerService#sendIntentSender */ + public abstract int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code, + Intent intent, String resolvedType, + IIntentReceiver finishedReceiver, String requiredPermission, Bundle options); + + /** + * Sets the provider to communicate between voice interaction manager service and + * ActivityManagerService. + */ + public abstract void setVoiceInteractionManagerProvider( + @Nullable VoiceInteractionManagerProvider provider); + + /** + * Sets whether the current foreground user (and its profiles) should be stopped after switched + * out. + */ + public abstract void setStopUserOnSwitch(@StopUserOnSwitch int value); + + /** + * Provides the interface to communicate between voice interaction manager service and + * ActivityManagerService. + */ + public interface VoiceInteractionManagerProvider { + /** + * Notifies the service when a high-level activity event has been changed, for example, + * an activity was resumed or stopped. + */ + void notifyActivityEventChanged(); + } } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 8e1f263ebf03cb685fef6041624862b4c4533319..0ff9f6655b8a8556ddd0170d6d75fc75cb6bdbb0 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -26,6 +26,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.ExitTransitionCoordinator.ActivityExitTransitionCallbacks; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; @@ -55,7 +56,7 @@ import android.view.RemoteAnimationAdapter; import android.view.View; import android.view.ViewGroup; import android.view.Window; -import android.window.IRemoteTransition; +import android.window.RemoteTransition; import android.window.SplashScreen; import android.window.WindowContainerToken; @@ -214,6 +215,14 @@ public class ActivityOptions { public static final String KEY_LAUNCH_ROOT_TASK_TOKEN = "android.activity.launchRootTaskToken"; + /** + * The {@link com.android.server.wm.TaskFragment} token the activity should be launched into. + * @see #setLaunchTaskFragmentToken(IBinder) + * @hide + */ + public static final String KEY_LAUNCH_TASK_FRAGMENT_TOKEN = + "android.activity.launchTaskFragmentToken"; + /** * The windowing mode the activity should be launched into. * @hide @@ -396,6 +405,7 @@ public class ActivityOptions { private int mCallerDisplayId = INVALID_DISPLAY; private WindowContainerToken mLaunchTaskDisplayArea; private WindowContainerToken mLaunchRootTask; + private IBinder mLaunchTaskFragmentToken; @WindowConfiguration.WindowingMode private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED; @WindowConfiguration.ActivityType @@ -417,11 +427,11 @@ public class ActivityOptions { private IAppTransitionAnimationSpecsFuture mSpecsFuture; private RemoteAnimationAdapter mRemoteAnimationAdapter; private IBinder mLaunchCookie; - private IRemoteTransition mRemoteTransition; + private RemoteTransition mRemoteTransition; private boolean mOverrideTaskTransition; private String mSplashScreenThemeResName; @SplashScreen.SplashScreenStyle - private int mSplashScreenStyle; + private int mSplashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED; private boolean mRemoveWithTaskOrganizer; private boolean mLaunchedFromBubble; private boolean mTransientLaunch; @@ -1045,7 +1055,7 @@ public class ActivityOptions { */ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) public static ActivityOptions makeRemoteAnimation(RemoteAnimationAdapter remoteAnimationAdapter, - IRemoteTransition remoteTransition) { + RemoteTransition remoteTransition) { final ActivityOptions opts = new ActivityOptions(); opts.mRemoteAnimationAdapter = remoteAnimationAdapter; opts.mAnimationType = ANIM_REMOTE_ANIMATION; @@ -1055,11 +1065,11 @@ public class ActivityOptions { /** * Create an {@link ActivityOptions} instance that lets the application control the entire - * transition using a {@link IRemoteTransition}. + * transition using a {@link RemoteTransition}. * @hide */ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) - public static ActivityOptions makeRemoteTransition(IRemoteTransition remoteTransition) { + public static ActivityOptions makeRemoteTransition(RemoteTransition remoteTransition) { final ActivityOptions opts = new ActivityOptions(); opts.mRemoteTransition = remoteTransition; return opts; @@ -1138,6 +1148,7 @@ public class ActivityOptions { mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY); mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN); mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN); + mLaunchTaskFragmentToken = opts.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN); mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED); mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED); mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); @@ -1171,8 +1182,7 @@ public class ActivityOptions { } mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER); mLaunchCookie = opts.getBinder(KEY_LAUNCH_COOKIE); - mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder( - KEY_REMOTE_TRANSITION)); + mRemoteTransition = opts.getParcelable(KEY_REMOTE_TRANSITION); mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION); mSplashScreenThemeResName = opts.getString(KEY_SPLASH_SCREEN_THEME); mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER); @@ -1338,7 +1348,7 @@ public class ActivityOptions { } /** @hide */ - public IRemoteTransition getRemoteTransition() { + public RemoteTransition getRemoteTransition() { return mRemoteTransition; } @@ -1472,6 +1482,17 @@ public class ActivityOptions { return this; } + /** @hide */ + public IBinder getLaunchTaskFragmentToken() { + return mLaunchTaskFragmentToken; + } + + /** @hide */ + public ActivityOptions setLaunchTaskFragmentToken(IBinder taskFragmentToken) { + mLaunchTaskFragmentToken = taskFragmentToken; + return this; + } + /** @hide */ public int getLaunchWindowingMode() { return mLaunchWindowingMode; @@ -1501,7 +1522,8 @@ public class ActivityOptions { * Sets the task the activity will be launched in. * @hide */ - @TestApi + @RequiresPermission(START_TASKS_FROM_RECENTS) + @SystemApi public void setLaunchTaskId(int taskId) { mLaunchTaskId = taskId; } @@ -1509,6 +1531,7 @@ public class ActivityOptions { /** * @hide */ + @SystemApi public int getLaunchTaskId() { return mLaunchTaskId; } @@ -1882,6 +1905,9 @@ public class ActivityOptions { if (mLaunchRootTask != null) { b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask); } + if (mLaunchTaskFragmentToken != null) { + b.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, mLaunchTaskFragmentToken); + } if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) { b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode); } @@ -1941,7 +1967,7 @@ public class ActivityOptions { b.putBinder(KEY_LAUNCH_COOKIE, mLaunchCookie); } if (mRemoteTransition != null) { - b.putBinder(KEY_REMOTE_TRANSITION, mRemoteTransition.asBinder()); + b.putParcelable(KEY_REMOTE_TRANSITION, mRemoteTransition); } if (mOverrideTaskTransition) { b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition); diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 4a7fcd232ce977942f0058173aeac057786b9fbd..a8366259251372f33cb3e1d94f4ea36a45f38b57 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -475,6 +475,19 @@ public class ActivityTaskManager { } } + /** + * Detaches the navigation bar from the app it was attached to during a transition. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) + public void detachNavigationBarFromApp(@NonNull IBinder transition) { + try { + getService().detachNavigationBarFromApp(transition); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Information you can retrieve about a root task in the system. * @hide diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 6d7835f84dc704b973d9faffd7aff9106fbba868..48edb2ec5474a49c4ab03d42159d9ce9366b4a2a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -18,7 +18,6 @@ package android.app; import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; import static android.app.ConfigurationController.createNewConfigAndUpdateIfNotNull; -import static android.app.ConfigurationController.freeTextLayoutCachesIfNeeded; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE; @@ -30,12 +29,23 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS; import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets; +import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; +import static android.window.ConfigurationHelper.isDifferentDisplay; +import static android.window.ConfigurationHelper.shouldUpdateResources; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.RemoteServiceException.BadForegroundServiceNotificationException; +import android.app.RemoteServiceException.CannotDeliverBroadcastException; +import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException; +import android.app.RemoteServiceException.CrashedByAdbException; +import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException; +import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.app.backup.BackupAgent; @@ -88,10 +98,8 @@ import android.database.sqlite.SQLiteDebug.DbStats; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.HardwareRenderer; -import android.graphics.Rect; import android.graphics.Typeface; import android.hardware.display.DisplayManagerGlobal; -import android.inputmethodservice.InputMethodService; import android.media.MediaFrameworkInitializer; import android.media.MediaFrameworkPlatformInitializer; import android.media.MediaServiceManager; @@ -166,6 +174,7 @@ import android.view.Choreographer; import android.view.Display; import android.view.DisplayAdjustments; import android.view.DisplayAdjustments.FixedRotationAdjustments; +import android.view.SurfaceControl; import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewDebug; @@ -184,6 +193,7 @@ import android.webkit.WebView; import android.window.SizeConfigurationBuckets; import android.window.SplashScreen; import android.window.SplashScreenView; +import android.window.WindowProviderService; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -234,7 +244,6 @@ import java.util.Map; import java.util.Objects; import java.util.TimeZone; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; /** @@ -313,7 +322,8 @@ public final class ActivityThread extends ClientTransactionHandler @UnsupportedAppUsage private ContextImpl mSystemContext; - private ContextImpl mSystemUiContext; + @GuardedBy("this") + private SparseArray mDisplaySystemUiContexts; @UnsupportedAppUsage static volatile IPackageManager sPackageManager; @@ -1286,8 +1296,11 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public void scheduleCrash(String msg, int typeId) { - sendMessage(H.SCHEDULE_CRASH, msg, typeId); + public void scheduleCrash(String msg, int typeId, @Nullable Bundle extras) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = msg; + args.arg2 = extras; + sendMessage(H.SCHEDULE_CRASH, args, typeId); } public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken, @@ -1917,17 +1930,42 @@ public final class ActivityThread extends ClientTransactionHandler } } - private void throwRemoteServiceException(String message, int typeId) { + private void throwRemoteServiceException(String message, int typeId, @Nullable Bundle extras) { // Use a switch to ensure all the type IDs are unique. switch (typeId) { - case ForegroundServiceDidNotStartInTimeException.TYPE_ID: // 1 - throw new ForegroundServiceDidNotStartInTimeException(message); - case RemoteServiceException.TYPE_ID: // 0 + case ForegroundServiceDidNotStartInTimeException.TYPE_ID: + throw generateForegroundServiceDidNotStartInTimeException(message, extras); + + case CannotDeliverBroadcastException.TYPE_ID: + throw new CannotDeliverBroadcastException(message); + + case CannotPostForegroundServiceNotificationException.TYPE_ID: + throw new CannotPostForegroundServiceNotificationException(message); + + case BadForegroundServiceNotificationException.TYPE_ID: + throw new BadForegroundServiceNotificationException(message); + + case MissingRequestPasswordComplexityPermissionException.TYPE_ID: + throw new MissingRequestPasswordComplexityPermissionException(message); + + case CrashedByAdbException.TYPE_ID: + throw new CrashedByAdbException(message); + default: - throw new RemoteServiceException(message); + throw new RemoteServiceException(message + + " (with unwknown typeId:" + typeId + ")"); } } + private ForegroundServiceDidNotStartInTimeException + generateForegroundServiceDidNotStartInTimeException(String message, Bundle extras) { + final String serviceClassName = + ForegroundServiceDidNotStartInTimeException.getServiceClassNameFromExtras(extras); + final Exception inner = (serviceClassName == null) ? null + : Service.getStartForegroundServiceStackTrace(serviceClassName); + throw new ForegroundServiceDidNotStartInTimeException(message, inner); + } + class H extends Handler { public static final int BIND_APPLICATION = 110; @UnsupportedAppUsage @@ -2145,9 +2183,14 @@ public final class ActivityThread extends ClientTransactionHandler handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; - case SCHEDULE_CRASH: - throwRemoteServiceException((String) msg.obj, msg.arg1); + case SCHEDULE_CRASH: { + SomeArgs args = (SomeArgs) msg.obj; + String message = (String) args.arg1; + Bundle extras = (Bundle) args.arg2; + args.recycle(); + throwRemoteServiceException(message, msg.arg1, extras); break; + } case DUMP_HEAP: handleDumpHeap((DumpHeapData) msg.obj); break; @@ -2609,23 +2652,48 @@ public final class ActivityThread extends ClientTransactionHandler } } - @Override + @NonNull public ContextImpl getSystemUiContext() { - synchronized (this) { - if (mSystemUiContext == null) { - mSystemUiContext = ContextImpl.createSystemUiContext(getSystemContext()); - } - return mSystemUiContext; - } + return getSystemUiContext(DEFAULT_DISPLAY); } /** - * Create the context instance base on system resources & display information which used for UI. + * Gets the context instance base on system resources & display information which used for UI. * @param displayId The ID of the display where the UI is shown. * @see ContextImpl#createSystemUiContext(ContextImpl, int) */ - public ContextImpl createSystemUiContext(int displayId) { - return ContextImpl.createSystemUiContext(getSystemUiContext(), displayId); + @NonNull + public ContextImpl getSystemUiContext(int displayId) { + synchronized (this) { + if (mDisplaySystemUiContexts == null) { + mDisplaySystemUiContexts = new SparseArray<>(); + } + ContextImpl systemUiContext = mDisplaySystemUiContexts.get(displayId); + if (systemUiContext == null) { + systemUiContext = ContextImpl.createSystemUiContext(getSystemContext(), displayId); + mDisplaySystemUiContexts.put(displayId, systemUiContext); + } + return systemUiContext; + } + } + + @Nullable + @Override + public ContextImpl getSystemUiContextNoCreate() { + synchronized (this) { + if (mDisplaySystemUiContexts == null) return null; + return mDisplaySystemUiContexts.get(DEFAULT_DISPLAY); + } + } + + void onSystemUiContextCleanup(ContextImpl context) { + synchronized (this) { + if (mDisplaySystemUiContexts == null) return; + final int index = mDisplaySystemUiContexts.indexOfValue(context); + if (index >= 0) { + mDisplaySystemUiContexts.removeAt(index); + } + } } public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { @@ -3558,6 +3626,13 @@ public final class ActivityThread extends ClientTransactionHandler + ", comp=" + r.intent.getComponent().toShortString() + ", dir=" + r.packageInfo.getAppDir()); + // updatePendingActivityConfiguration() reads from mActivities to update + // ActivityClientRecord which runs in a different thread. Protect modifications to + // mActivities to avoid race. + synchronized (mResourcesManager) { + mActivities.put(r.token, r); + } + if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = @@ -3603,6 +3678,11 @@ public final class ActivityThread extends ClientTransactionHandler } activity.mLaunchedFromBubble = r.mLaunchedFromBubble; activity.mCalled = false; + // Assigning the activity to the record before calling onCreate() allows + // ActivityThread#getActivity() lookup for the callbacks triggered from + // ActivityLifecycleCallbacks#onActivityCreated() or + // ActivityLifecycleCallback#onActivityPostCreated(). + r.activity = activity; if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { @@ -3613,19 +3693,11 @@ public final class ActivityThread extends ClientTransactionHandler "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()"); } - r.activity = activity; mLastReportedWindowingMode.put(activity.getActivityToken(), config.windowConfiguration.getWindowingMode()); } r.setState(ON_CREATE); - // updatePendingActivityConfiguration() reads from mActivities to update - // ActivityClientRecord which runs in a different thread. Protect modifications to - // mActivities to avoid race. - synchronized (mResourcesManager) { - mActivities.put(r.token, r); - } - } catch (SuperNotCalledException e) { throw e; @@ -3740,7 +3812,7 @@ public final class ActivityThread extends ClientTransactionHandler if (pkgName != null && !pkgName.isEmpty() && r.packageInfo.mPackageName.contains(pkgName)) { for (int id : dm.getDisplayIds()) { - if (id != Display.DEFAULT_DISPLAY) { + if (id != DEFAULT_DISPLAY) { Display display = dm.getCompatibleDisplay(id, appContext.getResources()); appContext = (ContextImpl) appContext.createDisplayContext(display); @@ -4068,10 +4140,11 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r, - @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) { + @Nullable SplashScreenView.SplashScreenViewParcelable parcelable, + @NonNull SurfaceControl startingWindowLeash) { final DecorView decorView = (DecorView) r.window.peekDecorView(); if (parcelable != null && decorView != null) { - createSplashScreen(r, decorView, parcelable); + createSplashScreen(r, decorView, parcelable, startingWindowLeash); } else { // shouldn't happen! Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach"); @@ -4079,63 +4152,57 @@ public final class ActivityThread extends ClientTransactionHandler } private void createSplashScreen(ActivityClientRecord r, DecorView decorView, - SplashScreenView.SplashScreenViewParcelable parcelable) { + SplashScreenView.SplashScreenViewParcelable parcelable, + @NonNull SurfaceControl startingWindowLeash) { final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity); final SplashScreenView view = builder.createFromParcel(parcelable).build(); decorView.addView(view); view.attachHostActivityAndSetSystemUIColors(r.activity, r.window); view.requestLayout(); - // Ensure splash screen view is shown before remove the splash screen window. - final ViewRootImpl impl = decorView.getViewRootImpl(); - final boolean hardwareEnabled = impl != null && impl.isHardwareEnabled(); - final AtomicBoolean notified = new AtomicBoolean(); - if (hardwareEnabled) { - final Runnable frameCommit = new Runnable() { - @Override - public void run() { - view.post(() -> { - if (!notified.get()) { - view.getViewTreeObserver().unregisterFrameCommitCallback(this); - ActivityClient.getInstance().reportSplashScreenAttached( - r.token); - notified.set(true); - } - }); - } - }; - view.getViewTreeObserver().registerFrameCommitCallback(frameCommit); - } else { - final ViewTreeObserver.OnDrawListener onDrawListener = - new ViewTreeObserver.OnDrawListener() { - @Override - public void onDraw() { - view.post(() -> { - if (!notified.get()) { - view.getViewTreeObserver().removeOnDrawListener(this); - ActivityClient.getInstance().reportSplashScreenAttached( - r.token); - notified.set(true); - } - }); - } - }; - view.getViewTreeObserver().addOnDrawListener(onDrawListener); - } + + view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() { + private boolean mHandled = false; + @Override + public void onDraw() { + if (mHandled) { + return; + } + mHandled = true; + // Transfer the splash screen view from shell to client. + // Call syncTransferSplashscreenViewTransaction at the first onDraw so we can ensure + // the client view is ready to show and we can use applyTransactionOnDraw to make + // all transitions happen at the same frame. + syncTransferSplashscreenViewTransaction( + view, r.token, decorView, startingWindowLeash); + view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this)); + } + }); } - @Override - public void handOverSplashScreenView(@NonNull ActivityClientRecord r) { - final SplashScreenView v = r.activity.getSplashScreenView(); - if (v == null) { - return; - } + private void reportSplashscreenViewShown(IBinder token, SplashScreenView view) { + ActivityClient.getInstance().reportSplashScreenAttached(token); synchronized (this) { if (mSplashScreenGlobal != null) { - mSplashScreenGlobal.handOverSplashScreenView(r.token, v); + mSplashScreenGlobal.handOverSplashScreenView(token, view); } } } + private void syncTransferSplashscreenViewTransaction(SplashScreenView view, IBinder token, + View decorView, @NonNull SurfaceControl startingWindowLeash) { + // Ensure splash screen view is shown before remove the splash screen window. + // Once the copied splash screen view is onDrawn on decor view, use applyTransactionOnDraw + // to ensure the transfer of surface view and hide starting window are happen at the same + // frame. + final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + transaction.hide(startingWindowLeash); + + decorView.getViewRootImpl().applyTransactionOnDraw(transaction); + view.syncTransferSurfaceOnDraw(); + // Tell server we can remove the starting window + decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view)); + } + /** * Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then * return to its previous state. This allows activities that rely on onUserLeaveHint instead of @@ -4907,7 +4974,7 @@ public final class ActivityThread extends ClientTransactionHandler Slog.w(TAG, "Activity top position already set to onTop=" + onTop); return; } - // TODO(b/197484331): Remove this short-term workaround while fixing the binder failure. + // TODO(b/209744518): Remove this short-term workaround while fixing the binder failure. Slog.e(TAG, "Activity top position already set to onTop=" + onTop); } @@ -5436,6 +5503,12 @@ public final class ActivityThread extends ClientTransactionHandler // behave properly when activity is relaunching. r.window.clearContentView(); } else { + final ViewRootImpl viewRoot = v.getViewRootImpl(); + if (viewRoot != null) { + // Clear the callback to avoid the destroyed activity from receiving + // configuration changes that are no longer effective. + viewRoot.setActivityConfigCallback(null); + } wm.removeViewImmediate(v); } } @@ -5759,7 +5832,7 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public ArrayList collectComponentCallbacks(boolean includeActivities) { + public ArrayList collectComponentCallbacks(boolean includeUiContexts) { ArrayList callbacks = new ArrayList(); @@ -5768,7 +5841,7 @@ public final class ActivityThread extends ClientTransactionHandler for (int i=0; i= 0; i--) { final Activity a = mActivities.valueAt(i).activity; if (a != null && !a.mFinished) { @@ -5778,11 +5851,12 @@ public final class ActivityThread extends ClientTransactionHandler } final int NSVC = mServices.size(); for (int i=0; i callbacks = - collectComponentCallbacks(true /* includeActivities */); + collectComponentCallbacks(true /* includeUiContexts */); final int N = callbacks.size(); for (int i=0; i callbacks = - collectComponentCallbacks(true /* includeActivities */); + collectComponentCallbacks(true /* includeUiContexts */); final int N = callbacks.size(); for (int i = 0; i < N; i++) { @@ -7576,12 +7613,6 @@ public final class ActivityThread extends ClientTransactionHandler ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> { synchronized (mResourcesManager) { - // TODO (b/135719017): Temporary log for debugging IME service. - if (Build.IS_DEBUGGABLE && mHasImeComponent) { - Log.d(TAG, "ViewRootImpl.ConfigChangedCallback for IME, " - + "config=" + globalConfig); - } - // We need to apply this change to the resources immediately, because upon returning // the view hierarchy will be informed about it. if (mResourcesManager.applyConfigurationToResources(globalConfig, @@ -7936,11 +7967,6 @@ public final class ActivityThread extends ClientTransactionHandler return mDensityCompatMode; } - @Override - public boolean hasImeComponent() { - return mHasImeComponent; - } - // ------------------ Regular JNI ------------------------ private native void nPurgePendingResources(); private native void nDumpGraphicsInfo(FileDescriptor fd); diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java index d91933c0f81737cb718d7ae60fba74c9ddf9228f..b9ad5c3378137f66d439b117dc8a0357c427f02a 100644 --- a/core/java/android/app/ActivityThreadInternal.java +++ b/core/java/android/app/ActivityThreadInternal.java @@ -28,15 +28,13 @@ import java.util.ArrayList; interface ActivityThreadInternal { ContextImpl getSystemContext(); - ContextImpl getSystemUiContext(); + ContextImpl getSystemUiContextNoCreate(); boolean isInDensityCompatMode(); - boolean hasImeComponent(); - boolean isCachedProcessState(); Application getApplication(); - ArrayList collectComponentCallbacks(boolean includeActivities); + ArrayList collectComponentCallbacks(boolean includeUiContexts); } diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 4b87a647a80b76583ee55a10d2d99f270e923f92..f5b3b40d88d6a57a95841efd710d5f8016201e96 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -871,6 +871,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (view.isAttachedToWindow()) { tempMatrix.reset(); mSharedElementParentMatrices.get(i).invert(tempMatrix); + decor.transformMatrixToLocal(tempMatrix); GhostView.addGhost(view, decor, tempMatrix); ViewGroup parent = (ViewGroup) view.getParent(); if (moveWithParent && !isInTransitionGroup(parent, decor)) { diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 618eda8c84e89d12cd204266aa319e1b0eda9216..a1eab6553cff0091345837dad93330ac2d0579d3 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -205,6 +205,13 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { */ default void onActivityPostDestroyed(@NonNull Activity activity) { } + + /** + * Called when the Activity configuration was changed. + * @hide + */ + default void onActivityConfigurationChanged(@NonNull Activity activity) { + } } /** @@ -554,6 +561,16 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } + /* package */ void dispatchActivityConfigurationChanged(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityConfigurationChanged( + activity); + } + } + } + @UnsupportedAppUsage private Object[] collectActivityLifecycleCallbacks() { Object[] callbacks = null; diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 115101c0bff65c4d8639a7a2a0558a7f09448123..c743f6572d5e5562ba8407f3eda5b7a521f03a94 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -28,6 +28,7 @@ import android.content.res.Configuration; import android.os.IBinder; import android.util.MergedConfiguration; import android.view.DisplayAdjustments.FixedRotationAdjustments; +import android.view.SurfaceControl; import android.window.SplashScreenView.SplashScreenViewParcelable; import com.android.internal.annotations.VisibleForTesting; @@ -165,10 +166,8 @@ public abstract class ClientTransactionHandler { /** Attach a splash screen window view to the top of the activity */ public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r, - @NonNull SplashScreenViewParcelable parcelable); - - /** Hand over the splash screen window view to the activity */ - public abstract void handOverSplashScreenView(@NonNull ActivityClientRecord r); + @NonNull SplashScreenViewParcelable parcelable, + @NonNull SurfaceControl startingWindowLeash); /** Perform activity launch. */ public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r, diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java index f79e0780ecae25c1f29ba73c3a778df5251c71b2..58f60a6a59a7ea586af1c2c53c806e51d79fc274 100644 --- a/core/java/android/app/ConfigurationController.java +++ b/core/java/android/app/ConfigurationController.java @@ -17,24 +17,20 @@ package android.app; import static android.app.ActivityThread.DEBUG_CONFIGURATION; +import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentCallbacks2; import android.content.Context; -import android.content.pm.ActivityInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.HardwareRenderer; -import android.inputmethodservice.InputMethodService; -import android.os.Build; import android.os.LocaleList; import android.os.Trace; import android.util.DisplayMetrics; -import android.util.Log; import android.util.Slog; import android.view.ContextThemeWrapper; import android.view.WindowManagerGlobal; @@ -158,9 +154,12 @@ class ConfigurationController { int configDiff; boolean equivalent; + // Get theme outside of synchronization to avoid nested lock. + final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme(); + final ContextImpl systemUiContext = mActivityThread.getSystemUiContextNoCreate(); + final Resources.Theme systemUiTheme = + systemUiContext != null ? systemUiContext.getTheme() : null; synchronized (mResourcesManager) { - final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme(); - final Resources.Theme systemUiTheme = mActivityThread.getSystemUiContext().getTheme(); if (mPendingConfiguration != null) { if (!mPendingConfiguration.isOtherSeqNewer(config)) { config = mPendingConfiguration; @@ -169,12 +168,7 @@ class ConfigurationController { mPendingConfiguration = null; } - final boolean hasIme = mActivityThread.hasImeComponent(); if (config == null) { - // TODO (b/135719017): Temporary log for debugging IME service. - if (Build.IS_DEBUGGABLE && hasIme) { - Log.w(TAG, "handleConfigurationChanged for IME app but config is null"); - } return; } @@ -205,12 +199,6 @@ class ConfigurationController { mConfiguration = new Configuration(); } if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { - // TODO (b/135719017): Temporary log for debugging IME service. - if (Build.IS_DEBUGGABLE && hasIme) { - Log.w(TAG, "handleConfigurationChanged for IME app but config seq is obsolete " - + ", config=" + config - + ", mConfiguration=" + mConfiguration); - } return; } @@ -222,13 +210,14 @@ class ConfigurationController { systemTheme.rebase(); } - if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) { + if (systemUiTheme != null + && (systemUiTheme.getChangingConfigurations() & configDiff) != 0) { systemUiTheme.rebase(); } } final ArrayList callbacks = - mActivityThread.collectComponentCallbacks(false /* includeActivities */); + mActivityThread.collectComponentCallbacks(false /* includeUiContexts */); freeTextLayoutCachesIfNeeded(configDiff); @@ -238,13 +227,6 @@ class ConfigurationController { ComponentCallbacks2 cb = callbacks.get(i); if (!equivalent) { performConfigurationChanged(cb, config); - } else { - // TODO (b/135719017): Temporary log for debugging IME service. - if (Build.IS_DEBUGGABLE && cb instanceof InputMethodService) { - Log.w(TAG, "performConfigurationChanged didn't callback to IME " - + ", configDiff=" + configDiff - + ", mConfiguration=" + mConfiguration); - } } } } @@ -326,16 +308,4 @@ class ConfigurationController { return newConfig; } - /** Ask test layout engine to free its caches if there is a locale change. */ - static void freeTextLayoutCachesIfNeeded(int configDiff) { - if (configDiff != 0) { - boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0); - if (hasLocaleConfigChange) { - Canvas.freeTextLayoutCaches(); - if (DEBUG_CONFIGURATION) { - Slog.v(TAG, "Cleared TextLayout Caches"); - } - } - } - } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 9272e45c3278d2bf2fac0ce6138b4b43551fec88..24046c0c327b279f2f5da2dd8e1953b236e6f2d6 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -311,6 +311,14 @@ class ContextImpl extends Context { @ContextType private int mContextType; + /** + * {@code true} to indicate that the {@link Context} owns the {@link #getWindowContextToken()} + * and is responsible for detaching the token when the Context is released. + * + * @see #finalize() + */ + private boolean mOwnsToken = false; + @GuardedBy("mSync") private File mDatabasesDir; @GuardedBy("mSync") @@ -1876,6 +1884,14 @@ class ContextImpl extends Context { "Not allowed to start service " + service + ": " + cn.getClassName()); } } + // If we started a foreground service in the same package, remember the stack trace. + if (cn != null && requireForeground) { + if (cn.getPackageName().equals(getOpPackageName())) { + Service.setStartForegroundServiceStackTrace(cn.getClassName(), + new StackTrace("Last startServiceCommon() call for this service was " + + "made here")); + } + } return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2632,7 +2648,10 @@ class ContextImpl extends Context { overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(), mResources.getLoaders())); context.mDisplay = display; - context.mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT; + // Inherit context type if the container is from System or System UI context to bypass + // UI context check. + context.mContextType = mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI + ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI : CONTEXT_TYPE_DISPLAY_CONTEXT; // Display contexts and any context derived from a display context should always override // the display that would otherwise be inherited from mToken (or the global configuration if // mToken is null). @@ -2685,7 +2704,8 @@ class ContextImpl extends Context { // Step 2. Create the base context of the window context, it will also create a Resources // associated with the WindowTokenClient and set the token to the base context. - final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, display); + final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, + display.getDisplayId()); // Step 3. Create a WindowContext instance and set it as the outer context of the base // context to make the service obtained by #getSystemService(String) able to query @@ -2710,9 +2730,7 @@ class ContextImpl extends Context { if (display == null) { throw new IllegalArgumentException("Display must not be null"); } - final ContextImpl tokenContext = createWindowContextBase(token, display); - tokenContext.setResources(createWindowContextResources(tokenContext)); - return tokenContext; + return createWindowContextBase(token, display.getDisplayId()); } /** @@ -2720,13 +2738,13 @@ class ContextImpl extends Context { * window. * * @param token The token to associate with {@link Resources} - * @param display The {@link Display} to associate with. + * @param displayId The ID of {@link Display} to associate with. * * @see #createWindowContext(Display, int, Bundle) * @see #createTokenContext(IBinder, Display) */ @UiContext - ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) { + ContextImpl createWindowContextBase(@NonNull IBinder token, int displayId) { ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams, mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), @@ -2740,8 +2758,8 @@ class ContextImpl extends Context { baseContext.setResources(windowContextResources); // Associate the display with window context resources so that configuration update from // the server side will also apply to the display's metrics. - baseContext.mDisplay = ResourcesManager.getInstance() - .getAdjustedDisplay(display.getDisplayId(), windowContextResources); + baseContext.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId, + windowContextResources); return baseContext; } @@ -2757,7 +2775,7 @@ class ContextImpl extends Context { */ private static Resources createWindowContextResources(@NonNull ContextImpl windowContextBase) { final LoadedApk packageInfo = windowContextBase.mPackageInfo; - final ClassLoader classLoader = windowContextBase.mClassLoader; + final ClassLoader classLoader = windowContextBase.getClassLoader(); final IBinder token = windowContextBase.getWindowContextToken(); final String resDir = packageInfo.getResDir(); @@ -2977,6 +2995,18 @@ class ContextImpl extends Context { mContentCaptureOptions = options; } + @Override + protected void finalize() throws Throwable { + // If mToken is a WindowTokenClient, the Context is usually associated with a + // WindowContainer. We should detach from WindowContainer when the Context is finalized + // if this Context is not a WindowContext. WindowContext finalization is handled in + // WindowContext class. + if (mToken instanceof WindowTokenClient && mOwnsToken) { + ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded(); + } + super.finalize(); + } + @UnsupportedAppUsage static ContextImpl createSystemContext(ActivityThread mainThread) { LoadedApk packageInfo = new LoadedApk(mainThread); @@ -2997,22 +3027,14 @@ class ContextImpl extends Context { * @param displayId The ID of the display where the UI is shown. */ static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) { - final LoadedApk packageInfo = systemContext.mPackageInfo; - ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, - ContextParams.EMPTY, null, null, null, null, null, 0, null, null); - context.setResources(createResources(null, packageInfo, null, displayId, null, - packageInfo.getCompatibilityInfo(), null)); - context.updateDisplay(displayId); + final WindowTokenClient token = new WindowTokenClient(); + final ContextImpl context = systemContext.createWindowContextBase(token, displayId); + token.attachContext(context); + token.attachToDisplayContent(displayId); context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI; - return context; - } + context.mOwnsToken = true; - /** - * The overloaded method of {@link #createSystemUiContext(ContextImpl, int)}. - * Uses {@Code Display.DEFAULT_DISPLAY} as the target display. - */ - static ContextImpl createSystemUiContext(ContextImpl systemContext) { - return createSystemUiContext(systemContext, Display.DEFAULT_DISPLAY); + return context; } @UnsupportedAppUsage @@ -3192,6 +3214,10 @@ class ContextImpl extends Context { final void performFinalCleanup(String who, String what) { //Log.i(TAG, "Cleanup up context: " + this); mPackageInfo.removeContextRegistrations(getOuterContext(), who, what); + if (mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI + && mToken instanceof WindowTokenClient) { + mMainThread.onSystemUiContextCleanup(this); + } } @UnsupportedAppUsage @@ -3205,12 +3231,6 @@ class ContextImpl extends Context { @UnsupportedAppUsage final void setOuterContext(@NonNull Context context) { mOuterContext = context; - // TODO(b/149463653): check if we still need this method after migrating IMS to - // WindowContext. - if (mOuterContext.isUiContext() && mContextType <= CONTEXT_TYPE_DISPLAY_CONTEXT) { - mContextType = CONTEXT_TYPE_WINDOW_CONTEXT; - mIsConfigurationBasedContext = true; - } } @UnsupportedAppUsage @@ -3226,7 +3246,13 @@ class ContextImpl extends Context { @Override public IBinder getWindowContextToken() { - return mContextType == CONTEXT_TYPE_WINDOW_CONTEXT ? mToken : null; + switch (mContextType) { + case CONTEXT_TYPE_WINDOW_CONTEXT: + case CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI: + return mToken; + default: + return null; + } } private void checkMode(int mode) { diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 9833ed60fe4693ac7c0e41fc1352d6a4f80e8045..306035341ea3a4992da9e0604ce40159fea8c3c0 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -151,6 +151,9 @@ public class Dialog implements DialogInterface, Window.Callback, private final Runnable mDismissAction = this::dismissDialog; + /** A {@link Runnable} to run instead of dismissing when {@link #dismiss()} is called. */ + private Runnable mDismissOverride; + /** * Creates a dialog window that uses the default dialog theme. *

@@ -370,6 +373,11 @@ public class Dialog implements DialogInterface, Window.Callback, */ @Override public void dismiss() { + if (mDismissOverride != null) { + mDismissOverride.run(); + return; + } + if (Looper.myLooper() == mHandler.getLooper()) { dismissDialog(); } else { @@ -1354,6 +1362,21 @@ public class Dialog implements DialogInterface, Window.Callback, mDismissMessage = msg; } + /** + * Set a {@link Runnable} to run when this dialog is dismissed instead of directly dismissing + * it. This allows to animate the dialog in its window before dismissing it. + * + * Note that {@code override} should always end up calling this method with {@code null} + * followed by a call to {@link #dismiss() dismiss} to actually dismiss the dialog. + * + * @see #dismiss() + * + * @hide + */ + public void setDismissOverride(@Nullable Runnable override) { + mDismissOverride = override; + } + /** @hide */ public boolean takeCancelAndDismissListeners(@Nullable String msg, @Nullable OnCancelListener cancel, @Nullable OnDismissListener dismiss) { diff --git a/core/java/android/app/DirectAction.java b/core/java/android/app/DirectAction.java index b0ed490369ad90ed8f6e143515db3bb98aa52af0..ac3868b2ece973d78233b66b7f96dcd9768d603c 100644 --- a/core/java/android/app/DirectAction.java +++ b/core/java/android/app/DirectAction.java @@ -22,14 +22,13 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.util.Preconditions; import java.util.Objects; /** - * Represents a abstract action that can be perform on this app. This are requested from + * Represents an abstract action that can be perform on this app. This are requested from * outside the app's UI (eg by SystemUI or assistant). The semantics of these actions are * not specified by the OS. This allows open-ended and scalable approach for defining how * an app interacts with components that expose alternative interaction models to the user diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java index 1f323c378093610b01d771aca5767285a3b6d1b7..ae3a9e6668ab08d3b350db89a79b871ad32d689d 100644 --- a/core/java/android/app/DisabledWallpaperManager.java +++ b/core/java/android/app/DisabledWallpaperManager.java @@ -74,6 +74,11 @@ final class DisabledWallpaperManager extends WallpaperManager { return false; } + private static int unsupportedInt() { + if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception()); + return -1; + } + @Override public Drawable getDrawable() { return unsupported(); @@ -189,12 +194,12 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int getWallpaperId(int which) { - return unsupported(); + return unsupportedInt(); } @Override public int getWallpaperIdForUser(int which, int userId) { - return unsupported(); + return unsupportedInt(); } @Override @@ -209,7 +214,8 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int setResource(int resid, int which) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override @@ -220,19 +226,22 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which, int userId) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override @@ -243,13 +252,15 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup, int which) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override @@ -259,12 +270,12 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int getDesiredMinimumWidth() { - return unsupported(); + return unsupportedInt(); } @Override public int getDesiredMinimumHeight() { - return unsupported(); + return unsupportedInt(); } @Override diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index c6649692d848e8241dd139ad704a5ab26b1fb9af..aba6eb9229f2dcb4cda21abec91a3690149e176e 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -70,6 +70,7 @@ interface IActivityClientController { boolean willActivityBeVisible(in IBinder token); int getDisplayId(in IBinder activityToken); int getTaskForActivity(in IBinder token, in boolean onlyRoot); + IBinder getActivityTokenBelow(IBinder token); ComponentName getCallingActivity(in IBinder token); String getCallingPackage(in IBinder token); int getLaunchedFromUid(in IBinder token); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index dfc4a6aecabba4fa835c8b275a5966759ec9c51f..8f2417b1955f914b2fe65ef4e406a62cffc9644f 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -326,10 +326,10 @@ interface IActivityManager { void handleApplicationStrictModeViolation(in IBinder app, int penaltyMask, in StrictMode.ViolationInfo crashInfo); boolean isTopActivityImmersive(); - void crashApplication(int uid, int initialPid, in String packageName, int userId, - in String message, boolean force); void crashApplicationWithType(int uid, int initialPid, in String packageName, int userId, in String message, boolean force, int exceptionTypeId); + void crashApplicationWithTypeWithExtras(int uid, int initialPid, in String packageName, + int userId, in String message, boolean force, int exceptionTypeId, in Bundle extras); /** @deprecated -- use getProviderMimeTypeAsync */ @UnsupportedAppUsage(maxTargetSdk = 29, publicAlternatives = "Use {@link android.content.ContentResolver#getType} public API instead.") @@ -347,7 +347,10 @@ interface IActivityManager { void setPackageScreenCompatMode(in String packageName, int mode); @UnsupportedAppUsage boolean switchUser(int userid); + String getSwitchingFromUserMessage(); + String getSwitchingToUserMessage(); @UnsupportedAppUsage + void setStopUserOnSwitch(int value); boolean removeTask(int taskId); @UnsupportedAppUsage void registerProcessObserver(in IProcessObserver observer); diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 74d51a0bcf63fbd0c8416d92f06b09b0609d0b00..2be78033ddf7032786f972582d6c02a74dc27213 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -330,4 +330,18 @@ interface IActivityTaskManager { * When the Picture-in-picture state has changed. */ void onPictureInPictureStateChanged(in PictureInPictureUiState pipState); + + /** + * Re-attach navbar to the display during a recents transition. + * TODO(188595497): Remove this once navbar attachment is in shell. + */ + void detachNavigationBarFromApp(in IBinder transition); + + /** + * Marks a process as a delegate for the currently playing remote transition animation. This + * must be called from a process that is already a remote transition player or delegate. Any + * marked delegates are cleaned-up automatically at the end of the transition. + * @param caller is the IApplicationThread representing the calling process. + */ + void setRunningRemoteTransitionDelegate(in IApplicationThread caller); } diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index d6ff6d3dfc3a5da8d4c0478bfb06d835e10407d6..448c3cf48e102b8f40d91f40908398ee88f31422 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -107,7 +107,7 @@ oneway interface IApplicationThread { void scheduleOnNewActivityOptions(IBinder token, in Bundle options); void scheduleSuicide(); void dispatchPackageBroadcast(int cmd, in String[] packages); - void scheduleCrash(in String msg, int typeId); + void scheduleCrash(in String msg, int typeId, in Bundle extras); void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, in String path, in ParcelFileDescriptor fd, in RemoteCallback finishCallback); void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix, diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index b2184fe65887c59ccab5c2df625f93c884d4d184..fd6fa57b9e8d1780d1dac7b9be00b9ef42700032 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -742,6 +742,18 @@ public class Instrumentation { } } + /** + * This overload is used for notifying the {@link android.window.TaskFragmentOrganizer} + * implementation internally about started activities. + * + * @see #onStartActivity(Intent) + * @hide + */ + public ActivityResult onStartActivity(@NonNull Context who, @NonNull Intent intent, + @NonNull Bundle options) { + return onStartActivity(intent); + } + /** * Used for intercepting any started activity. * @@ -1722,7 +1734,10 @@ public class Instrumentation { final ActivityMonitor am = mActivityMonitors.get(i); ActivityResult result = null; if (am.ignoreMatchingSpecificIntents()) { - result = am.onStartActivity(intent); + if (options == null) { + options = ActivityOptions.makeBasic().toBundle(); + } + result = am.onStartActivity(who, intent, options); } if (result != null) { am.mHits++; @@ -1790,7 +1805,10 @@ public class Instrumentation { final ActivityMonitor am = mActivityMonitors.get(i); ActivityResult result = null; if (am.ignoreMatchingSpecificIntents()) { - result = am.onStartActivity(intents[0]); + if (options == null) { + options = ActivityOptions.makeBasic().toBundle(); + } + result = am.onStartActivity(who, intents[0], options); } if (result != null) { am.mHits++; @@ -1861,7 +1879,10 @@ public class Instrumentation { final ActivityMonitor am = mActivityMonitors.get(i); ActivityResult result = null; if (am.ignoreMatchingSpecificIntents()) { - result = am.onStartActivity(intent); + if (options == null) { + options = ActivityOptions.makeBasic().toBundle(); + } + result = am.onStartActivity(who, intent, options); } if (result != null) { am.mHits++; @@ -1928,7 +1949,10 @@ public class Instrumentation { final ActivityMonitor am = mActivityMonitors.get(i); ActivityResult result = null; if (am.ignoreMatchingSpecificIntents()) { - result = am.onStartActivity(intent); + if (options == null) { + options = ActivityOptions.makeBasic().toBundle(); + } + result = am.onStartActivity(who, intent, options); } if (result != null) { am.mHits++; @@ -1974,7 +1998,10 @@ public class Instrumentation { final ActivityMonitor am = mActivityMonitors.get(i); ActivityResult result = null; if (am.ignoreMatchingSpecificIntents()) { - result = am.onStartActivity(intent); + if (options == null) { + options = ActivityOptions.makeBasic().toBundle(); + } + result = am.onStartActivity(who, intent, options); } if (result != null) { am.mHits++; @@ -2021,7 +2048,10 @@ public class Instrumentation { final ActivityMonitor am = mActivityMonitors.get(i); ActivityResult result = null; if (am.ignoreMatchingSpecificIntents()) { - result = am.onStartActivity(intent); + if (options == null) { + options = ActivityOptions.makeBasic().toBundle(); + } + result = am.onStartActivity(who, intent, options); } if (result != null) { am.mHits++; diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index f6d27e125cd7752173cf287081555ee574c1fd91..5750484c6169b054f451ebd6840680daac96b3a9 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -2163,4 +2163,38 @@ public final class LoadedApk { final IBinder mService; } } + + /** + * Check if the Apk paths in the cache are correct, and update them if they are not. + * @hide + */ + public static void checkAndUpdateApkPaths(ApplicationInfo expectedAppInfo) { + // Get the LoadedApk from the cache + ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread == null) { + Log.e(TAG, "Cannot find activity thread"); + return; + } + checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ true); + checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ false); + } + + private static void checkAndUpdateApkPaths(ActivityThread activityThread, + ApplicationInfo expectedAppInfo, boolean cacheWithCode) { + String expectedCodePath = expectedAppInfo.getCodePath(); + LoadedApk loadedApk = activityThread.peekPackageInfo( + expectedAppInfo.packageName, /* includeCode= */ cacheWithCode); + // If there is load apk cached, or if the cache is valid, don't do anything. + if (loadedApk == null || loadedApk.getApplicationInfo() == null + || loadedApk.getApplicationInfo().getCodePath().equals(expectedCodePath)) { + return; + } + // Duplicate framework logic + List oldPaths = new ArrayList<>(); + LoadedApk.makePaths(activityThread, expectedAppInfo, oldPaths); + + // Force update the LoadedApk instance, which should update the reference in the cache + loadedApk.updateApplicationInfo(expectedAppInfo, oldPaths); + } + } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 61b1abe25ca2978bc2594726d22523310c4ce24f..2c02be7dc6b976131012a8cca9e8d189900c0169 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5810,6 +5810,7 @@ public class Notification implements Parcelable p, result); buildCustomContentIntoTemplate(mContext, standard, customContent, p, result); + makeHeaderExpanded(standard); return standard; } @@ -6793,7 +6794,7 @@ public class Notification implements Parcelable // We show these sorts of notifications immediately in the absence of // any explicit app declaration - if (isMediaNotification() || hasMediaSession() + if (isMediaNotification() || CATEGORY_CALL.equals(category) || CATEGORY_NAVIGATION.equals(category) || (actions != null && actions.length > 0)) { @@ -6812,14 +6813,6 @@ public class Notification implements Parcelable return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior; } - /** - * @return whether this notification has a media session attached - * @hide - */ - public boolean hasMediaSession() { - return extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null; - } - /** * @return the style class of this notification * @hide @@ -6863,18 +6856,20 @@ public class Notification implements Parcelable } /** - * @return true if this is a media notification + * @return true if this is a media style notification with a media session * * @hide */ public boolean isMediaNotification() { Class style = getNotificationStyle(); - if (MediaStyle.class.equals(style)) { - return true; - } else if (DecoratedMediaCustomViewStyle.class.equals(style)) { - return true; - } - return false; + boolean isMediaStyle = (MediaStyle.class.equals(style) + || DecoratedMediaCustomViewStyle.class.equals(style)); + + boolean hasMediaSession = (extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null + && extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) + instanceof MediaSession.Token); + + return isMediaStyle && hasMediaSession; } /** diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java index 4b32463e2996dc0567e608800fabfc71fb4ab131..e220627706f924217f62ce75654dec8d8a90fba2 100644 --- a/core/java/android/app/RemoteServiceException.java +++ b/core/java/android/app/RemoteServiceException.java @@ -16,23 +16,133 @@ package android.app; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Bundle; import android.util.AndroidRuntimeException; /** - * Exception used by {@link ActivityThread} to crash an app process. + * Exception used by {@link ActivityThread} to crash an app process for an unknown cause. + * An exception of this class is no longer supposed to be thrown. Instead, we use fine-grained + * sub-exceptions. + * + * Subclasses must be registered in + * {@link android.app.ActivityThread#throwRemoteServiceException(java.lang.String, int)}. * * @hide */ public class RemoteServiceException extends AndroidRuntimeException { + public RemoteServiceException(String msg) { + super(msg); + } + + public RemoteServiceException(String msg, Throwable cause) { + super(msg, cause); + } + /** - * The type ID passed to {@link IApplicationThread#scheduleCrash}. + * Exception used to crash an app process when it didn't call {@link Service#startForeground} + * in time after the service was started with + * {@link android.content.Context#startForegroundService}. * - * Assign a unique ID to each subclass. See the above method for the numbers that are already - * taken. + * @hide */ - public static final int TYPE_ID = 0; + public static class ForegroundServiceDidNotStartInTimeException extends RemoteServiceException { + /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ + public static final int TYPE_ID = 1; - public RemoteServiceException(String msg) { - super(msg); + private static final String KEY_SERVICE_CLASS_NAME = "serviceclassname"; + + public ForegroundServiceDidNotStartInTimeException(String msg, Throwable cause) { + super(msg, cause); + } + + public static Bundle createExtrasForService(@NonNull ComponentName service) { + Bundle b = new Bundle(); + b.putString(KEY_SERVICE_CLASS_NAME, service.getClassName()); + return b; + } + + @Nullable + public static String getServiceClassNameFromExtras(@Nullable Bundle extras) { + return (extras == null) ? null : extras.getString(KEY_SERVICE_CLASS_NAME); + } + } + + /** + * Exception used to crash an app process when the system received a RemoteException + * while delivering a broadcast to an app process. + * + * @hide + */ + public static class CannotDeliverBroadcastException extends RemoteServiceException { + /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ + public static final int TYPE_ID = 2; + + public CannotDeliverBroadcastException(String msg) { + super(msg); + } + } + + /** + * Exception used to crash an app process when the system received a RemoteException + * while posting a notification of a foreground service. + * + * @hide + */ + public static class CannotPostForegroundServiceNotificationException + extends RemoteServiceException { + /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ + public static final int TYPE_ID = 3; + + public CannotPostForegroundServiceNotificationException(String msg) { + super(msg); + } + } + + /** + * Exception used to crash an app process when the system finds an error in a foreground service + * notification. + * + * @hide + */ + public static class BadForegroundServiceNotificationException extends RemoteServiceException { + /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ + public static final int TYPE_ID = 4; + + public BadForegroundServiceNotificationException(String msg) { + super(msg); + } + } + + /** + * Exception used to crash an app process when it calls a setting activity that requires + * the {@code REQUEST_PASSWORD_COMPLEXITY} permission. + * + * @hide + */ + public static class MissingRequestPasswordComplexityPermissionException + extends RemoteServiceException { + /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ + public static final int TYPE_ID = 5; + + public MissingRequestPasswordComplexityPermissionException(String msg) { + super(msg); + } + } + + /** + * Exception used to crash an app process by {@code adb shell am crash}. + * + * @hide + */ + public static class CrashedByAdbException extends RemoteServiceException { + /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ + public static final int TYPE_ID = 6; + + public CrashedByAdbException(String msg) { + super(msg); + } } } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index ab823983235cb086e5c8ee3a2ee17a9fbd4a42ae..f360bbed01567e2a4e67f4e0d9642178197105f7 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -692,7 +692,7 @@ public class ResourcesManager { * @return true if activity resources override config matches the provided one or they are both * null, false otherwise. */ - boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken, + public boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken, @Nullable Configuration overrideConfig) { synchronized (mLock) { final ActivityResources activityResources diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 33638720441062d4e53845c086b1aa8d4b73a8a4..5a5ccb5d8987a36a09915ff044bf1e0123b3b9e6 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -33,9 +33,12 @@ import android.content.res.Configuration; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Log; import android.view.contentcapture.ContentCaptureManager; +import com.android.internal.annotations.GuardedBy; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -733,6 +736,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac mActivityManager.setServiceForeground( new ComponentName(this, mClassName), mToken, id, notification, 0, FOREGROUND_SERVICE_TYPE_MANIFEST); + clearStartForegroundServiceStackTrace(); } catch (RemoteException ex) { } } @@ -786,6 +790,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac mActivityManager.setServiceForeground( new ComponentName(this, mClassName), mToken, id, notification, 0, foregroundServiceType); + clearStartForegroundServiceStackTrace(); } catch (RemoteException ex) { } } @@ -941,4 +946,34 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac private IActivityManager mActivityManager = null; @UnsupportedAppUsage private boolean mStartCompatibility = false; + + /** + * This keeps track of the stacktrace where Context.startForegroundService() was called + * for each service class. We use that when we crash the app for not calling + * {@link #startForeground} in time, in {@link ActivityThread#throwRemoteServiceException}. + */ + @GuardedBy("sStartForegroundServiceStackTraces") + private static final ArrayMap sStartForegroundServiceStackTraces = + new ArrayMap<>(); + + /** @hide */ + public static void setStartForegroundServiceStackTrace( + @NonNull String className, @NonNull StackTrace stacktrace) { + synchronized (sStartForegroundServiceStackTraces) { + sStartForegroundServiceStackTraces.put(className, stacktrace); + } + } + + private void clearStartForegroundServiceStackTrace() { + synchronized (sStartForegroundServiceStackTraces) { + sStartForegroundServiceStackTraces.remove(this.getClassName()); + } + } + + /** @hide */ + public static StackTrace getStartForegroundServiceStackTrace(@NonNull String className) { + synchronized (sStartForegroundServiceStackTraces) { + return sStartForegroundServiceStackTraces.get(className); + } + } } diff --git a/core/java/android/app/ServiceStartNotAllowedException.java b/core/java/android/app/ServiceStartNotAllowedException.java index 33285b2190eb54a1a46ee710db43511e518fc181..b1f47eee4bfd71c75461333a18f8169942262ca0 100644 --- a/core/java/android/app/ServiceStartNotAllowedException.java +++ b/core/java/android/app/ServiceStartNotAllowedException.java @@ -40,4 +40,11 @@ public abstract class ServiceStartNotAllowedException extends IllegalStateExcept return new BackgroundServiceStartNotAllowedException(message); } } + + @Override + public synchronized Throwable getCause() { + // "Cause" is often used for clustering exceptions, and developers don't want to have it + // for this exception. b/210890426 + return null; + } } diff --git a/core/java/android/app/ForegroundServiceDidNotStartInTimeException.java b/core/java/android/app/StackTrace.java similarity index 57% rename from core/java/android/app/ForegroundServiceDidNotStartInTimeException.java rename to core/java/android/app/StackTrace.java index 364291e69ad6248de21399e76d6ab35f143a3c7c..ec058f88118bc45e57e5e31bc7c639a24212af17 100644 --- a/core/java/android/app/ForegroundServiceDidNotStartInTimeException.java +++ b/core/java/android/app/StackTrace.java @@ -17,17 +17,11 @@ package android.app; /** - * Exception used to crash an app process when it didn't call {@link Service#startForeground} - * in time after the service was started with - * {@link android.content.Context#startForegroundService}. - * + * An Exception subclass that's used only for logging stacktraces. * @hide */ -public class ForegroundServiceDidNotStartInTimeException extends RemoteServiceException { - /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ - public static final int TYPE_ID = 1; - - public ForegroundServiceDidNotStartInTimeException(String msg) { - super(msg); +public class StackTrace extends Exception { + public StackTrace(String message) { + super(message); } } diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 77bcef3ae009f266f8ca4ad5feb494d9922bd682..be702c2a17568881c52364137bdaf8185d79b9b0 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -46,7 +46,7 @@ import java.lang.annotation.RetentionPolicy; */ @SystemService(Context.STATUS_BAR_SERVICE) public class StatusBarManager { - + // LINT.IfChange /** @hide */ public static final int DISABLE_EXPAND = View.STATUS_BAR_DISABLE_EXPAND; /** @hide */ @@ -144,6 +144,7 @@ public class StatusBarManager { }) @Retention(RetentionPolicy.SOURCE) public @interface Disable2Flags {} + // LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt) /** * Default disable flags for setup diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 85758a92fa9860dde9c038b02ed0887c37d00fcd..ddde27220b96a54f0216786c1cae93f11c8ae3ea 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -17,6 +17,7 @@ package android.app; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import android.annotation.NonNull; import android.annotation.Nullable; @@ -118,6 +119,12 @@ public class TaskInfo { */ public int displayId; + /** + * The feature id of {@link com.android.server.wm.TaskDisplayArea} this task is associated with. + * @hide + */ + public int displayAreaFeatureId = FEATURE_UNDEFINED; + /** * The recent activity values for the highest activity in the stack to have set the values. * {@link Activity#setTaskDescription(android.app.ActivityManager.TaskDescription)}. @@ -243,6 +250,12 @@ public class TaskInfo { */ public boolean isVisible; + /** + * Whether this task is sleeping due to sleeping display. + * @hide + */ + public boolean isSleeping; + TaskInfo() { // Do nothing } @@ -251,6 +264,13 @@ public class TaskInfo { readFromParcel(source); } + /** + * Whether this task is visible. + */ + public boolean isVisible() { + return isVisible; + } + /** * @param isLowResolution * @return @@ -329,11 +349,10 @@ public class TaskInfo { } /** - * Returns {@code true} if parameters that are important for task organizers have changed - * and {@link com.android.server.wm.TaskOrginizerController} needs to notify listeners - * about that. - * @hide - */ + * Returns {@code true} if the parameters that are important for task organizers are equal + * between this {@link TaskInfo} and {@param that}. + * @hide + */ public boolean equalsForTaskOrganizer(@Nullable TaskInfo that) { if (that == null) { return false; @@ -341,13 +360,16 @@ public class TaskInfo { return topActivityType == that.topActivityType && isResizeable == that.isResizeable && supportsMultiWindow == that.supportsMultiWindow + && displayAreaFeatureId == that.displayAreaFeatureId && Objects.equals(positionInParent, that.positionInParent) && Objects.equals(pictureInPictureParams, that.pictureInPictureParams) && Objects.equals(displayCutoutInsets, that.displayCutoutInsets) && getWindowingMode() == that.getWindowingMode() && Objects.equals(taskDescription, that.taskDescription) && isFocused == that.isFocused - && isVisible == that.isVisible; + && isVisible == that.isVisible + && isSleeping == that.isSleeping + && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId); } /** @@ -402,8 +424,10 @@ public class TaskInfo { parentTaskId = source.readInt(); isFocused = source.readBoolean(); isVisible = source.readBoolean(); + isSleeping = source.readBoolean(); topActivityInSizeCompat = source.readBoolean(); mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR); + displayAreaFeatureId = source.readInt(); } /** @@ -440,8 +464,10 @@ public class TaskInfo { dest.writeInt(parentTaskId); dest.writeBoolean(isFocused); dest.writeBoolean(isVisible); + dest.writeBoolean(isSleeping); dest.writeBoolean(topActivityInSizeCompat); dest.writeTypedObject(mTopActivityLocusId, flags); + dest.writeInt(displayAreaFeatureId); } @Override @@ -468,8 +494,10 @@ public class TaskInfo { + " parentTaskId=" + parentTaskId + " isFocused=" + isFocused + " isVisible=" + isVisible + + " isSleeping=" + isSleeping + " topActivityInSizeCompat=" + topActivityInSizeCompat - + " locusId= " + mTopActivityLocusId + + " locusId=" + mTopActivityLocusId + + " displayAreaFeatureId=" + displayAreaFeatureId + "}"; } } diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index e9b01750b3b10af10e28e958464b1b9ba0a22eba..99d406446dae8406cfa83f2e5069d3f79a183330 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -81,6 +81,7 @@ public final class WallpaperInfo implements Parcelable { final int mContextDescriptionResource; final boolean mShowMetadataInPreview; final boolean mSupportsAmbientMode; + final boolean mShouldUseDefaultUnfoldTransition; final String mSettingsSliceUri; final boolean mSupportMultipleDisplays; @@ -145,6 +146,9 @@ public final class WallpaperInfo implements Parcelable { mSupportsAmbientMode = sa.getBoolean( com.android.internal.R.styleable.Wallpaper_supportsAmbientMode, false); + mShouldUseDefaultUnfoldTransition = sa.getBoolean( + com.android.internal.R.styleable + .Wallpaper_shouldUseDefaultUnfoldTransition, true); mSettingsSliceUri = sa.getString( com.android.internal.R.styleable.Wallpaper_settingsSliceUri); mSupportMultipleDisplays = sa.getBoolean( @@ -171,6 +175,7 @@ public final class WallpaperInfo implements Parcelable { mSupportsAmbientMode = source.readInt() != 0; mSettingsSliceUri = source.readString(); mSupportMultipleDisplays = source.readInt() != 0; + mShouldUseDefaultUnfoldTransition = source.readInt() != 0; mService = ResolveInfo.CREATOR.createFromParcel(source); } @@ -393,6 +398,28 @@ public final class WallpaperInfo implements Parcelable { return mSupportMultipleDisplays; } + /** + * Returns whether this wallpaper should receive default zooming updates when the device + * changes its state (e.g. when folding or unfolding a foldable device). + * If set to false the wallpaper will not receive zoom events when changing the device state, + * so it can implement its own transition instead. + *

+ * This corresponds to the value {@link + * android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition} in the + * XML description of the wallpaper. + *

+ * The default value is {@code true}. + * + * @see android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition + * @return {@code true} if wallpaper should receive default device state change + * transition updates + * + * @attr ref android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition + */ + public boolean shouldUseDefaultUnfoldTransition() { + return mShouldUseDefaultUnfoldTransition; + } + public void dump(Printer pw, String prefix) { pw.println(prefix + "Service:"); mService.dump(pw, prefix + " "); @@ -423,6 +450,7 @@ public final class WallpaperInfo implements Parcelable { dest.writeInt(mSupportsAmbientMode ? 1 : 0); dest.writeString(mSettingsSliceUri); dest.writeInt(mSupportMultipleDisplays ? 1 : 0); + dest.writeInt(mShouldUseDefaultUnfoldTransition ? 1 : 0); mService.writeToParcel(dest, flags); } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index ab645960ee2cf5d789921cb7c5f59b1b60540195..fca4c698c49ce3a3fb6eaeedc717808bff0b806c 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -220,12 +220,34 @@ public class WallpaperManager { */ public static final String COMMAND_REAPPLY = "android.wallpaper.reapply"; + /** + * Command for {@link #sendWallpaperCommand}: reported when the live wallpaper needs to be + * frozen. + * @hide + */ + public static final String COMMAND_FREEZE = "android.wallpaper.freeze"; + + /** + * Command for {@link #sendWallpaperCommand}: reported when the live wallapper doesn't need + * to be frozen anymore. + * @hide + */ + public static final String COMMAND_UNFREEZE = "android.wallpaper.unfreeze"; + /** * Extra passed back from setWallpaper() giving the new wallpaper's assigned ID. * @hide */ public static final String EXTRA_NEW_WALLPAPER_ID = "android.service.wallpaper.extra.ID"; + /** + * Extra passed on {@link Intent.ACTION_WALLPAPER_CHANGED} indicating if wallpaper was set from + * a foreground app. + * @hide + */ + public static final String EXTRA_FROM_FOREGROUND_APP = + "android.service.wallpaper.extra.FROM_FOREGROUND_APP"; + // flags for which kind of wallpaper to act on /** @hide */ @@ -343,17 +365,18 @@ public class WallpaperManager { private int mCachedWallpaperUserId; private Bitmap mDefaultWallpaper; private Handler mMainLooperHandler; - private ArrayMap> mLocalColorAreas = - new ArrayMap<>(); + private ArrayMap> mLocalColorCallbackAreas = + new ArrayMap<>(); private ILocalWallpaperColorConsumer mLocalColorCallback = new ILocalWallpaperColorConsumer.Stub() { @Override public void onColorsChanged(RectF area, WallpaperColors colors) { - ArraySet callbacks = - mLocalColorAreas.get(area); - if (callbacks == null) return; - for (LocalWallpaperColorConsumer callback: callbacks) { - callback.onColorsChanged(area, colors); + for (LocalWallpaperColorConsumer callback : + mLocalColorCallbackAreas.keySet()) { + ArraySet areas = mLocalColorCallbackAreas.get(callback); + if (areas != null && areas.contains(area)) { + callback.onColorsChanged(area, colors); + } } } }; @@ -398,46 +421,52 @@ public class WallpaperManager { } } - public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback, + public void addOnColorsChangedListener( + @NonNull LocalWallpaperColorConsumer callback, @NonNull List regions, int which, int userId, int displayId) { - for (RectF area: regions) { - ArraySet callbacks = mLocalColorAreas.get(area); - if (callbacks == null) { - callbacks = new ArraySet<>(); - mLocalColorAreas.put(area, callbacks); + synchronized (this) { + for (RectF area : regions) { + ArraySet areas = mLocalColorCallbackAreas.get(callback); + if (areas == null) { + areas = new ArraySet<>(); + mLocalColorCallbackAreas.put(callback, areas); + } + areas.add(area); + } + try { + // one way returns immediately + mService.addOnLocalColorsChangedListener(mLocalColorCallback, regions, which, + userId, displayId); + } catch (RemoteException e) { + // Can't get colors, connection lost. + Log.e(TAG, "Can't register for local color updates", e); } - callbacks.add(callback); - } - try { - mService.addOnLocalColorsChangedListener(mLocalColorCallback , regions, which, - userId, displayId); - } catch (RemoteException e) { - // Can't get colors, connection lost. - Log.e(TAG, "Can't register for local color updates", e); } } public void removeOnColorsChangedListener( @NonNull LocalWallpaperColorConsumer callback, int which, int userId, int displayId) { - final ArrayList removeAreas = new ArrayList<>(); - for (RectF area : mLocalColorAreas.keySet()) { - ArraySet callbacks = mLocalColorAreas.get(area); - if (callbacks == null) continue; - callbacks.remove(callback); - if (callbacks.size() == 0) { - mLocalColorAreas.remove(area); - removeAreas.add(area); + synchronized (this) { + final ArraySet removeAreas = mLocalColorCallbackAreas.remove(callback); + if (removeAreas == null || removeAreas.size() == 0) { + return; } - } - try { - if (removeAreas.size() > 0) { - mService.removeOnLocalColorsChangedListener( - mLocalColorCallback, removeAreas, which, userId, displayId); + for (LocalWallpaperColorConsumer cb : mLocalColorCallbackAreas.keySet()) { + ArraySet areas = mLocalColorCallbackAreas.get(cb); + if (areas != null && cb != callback) removeAreas.removeAll(areas); + } + try { + if (removeAreas.size() > 0) { + // one way returns immediately + mService.removeOnLocalColorsChangedListener( + mLocalColorCallback, new ArrayList(removeAreas), which, userId, + displayId); + } + } catch (RemoteException e) { + // Can't get colors, connection lost. + Log.e(TAG, "Can't unregister for local color updates", e); } - } catch (RemoteException e) { - // Can't get colors, connection lost. - Log.e(TAG, "Can't unregister for local color updates", e); } } @@ -548,7 +577,7 @@ public class WallpaperManager { } if (returnDefault) { Bitmap defaultWallpaper = mDefaultWallpaper; - if (defaultWallpaper == null) { + if (defaultWallpaper == null || defaultWallpaper.isRecycled()) { defaultWallpaper = getDefaultWallpaper(context, which); synchronized (this) { mDefaultWallpaper = defaultWallpaper; @@ -572,12 +601,12 @@ public class WallpaperManager { Rect dimensions = null; synchronized (this) { + ParcelFileDescriptor pfd = null; try { Bundle params = new Bundle(); + pfd = mService.getWallpaperWithFeature(context.getOpPackageName(), + context.getAttributionTag(), this, FLAG_SYSTEM, params, userId); // Let's peek user wallpaper first. - ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( - context.getOpPackageName(), context.getAttributionTag(), this, - FLAG_SYSTEM, params, userId); if (pfd != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; @@ -586,6 +615,13 @@ public class WallpaperManager { } } catch (RemoteException ex) { Log.w(TAG, "peek wallpaper dimensions failed", ex); + } finally { + if (pfd != null) { + try { + pfd.close(); + } catch (IOException ignored) { + } + } } } // If user wallpaper is unavailable, may be the default one instead. @@ -1452,27 +1488,18 @@ public class WallpaperManager { mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; - final Bitmap tmp = BitmapFactory.decodeStream(resources.openRawResource(resid)); + boolean ok = false; try { - // If the stream can't be decoded, treat it as an invalid input. - if (tmp != null) { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); - // The 'close()' is the trigger for any server-side image manipulation, - // so we must do that before waiting for completion. - fos.close(); - completion.waitForCompletion(); - } else { - throw new IllegalArgumentException( - "Resource 0x" + Integer.toHexString(resid) + " is invalid"); - } + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + copyStreamToWallpaperFile(resources.openRawResource(resid), fos); + // The 'close()' is the trigger for any server-side image manipulation, + // so we must do that before waiting for completion. + fos.close(); + completion.waitForCompletion(); } finally { // Might be redundant but completion shouldn't wait unless the write // succeeded; this is a fallback if it threw past the close+wait. IoUtils.closeQuietly(fos); - if (tmp != null) { - tmp.recycle(); - } } } } catch (RemoteException e) { @@ -1714,22 +1741,13 @@ public class WallpaperManager { result, which, completion, mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; - final Bitmap tmp = BitmapFactory.decodeStream(bitmapData); try { - // If the stream can't be decoded, treat it as an invalid input. - if (tmp != null) { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); - fos.close(); - completion.waitForCompletion(); - } else { - throw new IllegalArgumentException("InputStream is invalid"); - } + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + copyStreamToWallpaperFile(bitmapData, fos); + fos.close(); + completion.waitForCompletion(); } finally { IoUtils.closeQuietly(fos); - if (tmp != null) { - tmp.recycle(); - } } } } catch (RemoteException e) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3840f760eda8b07b38155b8db8aa634119b7acb6..550d1d8112282bc3d685a26432c5277f8ae2d93b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -679,8 +679,9 @@ public class DevicePolicyManager { * A String extra holding the time zone {@link android.app.AlarmManager} that the device * will be set to. * - *

Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + *

Use only for device owner provisioning. This extra can be returned by the admin app when + * performing the admin-integrated provisioning flow as a result of the {@link + * #ACTION_GET_PROVISIONING_MODE} activity. */ public static final String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE"; @@ -689,8 +690,9 @@ public class DevicePolicyManager { * A Long extra holding the wall clock time (in milliseconds) to be set on the device's * {@link android.app.AlarmManager}. * - *

Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + *

Use only for device owner provisioning. This extra can be returned by the admin app when + * performing the admin-integrated provisioning flow as a result of the {@link + * #ACTION_GET_PROVISIONING_MODE} activity. */ public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME"; @@ -699,8 +701,9 @@ public class DevicePolicyManager { * A String extra holding the {@link java.util.Locale} that the device will be set to. * Format: xx_yy, where xx is the language code, and yy the country code. * - *

Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + *

Use only for device owner provisioning. This extra can be returned by the admin app when + * performing the admin-integrated provisioning flow as a result of the {@link + * #ACTION_GET_PROVISIONING_MODE} activity. */ public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE"; @@ -996,7 +999,10 @@ public class DevicePolicyManager { * The default for this extra is {@code false} - by default, the admin of a fully-managed * device has the ability to grant sensors-related permissions. * - *

Use only for device owner provisioning. + *

Use only for device owner provisioning. This extra can be returned by the + * admin app when performing the admin-integrated provisioning flow as a result of the + * {@link #ACTION_GET_PROVISIONING_MODE} activity. + * * @see #ACTION_GET_PROVISIONING_MODE */ public static final String EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT = @@ -1065,6 +1071,9 @@ public class DevicePolicyManager { * *

From {@link android.os.Build.VERSION_CODES#N} onwards, this is also supported for an * intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE}. + * + *

This extra can also be returned by the admin app when performing the admin-integrated + * provisioning flow as a result of the {@link #ACTION_GET_PROVISIONING_MODE} activity. */ public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; @@ -1104,8 +1113,9 @@ public class DevicePolicyManager { * *

Maximum 3 key-value pairs can be specified. The rest will be ignored. * - *

Use in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE} or - * {@link #ACTION_PROVISION_MANAGED_DEVICE} + *

Can be used in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE}. This + * extra can also be returned by the admin app when performing the admin-integrated + * provisioning flow as a result of the {@link #ACTION_GET_PROVISIONING_MODE} activity. */ public static final String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS"; @@ -3437,6 +3447,9 @@ public class DevicePolicyManager { * set on the primary {@link DevicePolicyManager} must be cleared first by calling * {@link #setRequiredPasswordComplexity} with {@link #PASSWORD_COMPLEXITY_NONE) first. * + *

Note: this method is ignored on + * {PackageManager#FEATURE_AUTOMOTIVE automotive builds}. + * * @deprecated Prefer using {@link #setRequiredPasswordComplexity(int)}, to require a password * that satisfies a complexity level defined by the platform, rather than specifying custom * password requirement. @@ -3527,12 +3540,14 @@ public class DevicePolicyManager { * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. *

- * * Apps targeting {@link android.os.Build.VERSION_CODES#R} and below can call this method on the * {@link DevicePolicyManager} instance returned by * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent * profile. * + *

Note: this method is ignored on + * {PackageManager#FEATURE_AUTOMOTIVE automotive builds}. + * * @deprecated see {@link #setPasswordQuality(ComponentName, int)} for details. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -3610,12 +3625,14 @@ public class DevicePolicyManager { * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. *

- * * Apps targeting {@link android.os.Build.VERSION_CODES#R} and below can call this method on the * {@link DevicePolicyManager} instance returned by * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent * profile. * + *

Note: this method is ignored on + * {PackageManager#FEATURE_AUTOMOTIVE automotive builds}. + * * @deprecated see {@link #setPasswordQuality(ComponentName, int)} for details. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -3700,12 +3717,14 @@ public class DevicePolicyManager { * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. *

- * * Apps targeting {@link android.os.Build.VERSION_CODES#R} and below can call this method on the * {@link DevicePolicyManager} instance returned by * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent * profile. * + *

Note: this method is ignored on + * {PackageManager#FEATURE_AUTOMOTIVE automotive builds}. + * * @deprecated see {@link #setPasswordQuality(ComponentName, int)} for details. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -3790,12 +3809,14 @@ public class DevicePolicyManager { * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. *

- * * Apps targeting {@link android.os.Build.VERSION_CODES#R} and below can call this method on the * {@link DevicePolicyManager} instance returned by * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent * profile. * + *

Note: this method is ignored on + * {PackageManager#FEATURE_AUTOMOTIVE automotive builds}. + * * @deprecated see {@link #setPasswordQuality(ComponentName, int)} for details. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -3879,12 +3900,14 @@ public class DevicePolicyManager { * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. *

- * * Apps targeting {@link android.os.Build.VERSION_CODES#R} and below can call this method on the * {@link DevicePolicyManager} instance returned by * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent * profile. * + *

Note: this method is ignored on + * {PackageManager#FEATURE_AUTOMOTIVE automotive builds}. + * * @deprecated see {@link #setPasswordQuality(ComponentName, int)} for details. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -3968,12 +3991,14 @@ public class DevicePolicyManager { * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. *

- * * Apps targeting {@link android.os.Build.VERSION_CODES#R} and below can call this method on the * {@link DevicePolicyManager} instance returned by * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent * profile. * + *

Note: this method is ignored on + * {PackageManager#FEATURE_AUTOMOTIVE automotive builds}. + * * @deprecated see {@link #setPasswordQuality(ComponentName, int)} for details. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -4056,12 +4081,14 @@ public class DevicePolicyManager { * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. *

- * * Apps targeting {@link android.os.Build.VERSION_CODES#R} and below can call this method on the * {@link DevicePolicyManager} instance returned by * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent * profile. * + *

Note: this method is ignored on + * {PackageManager#FEATURE_AUTOMOTIVE automotive builds}. + * * @deprecated see {@link #setPasswordQuality(ComponentName, int)} for details. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -7726,27 +7753,64 @@ public class DevicePolicyManager { } /** - * @hide - * Sets the given package as the device owner. The package must already be installed. There - * must not already be a device owner. - * Only apps with the MANAGE_PROFILE_AND_DEVICE_OWNERS permission and the shell uid can call - * this method. - * Calling this after the setup phase of the primary user has completed is allowed only if - * the caller is the shell uid, and there are no additional users and no accounts. + * Sets the given package as the device owner. + * + *

Preconditions: + *

    + *
  • The package must already be installed. + *
  • There must not already be a device owner. + *
  • Only apps with the {@code MANAGE_PROFILE_AND_DEVICE_OWNERS} permission or the + * {@link Process#SHELL_UID Shell UID} can call this method. + *
+ * + *

Calling this after the setup phase of the device owner user has completed is allowed only + * if the caller is the {@link Process#SHELL_UID Shell UID}, and there are no additional users + * (except when the device runs on headless system user mode, in which case it could have exact + * one extra user, which is the current user - the device owner will be set in the + * {@link UserHandle#SYSTEM system} user and a profile owner will be set in the current user) + * and no accounts. + * * @param who the component name to be registered as device owner. * @param ownerName the human readable name of the institution that owns this device. * @param userId ID of the user on which the device owner runs. + * * @return whether the package was successfully registered as the device owner. - * @throws IllegalArgumentException if the package name is null or invalid + * + * @throws IllegalArgumentException if the package name is {@code null} or invalid. * @throws IllegalStateException If the preconditions mentioned are not met. + * + * @hide */ @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) - public boolean setDeviceOwner( + public boolean setDeviceOwner(@NonNull ComponentName who, @Nullable String ownerName, + @UserIdInt int userId) { + if (mService != null) { + try { + return mService.setDeviceOwner(who, ownerName, userId, + /* setProfileOwnerOnCurrentUserIfNecessary= */ true); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Same as {@link #setDeviceOwner(ComponentName, String, int)}, but without setting the profile + * owner on current user when running on headless system user mode - should be used only by + * testing infra. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public boolean setDeviceOwnerOnly( @NonNull ComponentName who, @Nullable String ownerName, @UserIdInt int userId) { if (mService != null) { try { - return mService.setDeviceOwner(who, ownerName, userId); + return mService.setDeviceOwner(who, ownerName, userId, + /* setProfileOwnerOnCurrentUserIfNecessary= */ false); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -9528,7 +9592,14 @@ public class DevicePolicyManager { /** * Called by a profile owner of secondary user that is affiliated with the device to stop the - * calling user and switch back to primary. + * calling user and switch back to primary user. + * + *

Notice that on devices running with + * {@link UserManager#isHeadlessSystemUserMode() headless system user mode}, there is no primary + * user, so it switches back to the user that was in the foreground before the first call to + * {@link #switchUser(ComponentName, UserHandle)} (or fails with + * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN} if that method was not called prior to this + * call). * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return one of the following result codes: @@ -9548,6 +9619,37 @@ public class DevicePolicyManager { } } + /** + * Gets the user a {@link #logoutUser(ComponentName)} call would switch to, + * or {@link UserHandle#USER_NULL} if the current user is not in a session. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public @UserIdInt int getLogoutUserId() { + try { + return mService.getLogoutUserId(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Clears the user that {@link #logoutUser(ComponentName)} would switch to. + * + *

Typically used by system UI after it logout a session. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public void clearLogoutUser() { + try { + mService.clearLogoutUser(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** * Called by a device owner to list all secondary users on the device. Managed profiles are not * considered as secondary users. @@ -13813,6 +13915,23 @@ public class DevicePolicyManager { } } + /** + * Clears organization ID set by the DPC and resets the precomputed enrollment specific ID. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public void clearOrganizationId() { + if (mService == null) { + return; + } + try { + mService.clearOrganizationIdForUser(myUserId()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** * Creates and provisions a managed profile and sets the * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index c78a5a00f645ea750b0c349423fb5377063c343c..7ad8379762f5e3ce46f087f755511fa09812476e 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -160,7 +160,7 @@ interface IDevicePolicyManager { void reportKeyguardDismissed(int userHandle); void reportKeyguardSecured(int userHandle); - boolean setDeviceOwner(in ComponentName who, String ownerName, int userId); + boolean setDeviceOwner(in ComponentName who, String ownerName, int userId, boolean setProfileOwnerOnCurrentUserIfNecessary); ComponentName getDeviceOwnerComponent(boolean callingUserOnly); boolean hasDeviceOwner(); String getDeviceOwnerName(); @@ -262,6 +262,8 @@ interface IDevicePolicyManager { int startUserInBackground(in ComponentName who, in UserHandle userHandle); int stopUser(in ComponentName who, in UserHandle userHandle); int logoutUser(in ComponentName who); + int getLogoutUserId(); + void clearLogoutUser(); List getSecondaryUsers(in ComponentName who); void resetNewUserDisclaimer(); @@ -383,6 +385,7 @@ interface IDevicePolicyManager { void setOrganizationColor(in ComponentName admin, in int color); void setOrganizationColorForUser(in int color, in int userId); + void clearOrganizationIdForUser(int userHandle); int getOrganizationColor(in ComponentName admin); int getOrganizationColorForUser(int userHandle); diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java index fad6cd311021f6ae267a23b083e4cba0cf98e913..ebc2945fb1a04f2e2162d82e6d69621380d64875 100644 --- a/core/java/android/app/compat/PackageOverride.java +++ b/core/java/android/app/compat/PackageOverride.java @@ -24,6 +24,7 @@ import android.os.Parcel; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * An app compat override applied to a given package and change id pairing. @@ -137,6 +138,22 @@ public final class PackageOverride { return new PackageOverride(in.readLong(), in.readLong(), in.readBoolean()); } + /** @hide */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PackageOverride that = (PackageOverride) o; + return mMinVersionCode == that.mMinVersionCode && mMaxVersionCode == that.mMaxVersionCode + && mEnabled == that.mEnabled; + } + + /** @hide */ + @Override + public int hashCode() { + return Objects.hash(mMinVersionCode, mMaxVersionCode, mEnabled); + } + /** @hide */ @Override public String toString() { diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java index e059f177e34414b98eda681f0ffc5e6cdb885720..27d104b592844c45fa49a3d0be68c836d0964b05 100644 --- a/core/java/android/app/servertransaction/ActivityResultItem.java +++ b/core/java/android/app/servertransaction/ActivityResultItem.java @@ -16,6 +16,8 @@ package android.app.servertransaction; +import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; +import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import android.annotation.NonNull; @@ -23,6 +25,9 @@ import android.annotation.Nullable; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; import android.app.ResultInfo; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -41,11 +46,19 @@ public class ActivityResultItem extends ActivityTransactionItem { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private List mResultInfoList; - /* TODO(b/78294732) + /** + * Correct the lifecycle of activity result after {@link android.os.Build.VERSION_CODES#S} to + * guarantee that an activity gets activity result just before resume. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S) + public static final long CALL_ACTIVITY_RESULT_BEFORE_RESUME = 78294732L; + @Override public int getPostExecutionState() { - return ON_RESUME; - }*/ + return CompatChanges.isChangeEnabled(CALL_ACTIVITY_RESULT_BEFORE_RESUME) + ? ON_RESUME : UNDEFINED; + } @Override public void execute(ClientTransactionHandler client, ActivityClientRecord r, diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java index a539812fa8c6a98e15c32d625d1602faf3d658b9..186f25deab67e7392de6b8a18fa9413592f4c200 100644 --- a/core/java/android/app/servertransaction/ActivityTransactionItem.java +++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java @@ -59,36 +59,37 @@ public abstract class ActivityTransactionItem extends ClientTransactionItem { } /** - * Get the {@link ActivityClientRecord} instance that corresponds to the provided token. + * Gets the {@link ActivityClientRecord} instance that corresponds to the provided token. * @param client Target client handler. * @param token Target activity token. - * @param includeLaunching Indicate to also find the {@link ActivityClientRecord} in launching - * activity list. It should be noted that there is no activity in + * @param includeLaunching Indicate to find the {@link ActivityClientRecord} in launching + * activity list. + *

Note that there is no {@link android.app.Activity} instance in * {@link ActivityClientRecord} from the launching activity list. * @return The {@link ActivityClientRecord} instance that corresponds to the provided token. */ @NonNull ActivityClientRecord getActivityClientRecord( @NonNull ClientTransactionHandler client, IBinder token, boolean includeLaunching) { - ActivityClientRecord r = client.getActivityClient(token); - if (r != null) { - if (client.getActivity(token) == null) { + ActivityClientRecord r = null; + // Check launching Activity first to prevent race condition that activity instance has not + // yet set to ActivityClientRecord. + if (includeLaunching) { + r = client.getLaunchingActivity(token); + } + // Then if we don't want to find launching Activity or the ActivityClientRecord doesn't + // exist in launching Activity list. The ActivityClientRecord should have been initialized + // and put in the Activity list. + if (r == null) { + r = client.getActivityClient(token); + if (r != null && client.getActivity(token) == null) { throw new IllegalArgumentException("Activity must not be null to execute " + "transaction item"); } - return r; - } - // The activity may not be launched yet. Fallback to check launching activity. - if (includeLaunching) { - r = client.getLaunchingActivity(token); } if (r == null) { throw new IllegalArgumentException("Activity client record must not be null to execute " + "transaction item"); } - - // We don't need to check the activity of launching activity client records because they - // have not been launched yet. - return r; } } diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java index 5374984d31d08a36bbdca9e700c863946980b0ce..767fd28b8a2a9a28417c6b6e5c39affe0d0463ea 100644 --- a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java +++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java @@ -16,17 +16,14 @@ package android.app.servertransaction; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.app.ClientTransactionHandler; import android.os.Parcel; +import android.view.SurfaceControl; import android.window.SplashScreenView.SplashScreenViewParcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Transfer a splash screen view to an Activity. * @hide @@ -34,31 +31,13 @@ import java.lang.annotation.RetentionPolicy; public class TransferSplashScreenViewStateItem extends ActivityTransactionItem { private SplashScreenViewParcelable mSplashScreenViewParcelable; - private @TransferRequest int mRequest; - - @IntDef(value = { - ATTACH_TO, - HANDOVER_TO - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TransferRequest {} - // request client to attach the view on it. - public static final int ATTACH_TO = 0; - // tell client that you can handle the splash screen view. - public static final int HANDOVER_TO = 1; + private SurfaceControl mStartingWindowLeash; @Override public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityThread.ActivityClientRecord r, PendingTransactionActions pendingActions) { - switch (mRequest) { - case ATTACH_TO: - client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable); - break; - case HANDOVER_TO: - client.handOverSplashScreenView(r); - break; - } + client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable, mStartingWindowLeash); } @Override @@ -68,26 +47,27 @@ public class TransferSplashScreenViewStateItem extends ActivityTransactionItem { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRequest); dest.writeTypedObject(mSplashScreenViewParcelable, flags); + dest.writeTypedObject(mStartingWindowLeash, flags); } private TransferSplashScreenViewStateItem() {} private TransferSplashScreenViewStateItem(Parcel in) { - mRequest = in.readInt(); mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR); + mStartingWindowLeash = in.readTypedObject(SurfaceControl.CREATOR); } /** Obtain an instance initialized with provided params. */ - public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state, - @Nullable SplashScreenViewParcelable parcelable) { + public static TransferSplashScreenViewStateItem obtain( + @Nullable SplashScreenViewParcelable parcelable, + @Nullable SurfaceControl startingWindowLeash) { TransferSplashScreenViewStateItem instance = ObjectPool.obtain(TransferSplashScreenViewStateItem.class); if (instance == null) { instance = new TransferSplashScreenViewStateItem(); } - instance.mRequest = state; instance.mSplashScreenViewParcelable = parcelable; + instance.mStartingWindowLeash = startingWindowLeash; return instance; } diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 8aa27853b4622b8ec282c020ff120d25455c36ef..8be2b4873c67394e6044e7c1e80433aeaef43e6c 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityOptions; +import android.app.LoadedApk; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -96,7 +97,8 @@ public class AppWidgetHostView extends FrameLayout { AppWidgetProviderInfo mInfo; View mView; int mViewMode = VIEW_MODE_NOINIT; - int mLayoutId = -1; + // If true, we should not try to re-apply the RemoteViews on the next inflation. + boolean mColorMappingChanged = false; private InteractionHandler mInteractionHandler; private boolean mOnLightBackground; private SizeF mCurrentSize = null; @@ -539,7 +541,6 @@ public class AppWidgetHostView extends FrameLayout { return; } content = getDefaultView(); - mLayoutId = -1; mViewMode = VIEW_MODE_DEFAULT; } else { // Select the remote view we are actually going to apply. @@ -552,8 +553,11 @@ public class AppWidgetHostView extends FrameLayout { inflateAsync(rvToApply); return; } - int layoutId = rvToApply.getLayoutId(); - if (rvToApply.canRecycleView(mView)) { + // Prepare a local reference to the remote Context so we're ready to + // inflate any requested LayoutParams. + mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath(); + + if (!mColorMappingChanged && rvToApply.canRecycleView(mView)) { try { rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize, mColorResources); @@ -578,7 +582,6 @@ public class AppWidgetHostView extends FrameLayout { } } - mLayoutId = layoutId; mViewMode = VIEW_MODE_CONTENT; } @@ -586,6 +589,7 @@ public class AppWidgetHostView extends FrameLayout { } private void applyContent(View content, boolean recycled, Exception exception) { + mColorMappingChanged = false; if (content == null) { if (mViewMode == VIEW_MODE_ERROR) { // We've already done this -- nothing to do. @@ -612,7 +616,7 @@ public class AppWidgetHostView extends FrameLayout { private void inflateAsync(@NonNull RemoteViews remoteViews) { // Prepare a local reference to the remote Context so we're ready to // inflate any requested LayoutParams. - mRemoteContext = getRemoteContext(); + mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath(); int layoutId = remoteViews.getLayoutId(); if (mLastExecutionSignal != null) { @@ -621,7 +625,7 @@ public class AppWidgetHostView extends FrameLayout { // If our stale view has been prepared to match active, and the new // layout matches, try recycling it - if (layoutId == mLayoutId && mView != null) { + if (!mColorMappingChanged && remoteViews.canRecycleView(mView)) { try { mLastExecutionSignal = remoteViews.reapplyAsync(mContext, mView, @@ -661,7 +665,6 @@ public class AppWidgetHostView extends FrameLayout { @Override public void onViewApplied(View v) { - AppWidgetHostView.this.mLayoutId = mLayoutId; mViewMode = VIEW_MODE_CONTENT; applyContent(v, mIsReapply, null); @@ -714,8 +717,10 @@ public class AppWidgetHostView extends FrameLayout { * purposes of reading remote resources. * @hide */ - protected Context getRemoteContext() { + protected Context getRemoteContextEnsuringCorrectCachedApkPath() { try { + ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo; + LoadedApk.checkAndUpdateApkPaths(expectedAppInfo); // Return if cloned successfully, otherwise default Context newContext = mContext.createApplicationContext( mInfo.providerInfo.applicationInfo, @@ -727,6 +732,9 @@ public class AppWidgetHostView extends FrameLayout { } catch (NameNotFoundException e) { Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found"); return mContext; + } catch (NullPointerException e) { + Log.e(TAG, "Error trying to create the remote context.", e); + return mContext; } } @@ -758,7 +766,7 @@ public class AppWidgetHostView extends FrameLayout { try { if (mInfo != null) { - Context theirContext = getRemoteContext(); + Context theirContext = getRemoteContextEnsuringCorrectCachedApkPath(); mRemoteContext = theirContext; LayoutInflater inflater = (LayoutInflater) theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -897,7 +905,7 @@ public class AppWidgetHostView extends FrameLayout { } mColorMapping = colorMapping.clone(); mColorResources = RemoteViews.ColorResources.create(mContext, mColorMapping); - mLayoutId = -1; + mColorMappingChanged = true; mViewMode = VIEW_MODE_NOINIT; reapplyLastRemoteViews(); } @@ -927,7 +935,7 @@ public class AppWidgetHostView extends FrameLayout { if (mColorResources != null) { mColorResources = null; mColorMapping = null; - mLayoutId = -1; + mColorMappingChanged = true; mViewMode = VIEW_MODE_NOINIT; reapplyLastRemoteViews(); } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index b99ad5125149e285f91b3f84c9d73342b234ce56..32c491753af47bb69442f8ddd0c5b21772328f70 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -108,7 +108,7 @@ public final class CompanionDeviceManager { } private final ICompanionDeviceManager mService; - private final Context mContext; + private Context mContext; /** @hide */ public CompanionDeviceManager( @@ -541,6 +541,7 @@ public final class CompanionDeviceManager { mCallback = null; mHandler = null; mRequest = null; + mContext = null; } } diff --git a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl index a630873c7f6774a45e88f522f713e2eb1f8c98ae..e1c13f7fc9e146d692118b0d6ca2017e402e876f 100644 --- a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl +++ b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl @@ -29,4 +29,6 @@ oneway interface ICompanionDeviceDiscoveryService { in String callingPackage, in IFindDeviceCallback findCallback, in AndroidFuture serviceCallback); + + void onAssociationCreated(); } diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java index f49362e9300e803da4bc20f43a9ddc4f2a715948..2ecd71bc1f06b9bf76199f0dfc518407eb61e3cd 100644 --- a/core/java/android/content/ClipDescription.java +++ b/core/java/android/content/ClipDescription.java @@ -124,6 +124,16 @@ public class ClipDescription implements Parcelable { */ public static final String EXTRA_ACTIVITY_OPTIONS = "android.intent.extra.ACTIVITY_OPTIONS"; + /** + * An instance id used for logging. + *

+ * Type: {@link com.android.internal.logging.InstanceId} + *

+ * @hide + */ + public static final String EXTRA_LOGGING_INSTANCE_ID = + "android.intent.extra.LOGGING_INSTANCE_ID"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 7cb0934214172e70d54a31529e6878d753370bf9..bde612ebfd085433aa93723415f1f60b2d28de08 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3337,7 +3337,11 @@ public abstract class Context { * Service will call {@link android.app.Service#startForeground(int, android.app.Notification) * startForeground(int, android.app.Notification)} once it begins running. The service is given * an amount of time comparable to the ANR interval to do this, otherwise the system - * will automatically stop the service and declare the app ANR. + * will automatically crash the process, in which case an internal exception + * {@code ForegroundServiceDidNotStartInTimeException} is logged on logcat on devices + * running SDK Version {@link android.os.Build.VERSION_CODES#S} or later. On older Android + * versions, an internal exception {@code RemoteServiceException} is logged instead, with + * a corresponding message. * *

Unlike the ordinary {@link #startService(Intent)}, this method can be used * at any time, regardless of whether the app hosting the service is in a foreground diff --git a/core/java/android/content/integrity/OWNERS b/core/java/android/content/integrity/OWNERS index a1fe59c862d2d4a7ba84f6ad9fcf4bbe609a3ffe..20c758aedd674c1df2f808069995437a5362d41c 100644 --- a/core/java/android/content/integrity/OWNERS +++ b/core/java/android/content/integrity/OWNERS @@ -1,3 +1,5 @@ # Bug component: 722021 +toddke@android.com +toddke@google.com patb@google.com diff --git a/core/java/android/content/om/OWNERS b/core/java/android/content/om/OWNERS index 9c473360a46ab4271265bca4984115900f980301..3669817e98443229d007b58f86a9a1d762a4417b 100644 --- a/core/java/android/content/om/OWNERS +++ b/core/java/android/content/om/OWNERS @@ -1,4 +1,6 @@ # Bug component: 568631 +toddke@android.com +toddke@google.com patb@google.com zyy@google.com diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 95c5612aeee4716befa16408e138ec4e4f432a83..d1ef5917ba47f47f4c634dc1ce5c3c4e7090a65b 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -22,6 +22,7 @@ import android.app.Activity; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledSince; import android.compat.annotation.Overridable; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -994,9 +995,9 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM * OVERRIDE_MIN_ASPECT_RATIO_LARGE * - * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's - * manifest will be overridden to the largest enabled aspect ratio treatment unless the app's - * manifest value is higher. + * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's manifest + * will be overridden to the largest enabled aspect ratio treatment unless the app's manifest + * value is higher. * @hide */ @ChangeId @@ -1005,6 +1006,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @TestApi public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id + /** + * This change id restricts treatments that force a given min aspect ratio to activities + * whose orientation is fixed to portrait. + * + * This treatment is enabled by default and only takes effect if OVERRIDE_MIN_ASPECT_RATIO is + * also enabled. + * @hide + */ + @ChangeId + @Overridable + @TestApi + public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id + /** * This change id sets the activity's min aspect ratio to a medium value as defined by * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE. @@ -1039,6 +1053,14 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @TestApi public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 16 / 9f; + /** + * Compares activity window layout min width/height with require space for multi window to + * determine if it can be put into multi window mode. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) + private static final long CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW = 197654537L; + /** * Convert Java change bits to native. * @@ -1232,8 +1254,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * Returns true if the activity has maximum or minimum aspect ratio. * @hide */ - public boolean hasFixedAspectRatio() { - return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; + public boolean hasFixedAspectRatio(@ScreenOrientation int orientation) { + return getMaxAspectRatio() != 0 || getMinAspectRatio(orientation) != 0; } /** @@ -1326,9 +1348,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { */ @SizeChangesSupportMode public int supportsSizeChanges() { - if (CompatChanges.isChangeEnabled(FORCE_NON_RESIZE_APP, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(FORCE_NON_RESIZE_APP)) { return SIZE_CHANGES_UNSUPPORTED_OVERRIDE; } @@ -1336,9 +1356,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { return SIZE_CHANGES_SUPPORTED_METADATA; } - if (CompatChanges.isChangeEnabled(FORCE_RESIZE_APP, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(FORCE_RESIZE_APP)) { return SIZE_CHANGES_SUPPORTED_OVERRIDE; } @@ -1349,22 +1367,18 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * Returns if the activity should never be sandboxed to the activity window bounds. * @hide */ - public boolean neverSandboxDisplayApis() { - return CompatChanges.isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)) - || ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo); + public boolean neverSandboxDisplayApis(ConstrainDisplayApisConfig constrainDisplayApisConfig) { + return isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS) + || constrainDisplayApisConfig.getNeverConstrainDisplayApis(applicationInfo); } /** * Returns if the activity should always be sandboxed to the activity window bounds. * @hide */ - public boolean alwaysSandboxDisplayApis() { - return CompatChanges.isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)) - || ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(applicationInfo); + public boolean alwaysSandboxDisplayApis(ConstrainDisplayApisConfig constrainDisplayApisConfig) { + return isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS) + || constrainDisplayApisConfig.getAlwaysConstrainDisplayApis(applicationInfo); } /** @hide */ @@ -1392,28 +1406,29 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * {@code getManifestMinAspectRatio}. * @hide */ - public float getMinAspectRatio() { - if (applicationInfo == null || !CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + public float getMinAspectRatio(@ScreenOrientation int orientation) { + if (applicationInfo == null || !isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || ( + isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY) + && !isFixedOrientationPortrait(orientation))) { return mMinAspectRatio; } - if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) { return Math.max(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, mMinAspectRatio); } - if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) { return Math.max(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, mMinAspectRatio); } return mMinAspectRatio; } + private boolean isChangeEnabled(long changeId) { + return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName, + UserHandle.getUserHandleForUid(applicationInfo.uid)); + } + /** @hide */ public float getManifestMinAspectRatio() { return mMinAspectRatio; @@ -1475,6 +1490,15 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { } } + /** + * Whether we should compare activity window layout min width/height with require space for + * multi window to determine if it can be put into multi window mode. + * @hide + */ + public boolean shouldCheckMinWidthHeightForMultiWindow() { + return isChangeEnabled(CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW); + } + public void dump(Printer pw, String prefix) { dump(pw, prefix, DUMP_FLAG_ALL); } @@ -1521,9 +1545,10 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { if (getMaxAspectRatio() != 0) { pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio()); } - if (getMinAspectRatio() != 0) { - pw.println(prefix + "minAspectRatio=" + getMinAspectRatio()); - if (getManifestMinAspectRatio() != getMinAspectRatio()) { + final float minAspectRatio = getMinAspectRatio(screenOrientation); + if (minAspectRatio != 0) { + pw.println(prefix + "minAspectRatio=" + minAspectRatio); + if (getManifestMinAspectRatio() != minAspectRatio) { pw.println(prefix + "getManifestMinAspectRatio=" + getManifestMinAspectRatio()); } } diff --git a/core/java/android/content/pm/ConstrainDisplayApisConfig.java b/core/java/android/content/pm/ConstrainDisplayApisConfig.java index 11ba3d4ba9a2988d4b4e655898740e10a91a3a3e..98b73aa8860a3e09871583ee96f781ac1756be80 100644 --- a/core/java/android/content/pm/ConstrainDisplayApisConfig.java +++ b/core/java/android/content/pm/ConstrainDisplayApisConfig.java @@ -19,10 +19,15 @@ package android.content.pm; import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; import android.provider.DeviceConfig; +import android.util.ArrayMap; +import android.util.Pair; import android.util.Slog; +import com.android.internal.os.BackgroundThread; + import java.util.Arrays; import java.util.List; +import java.util.Map; /** * Class for processing flags in the Device Config namespace 'constrain_display_apis'. @@ -54,6 +59,33 @@ public final class ConstrainDisplayApisConfig { private static final String FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS = "always_constrain_display_apis"; + /** + * Indicates that display APIs should never be constrained to the activity window bounds for all + * packages. + */ + private boolean mNeverConstrainDisplayApisAllPackages; + + /** + * Indicates that display APIs should never be constrained to the activity window bounds for + * a set of defined packages. Map keys are package names, and entries are a + * 'Pair(, )'. + */ + private ArrayMap> mNeverConstrainConfigMap; + + /** + * Indicates that display APIs should always be constrained to the activity window bounds for + * a set of defined packages. Map keys are package names, and entries are a + * 'Pair(, )'. + */ + private ArrayMap> mAlwaysConstrainConfigMap; + + public ConstrainDisplayApisConfig() { + updateCache(); + + DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CONSTRAIN_DISPLAY_APIS, + BackgroundThread.getExecutor(), properties -> updateCache()); + } + /** * Returns true if either the flag 'never_constrain_display_apis_all_packages' is true or the * flag 'never_constrain_display_apis' contains a package entry that matches the given {@code @@ -61,13 +93,12 @@ public final class ConstrainDisplayApisConfig { * * @param applicationInfo Information about the application/package. */ - public static boolean neverConstrainDisplayApis(ApplicationInfo applicationInfo) { - if (DeviceConfig.getBoolean(NAMESPACE_CONSTRAIN_DISPLAY_APIS, - FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false)) { + public boolean getNeverConstrainDisplayApis(ApplicationInfo applicationInfo) { + if (mNeverConstrainDisplayApisAllPackages) { return true; } - return flagHasMatchingPackageEntry(FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, applicationInfo); + return flagHasMatchingPackageEntry(mNeverConstrainConfigMap, applicationInfo); } /** @@ -76,73 +107,106 @@ public final class ConstrainDisplayApisConfig { * * @param applicationInfo Information about the application/package. */ - public static boolean alwaysConstrainDisplayApis(ApplicationInfo applicationInfo) { - return flagHasMatchingPackageEntry(FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, applicationInfo); + public boolean getAlwaysConstrainDisplayApis(ApplicationInfo applicationInfo) { + return flagHasMatchingPackageEntry(mAlwaysConstrainConfigMap, applicationInfo); } + /** - * Returns true if the flag with the given {@code flagName} contains a package entry that - * matches the given {@code applicationInfo}. - * - * @param applicationInfo Information about the application/package. + * Updates {@link #mNeverConstrainDisplayApisAllPackages}, {@link #mNeverConstrainConfigMap}, + * and {@link #mAlwaysConstrainConfigMap} from the {@link DeviceConfig}. */ - private static boolean flagHasMatchingPackageEntry(String flagName, - ApplicationInfo applicationInfo) { - String configStr = DeviceConfig.getString(NAMESPACE_CONSTRAIN_DISPLAY_APIS, - flagName, /* defaultValue= */ ""); + private void updateCache() { + mNeverConstrainDisplayApisAllPackages = DeviceConfig.getBoolean( + NAMESPACE_CONSTRAIN_DISPLAY_APIS, + FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false); + + final String neverConstrainConfigStr = DeviceConfig.getString( + NAMESPACE_CONSTRAIN_DISPLAY_APIS, + FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ ""); + mNeverConstrainConfigMap = buildConfigMap(neverConstrainConfigStr); + + final String alwaysConstrainConfigStr = DeviceConfig.getString( + NAMESPACE_CONSTRAIN_DISPLAY_APIS, + FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ ""); + mAlwaysConstrainConfigMap = buildConfigMap(alwaysConstrainConfigStr); + } + /** + * Processes the configuration string into a map of version codes, for the given + * configuration to be applied to the specified packages. If the given package + * entry string is invalid, then the map will not contain an entry for the package. + * + * @param configStr A configuration string expected to be in the format of a list of package + * entries separated by ','. A package entry expected to be in the format + * ':?:?'. + * @return a map of configuration entries, where each key is a package name. Each value is + * a pair of version codes, in the format 'Pair(, )'. + */ + private static ArrayMap> buildConfigMap(String configStr) { + ArrayMap> configMap = new ArrayMap<>(); // String#split returns a non-empty array given an empty string. if (configStr.isEmpty()) { - return false; + return configMap; } - for (String packageEntryString : configStr.split(",")) { - if (matchesApplicationInfo(packageEntryString, applicationInfo)) { - return true; + List packageAndVersions = Arrays.asList(packageEntryString.split(":", 3)); + if (packageAndVersions.size() != 3) { + Slog.w(TAG, "Invalid package entry in flag 'never/always_constrain_display_apis': " + + packageEntryString); + // Skip this entry. + continue; + } + String packageName = packageAndVersions.get(0); + String minVersionCodeStr = packageAndVersions.get(1); + String maxVersionCodeStr = packageAndVersions.get(2); + try { + final long minVersion = + minVersionCodeStr.isEmpty() ? Long.MIN_VALUE : Long.parseLong( + minVersionCodeStr); + final long maxVersion = + maxVersionCodeStr.isEmpty() ? Long.MAX_VALUE : Long.parseLong( + maxVersionCodeStr); + Pair minMaxVersionCodes = new Pair<>(minVersion, maxVersion); + configMap.put(packageName, minMaxVersionCodes); + } catch (NumberFormatException e) { + Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryString); + // Skip this entry. } } - - return false; + return configMap; } /** - * Parses the given {@code packageEntryString} and returns true if {@code - * applicationInfo.packageName} matches the package name in the config and {@code - * applicationInfo.longVersionCode} is within the version range in the config. - * - *

Logs a warning and returns false in case the given {@code packageEntryString} is invalid. + * Returns true if the flag with the given {@code flagName} contains a package entry that + * matches the given {@code applicationInfo}. * - * @param packageEntryStr A package entry expected to be in the format - * ':?:?'. + * @param configMap the map representing the current configuration value to examine * @param applicationInfo Information about the application/package. */ - private static boolean matchesApplicationInfo(String packageEntryStr, + private static boolean flagHasMatchingPackageEntry(Map> configMap, ApplicationInfo applicationInfo) { - List packageAndVersions = Arrays.asList(packageEntryStr.split(":", 3)); - if (packageAndVersions.size() != 3) { - Slog.w(TAG, "Invalid package entry in flag 'never_constrain_display_apis': " - + packageEntryStr); + if (configMap.isEmpty()) { return false; } - String packageName = packageAndVersions.get(0); - String minVersionCodeStr = packageAndVersions.get(1); - String maxVersionCodeStr = packageAndVersions.get(2); - - if (!packageName.equals(applicationInfo.packageName)) { + if (!configMap.containsKey(applicationInfo.packageName)) { return false; } - long version = applicationInfo.longVersionCode; - try { - if (!minVersionCodeStr.isEmpty() && version < Long.parseLong(minVersionCodeStr)) { - return false; - } - if (!maxVersionCodeStr.isEmpty() && version > Long.parseLong(maxVersionCodeStr)) { - return false; - } - } catch (NumberFormatException e) { - Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryStr); - return false; - } - return true; + return matchesApplicationInfo(configMap.get(applicationInfo.packageName), applicationInfo); + } + + /** + * Parses the given {@code minMaxVersionCodes} and returns true if {@code + * applicationInfo.longVersionCode} is within the version range in the pair. + * Returns false otherwise. + * + * @param minMaxVersionCodes A pair expected to be in the format + * 'Pair(, )'. + * @param applicationInfo Information about the application/package. + */ + private static boolean matchesApplicationInfo(Pair minMaxVersionCodes, + ApplicationInfo applicationInfo) { + return applicationInfo.longVersionCode >= minMaxVersionCodes.first + && applicationInfo.longVersionCode <= minMaxVersionCodes.second; } } diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS index 128bfb99ccd4f1ae1be5d8eea66d9805a1f3e691..4e674f6ec9a8ee5da2e105dd5e0311935c210f46 100644 --- a/core/java/android/content/pm/OWNERS +++ b/core/java/android/content/pm/OWNERS @@ -1,9 +1,11 @@ # Bug component: 36137 +toddke@android.com +toddke@google.com patb@google.com per-file PackageParser.java = set noparent -per-file PackageParser.java = chiuwinson@google.com,patb@google.com +per-file PackageParser.java = chiuwinson@google.com,patb@google.com,toddke@google.com per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index dd2080b60b37a7b076eadfc925ee84819d7c31c2..2bac066ed1867a8d0707fe995c6ddfcde3274a64 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -207,7 +207,9 @@ public class PackageItemInfo { return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_STRING_FLAG_TRIM | SAFE_STRING_FLAG_FIRST_LINE); } else { - return loadUnsafeLabel(pm); + // Trims the label string to the MAX_SAFE_LABEL_LENGTH. This is to prevent that the + // system is overwhelmed by an enormous string returned by the application. + return TextUtils.trimToSize(loadUnsafeLabel(pm), MAX_SAFE_LABEL_LENGTH); } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 7cd7e7acab121106335ccdaa29bc54d79f8730f1..511901037a308dc33dac3926b96961b08040df88 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2473,6 +2473,16 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_CTS = "android.software.cts"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * is opted-in to render the application using Automotive App Host + * + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAR_TEMPLATES_HOST = + "android.software.car.templates_host"; + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports @@ -3701,6 +3711,17 @@ public abstract class PackageManager { public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = "android.hardware.keystore.app_attest_key"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * is opted-in to receive per-app compatibility overrides that are applied in + * {@link com.android.server.compat.overrides.AppCompatOverridesService}. + * + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_APP_COMPAT_OVERRIDES = + "android.software.app_compat_overrides"; + /** @hide */ public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true; diff --git a/core/java/android/content/pm/dex/OWNERS b/core/java/android/content/pm/dex/OWNERS index b590f659a5d4bc44e619cfeed42dc82985fcc0b7..267e5d58f9a6899fe860440ee0a2f46d877f29d4 100644 --- a/core/java/android/content/pm/dex/OWNERS +++ b/core/java/android/content/pm/dex/OWNERS @@ -1,5 +1,7 @@ # Bug component: 86431 +toddke@android.com +toddke@google.com patb@google.com calin@google.com ngeoffray@google.com diff --git a/core/java/android/content/pm/parsing/OWNERS b/core/java/android/content/pm/parsing/OWNERS index 445a8330037b175011f93c581735448d97579533..8049d5cb7fa2825c52076f3edcc0d52aa9dbc432 100644 --- a/core/java/android/content/pm/parsing/OWNERS +++ b/core/java/android/content/pm/parsing/OWNERS @@ -2,3 +2,4 @@ chiuwinson@google.com patb@google.com +toddke@google.com diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS index f9c51dd2ae5816713628ac1b1627c5ed12ee464b..cf7e6890876a4c08bb9273655e04fc35870902ed 100644 --- a/core/java/android/content/pm/permission/OWNERS +++ b/core/java/android/content/pm/permission/OWNERS @@ -2,5 +2,7 @@ include platform/frameworks/base:/core/java/android/permission/OWNERS +toddke@android.com +toddke@google.com patb@google.com diff --git a/core/java/android/content/pm/split/OWNERS b/core/java/android/content/pm/split/OWNERS index b8fa1a93ed78fa8e974ec3bbbe6894e8cdbb0321..3d126d297e60575f735281fd286bd7c0c47258ee 100644 --- a/core/java/android/content/pm/split/OWNERS +++ b/core/java/android/content/pm/split/OWNERS @@ -1,3 +1,5 @@ # Bug component: 36137 +toddke@android.com +toddke@google.com patb@google.com diff --git a/core/java/android/content/pm/verify/domain/OWNERS b/core/java/android/content/pm/verify/domain/OWNERS index 445a8330037b175011f93c581735448d97579533..c669112e05124310063102be8b898f323126d3bb 100644 --- a/core/java/android/content/pm/verify/domain/OWNERS +++ b/core/java/android/content/pm/verify/domain/OWNERS @@ -2,3 +2,4 @@ chiuwinson@google.com patb@google.com +toddke@google.com \ No newline at end of file diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS index 7460a14182e5b921ac0be638ebde624feb99038a..d12d920b2a5454c25df85ac2c3a75f59a8978417 100644 --- a/core/java/android/content/res/OWNERS +++ b/core/java/android/content/res/OWNERS @@ -1,4 +1,6 @@ # Bug component: 568761 +toddke@android.com +toddke@google.com patb@google.com zyy@google.com diff --git a/core/java/android/hardware/OWNERS b/core/java/android/hardware/OWNERS index ce5cf674b5be31844e60a0874de4716afcbf6ed7..3b6a564fc353a6ca90655122b0f7d5e5356ec714 100644 --- a/core/java/android/hardware/OWNERS +++ b/core/java/android/hardware/OWNERS @@ -5,7 +5,7 @@ michaelwr@google.com sumir@google.com # Camera -per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,zhijunhe@google.com,jchowdhary@google.com +per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,yinchiayeh@google.com,zhijunhe@google.com,jchowdhary@google.com # Sensor Privacy per-file *SensorPrivacy* = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 43ef33e1f420c44bae3efb12b73a4bf748d9d0c7..28046c56b9f88e40736b5165e3118bd01a0e17b8 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -150,6 +150,12 @@ public interface BiometricConstants { */ int BIOMETRIC_ERROR_RE_ENROLL = 16; + /** + * The privacy setting has been enabled and will block use of the sensor. + * @hide + */ + int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18; + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index fe43c83d17f1a30a268bcc537b13c34edd15a2a4..fd46f243874b44222d3551e296303254cc77c608 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -69,7 +69,7 @@ public interface BiometricFaceConstants { BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, BIOMETRIC_ERROR_RE_ENROLL, - FACE_ERROR_UNKNOWN + FACE_ERROR_UNKNOWN, }) @Retention(RetentionPolicy.SOURCE) @interface FaceError {} diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..065ae64a92ad227a58754253ed403e7159a63b1a --- /dev/null +++ b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.biometrics; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Common constants for biometric overlays. + * @hide + */ +public interface BiometricOverlayConstants { + /** Unknown usage. */ + int REASON_UNKNOWN = 0; + /** User is about to enroll. */ + int REASON_ENROLL_FIND_SENSOR = 1; + /** User is enrolling. */ + int REASON_ENROLL_ENROLLING = 2; + /** Usage from BiometricPrompt. */ + int REASON_AUTH_BP = 3; + /** Usage from Keyguard. */ + int REASON_AUTH_KEYGUARD = 4; + /** Non-specific usage (from FingerprintManager). */ + int REASON_AUTH_OTHER = 5; + /** Usage from Settings. */ + int REASON_AUTH_SETTINGS = 6; + + @IntDef({REASON_UNKNOWN, + REASON_ENROLL_FIND_SENSOR, + REASON_ENROLL_ENROLLING, + REASON_AUTH_BP, + REASON_AUTH_KEYGUARD, + REASON_AUTH_OTHER, + REASON_AUTH_SETTINGS}) + @Retention(RetentionPolicy.SOURCE) + @interface ShowReason {} +} diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 9fb70d6a07f575021274540edc14985ab4201c39..dc65beffa6e580db5a5f72c3eae668d40ddf5a20 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -408,6 +408,19 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan return this; } + /** + * Flag to decide if authentication should ignore enrollment state. + * Defaults to false (not ignoring enrollment state) + * @param ignoreEnrollmentState + * @return This builder. + * @hide + */ + @NonNull + public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) { + mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState); + return this; + } + /** * Creates a {@link BiometricPrompt}. * diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 339c654f4d2f5c86659eacb649f70e5ec9d92f36..e6b762a64384d4c9c0b39ee772c55c8c3c512cb7 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -45,6 +45,7 @@ public class PromptInfo implements Parcelable { private boolean mReceiveSystemEvents; @NonNull private List mAllowedSensorIds = new ArrayList<>(); private boolean mAllowBackgroundAuthentication; + private boolean mIgnoreEnrollmentState; public PromptInfo() { @@ -66,6 +67,7 @@ public class PromptInfo implements Parcelable { mReceiveSystemEvents = in.readBoolean(); mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader()); mAllowBackgroundAuthentication = in.readBoolean(); + mIgnoreEnrollmentState = in.readBoolean(); } public static final Creator CREATOR = new Creator() { @@ -102,6 +104,7 @@ public class PromptInfo implements Parcelable { dest.writeBoolean(mReceiveSystemEvents); dest.writeList(mAllowedSensorIds); dest.writeBoolean(mAllowBackgroundAuthentication); + dest.writeBoolean(mIgnoreEnrollmentState); } public boolean containsTestConfigurations() { @@ -192,6 +195,10 @@ public class PromptInfo implements Parcelable { mAllowBackgroundAuthentication = allow; } + public void setIgnoreEnrollmentState(boolean ignoreEnrollmentState) { + mIgnoreEnrollmentState = ignoreEnrollmentState; + } + // Getters public CharSequence getTitle() { @@ -261,4 +268,8 @@ public class PromptInfo implements Parcelable { public boolean isAllowBackgroundAuthentication() { return mAllowBackgroundAuthentication; } + + public boolean isIgnoreEnrollmentState() { + return mIgnoreEnrollmentState; + } } diff --git a/core/java/android/hardware/biometrics/SensorLocationInternal.aidl b/core/java/android/hardware/biometrics/SensorLocationInternal.aidl new file mode 100644 index 0000000000000000000000000000000000000000..098190449d53e54da3a21d00ef0be66dc90d905d --- /dev/null +++ b/core/java/android/hardware/biometrics/SensorLocationInternal.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.biometrics; + +// @hide +parcelable SensorLocationInternal; diff --git a/core/java/android/hardware/biometrics/SensorLocationInternal.java b/core/java/android/hardware/biometrics/SensorLocationInternal.java new file mode 100644 index 0000000000000000000000000000000000000000..fb25a2fcd8237413eede108a0152f4611b7eb8a8 --- /dev/null +++ b/core/java/android/hardware/biometrics/SensorLocationInternal.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.biometrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The location of a sensor relative to a physical display. + * + * Note that the location may change depending on other attributes of the device, such as + * fold status, which are not yet included in this class. + * @hide + */ +public class SensorLocationInternal implements Parcelable { + + /** Default value to use when the sensor's location is unknown or undefined. */ + public static final SensorLocationInternal DEFAULT = new SensorLocationInternal("", 0, 0, 0); + + /** + * The stable display id. + */ + @NonNull + public final String displayId; + + /** + * The location of the center of the sensor if applicable. For example, sensors of type + * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the + * distance in pixels, measured from the left edge of the screen. + */ + public final int sensorLocationX; + + /** + * The location of the center of the sensor if applicable. For example, sensors of type + * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the + * distance in pixels, measured from the top edge of the screen. + * + */ + public final int sensorLocationY; + + /** + * The radius of the sensor if applicable. For example, sensors of type + * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius + * of the sensor, in pixels. + */ + public final int sensorRadius; + + public SensorLocationInternal(@Nullable String displayId, + int sensorLocationX, int sensorLocationY, int sensorRadius) { + this.displayId = displayId != null ? displayId : ""; + this.sensorLocationX = sensorLocationX; + this.sensorLocationY = sensorLocationY; + this.sensorRadius = sensorRadius; + } + + protected SensorLocationInternal(Parcel in) { + displayId = in.readString16NoHelper(); + sensorLocationX = in.readInt(); + sensorLocationY = in.readInt(); + sensorRadius = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(displayId); + dest.writeInt(sensorLocationX); + dest.writeInt(sensorLocationY); + dest.writeInt(sensorRadius); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = + new Creator() { + @Override + public SensorLocationInternal createFromParcel(Parcel in) { + return new SensorLocationInternal(in); + } + + @Override + public SensorLocationInternal[] newArray(int size) { + return new SensorLocationInternal[size]; + } + }; + + @Override + public String toString() { + return "[id: " + displayId + + ", x: " + sensorLocationX + + ", y: " + sensorLocationY + + ", r: " + sensorRadius + "]"; + } +} diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index e0138c5db1784ff6adb95e5782a2a8cd372703d3..0b02a919b2d95a3a3ec8b4df44c23858cc868735 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -22,15 +22,18 @@ import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; +import android.hardware.camera2.params.DeviceStateSensorOrientationMap; import android.hardware.camera2.params.RecommendedStreamConfigurationMap; import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.utils.TypeReference; import android.os.Build; +import android.util.Log; import android.util.Rational; +import com.android.internal.annotations.GuardedBy; + import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -202,8 +205,25 @@ public final class CameraCharacteristics extends CameraMetadata> mAvailableResultKeys; private ArrayList mRecommendedConfigurations; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private boolean mFoldedDeviceState; + + private final CameraManager.DeviceStateListener mFoldStateListener = + new CameraManager.DeviceStateListener() { + @Override + public final void onDeviceStateChanged(boolean folded) { + synchronized (mLock) { + mFoldedDeviceState = folded; + } + }}; + + private static final String TAG = "CameraCharacteristics"; + /** * Takes ownership of the passed-in properties object + * + * @param properties Camera properties. * @hide */ public CameraCharacteristics(CameraMetadataNative properties) { @@ -219,6 +239,42 @@ public final class CameraCharacteristics extends CameraMetadataCheck whether a given property value needs to be overridden in some specific + * case.

+ * + * @param key The characteristics field to override. + * @return The value of overridden property, or {@code null} if the property doesn't need an + * override. + */ + @Nullable + private T overrideProperty(Key key) { + if (CameraCharacteristics.SENSOR_ORIENTATION.equals(key) && (mFoldStateListener != null) && + (mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS) != null)) { + DeviceStateSensorOrientationMap deviceStateSensorOrientationMap = + mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP); + synchronized (mLock) { + Integer ret = deviceStateSensorOrientationMap.getSensorOrientation( + mFoldedDeviceState ? DeviceStateSensorOrientationMap.FOLDED : + DeviceStateSensorOrientationMap.NORMAL); + if (ret >= 0) { + return (T) ret; + } else { + Log.w(TAG, "No valid device state to orientation mapping! Using default!"); + } + } + } + + return null; + } + /** * Get a camera characteristics field value. * @@ -235,7 +291,8 @@ public final class CameraCharacteristics extends CameraMetadata T get(Key key) { - return mProperties.get(key); + T propertyOverride = overrideProperty(key); + return (propertyOverride != null) ? propertyOverride : mProperties.get(key); } /** @@ -2575,7 +2632,8 @@ public final class CameraCharacteristics extends CameraMetadata * *

For applications targeting SDK version 31 or newer, if the mobile device declares to be - * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS media performance class} S, + * media performance class 12 or higher by setting + * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger, * the primary camera devices (first rear/front camera in the camera ID list) will not * support JPEG sizes smaller than 1080p. If the application configures a JPEG stream * smaller than 1080p, the camera device will round up the JPEG image size to at least @@ -2648,9 +2706,11 @@ public final class CameraCharacteristics extends CameraMetadata * *

For applications targeting SDK version 31 or newer, if the mobile device doesn't declare - * to be media performance class S, or if the camera device isn't a primary rear/front - * camera, the minimum required output stream configurations are the same as for applications - * targeting SDK version older than 31.

+ * to be media performance class 12 or better by setting + * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger, + * or if the camera device isn't a primary rear/front camera, the minimum required output + * stream configurations are the same as for applications targeting SDK version older than + * 31.

*

Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} for additional * mandatory stream configurations on a per-capability basis.

*

Exception on 176x144 (QCIF) resolution: camera devices usually have a fixed capability for @@ -3993,11 +4053,26 @@ public final class CameraCharacteristics extends CameraMetadata *

Also defines the direction of rolling shutter readout, which is from top to bottom in * the sensor's coordinate system.

+ *

Starting with Android API level 32, camera clients that query the orientation via + * {@link android.hardware.camera2.CameraCharacteristics#get } on foldable devices which + * include logical cameras can receive a value that can dynamically change depending on the + * device/fold state. + * Clients are advised to not cache or store the orientation value of such logical sensors. + * In case repeated queries to CameraCharacteristics are not preferred, then clients can + * also access the entire mapping from device state to sensor orientation in + * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }. + * Do note that a dynamically changing sensor orientation value in camera characteristics + * will not be the best way to establish the orientation per frame. Clients that want to + * know the sensor orientation of a particular captured frame should query the + * {@link CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID android.logicalMultiCamera.activePhysicalId} from the corresponding capture result and + * check the respective physical camera orientation.

*

Units: Degrees of clockwise rotation; always a multiple of * 90

*

Range of valid values:
* 0, 90, 180, 270

*

This key is available on all devices.

+ * + * @see CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID */ @PublicKey @NonNull @@ -4306,6 +4381,46 @@ public final class CameraCharacteristics extends CameraMetadata INFO_VERSION = new Key("android.info.version", String.class); + /** + *

This lists the mapping between a device folding state and + * specific camera sensor orientation for logical cameras on a foldable device.

+ *

Logical cameras on foldable devices can support sensors with different orientation + * values. The orientation value may need to change depending on the specific folding + * state. Information about the mapping between the device folding state and the + * sensor orientation can be obtained in + * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }. + * Device state orientation maps are optional and maybe present on devices that support + * {@link CaptureRequest#SCALER_ROTATE_AND_CROP android.scaler.rotateAndCrop}.

+ *

Optional - The value for this key may be {@code null} on some devices.

+ *

Limited capability - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key

+ * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CaptureRequest#SCALER_ROTATE_AND_CROP + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP = + new Key("android.info.deviceStateSensorOrientationMap", android.hardware.camera2.params.DeviceStateSensorOrientationMap.class); + + /** + *

HAL must populate the array with + * (hardware::camera::provider::V2_5::DeviceState, sensorOrientation) pairs for each + * supported device state bitwise combination.

+ *

Units: (device fold state, sensor orientation) x n

+ *

Optional - The value for this key may be {@code null} on some devices.

+ *

Limited capability - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key

+ * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key INFO_DEVICE_STATE_ORIENTATIONS = + new Key("android.info.deviceStateOrientations", long[].class); + /** *

The maximum number of frames that can occur after a request * (different than the previous) has been submitted, and before the diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 5833b3dbef8690b5de9a09e53d43cb1adafdf8f8..b7c5644df107a78d533d79255ca5f1a42f53d142 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -35,10 +35,13 @@ import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.utils.CameraIdAndSessionConfiguration; import android.hardware.camera2.utils.ConcurrentCameraIdCombination; +import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.DeadObjectException; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -50,6 +53,10 @@ import android.util.Log; import android.util.Size; import android.view.Display; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; + +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -97,6 +104,90 @@ public final class CameraManager { synchronized(mLock) { mContext = context; } + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + mFoldStateListener = new FoldStateListener(context); + try { + context.getSystemService(DeviceStateManager.class) + .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener); + } catch (IllegalStateException e) { + Log.v(TAG, "Failed to register device state listener!"); + Log.v(TAG, "Device state dependent characteristics updates will not be functional!"); + mHandlerThread.quitSafely(); + mHandler = null; + mFoldStateListener = null; + } + } + + private HandlerThread mHandlerThread; + private Handler mHandler; + private FoldStateListener mFoldStateListener; + @GuardedBy("mLock") + private ArrayList> mDeviceStateListeners = new ArrayList<>(); + private boolean mFoldedDeviceState; + + /** + * @hide + */ + public interface DeviceStateListener { + void onDeviceStateChanged(boolean folded); + } + + private final class FoldStateListener implements DeviceStateManager.DeviceStateCallback { + private final int[] mFoldedDeviceStates; + + public FoldStateListener(Context context) { + mFoldedDeviceStates = context.getResources().getIntArray( + com.android.internal.R.array.config_foldedDeviceStates); + } + + private void handleStateChange(int state) { + boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state); + synchronized (mLock) { + mFoldedDeviceState = folded; + ArrayList> invalidListeners = new ArrayList<>(); + for (WeakReference listener : mDeviceStateListeners) { + DeviceStateListener callback = listener.get(); + if (callback != null) { + callback.onDeviceStateChanged(folded); + } else { + invalidListeners.add(listener); + } + } + if (!invalidListeners.isEmpty()) { + mDeviceStateListeners.removeAll(invalidListeners); + } + } + } + + @Override + public final void onBaseStateChanged(int state) { + handleStateChange(state); + } + + @Override + public final void onStateChanged(int state) { + handleStateChange(state); + } + } + + /** + * Register a {@link CameraCharacteristics} device state listener + * + * @param chars Camera characteristics that need to receive device state updates + * + * @hide + */ + public void registerDeviceStateListener(@NonNull CameraCharacteristics chars) { + synchronized (mLock) { + DeviceStateListener listener = chars.getDeviceStateListener(); + listener.onDeviceStateChanged(mFoldedDeviceState); + if (mFoldStateListener != null) { + mDeviceStateListeners.add(new WeakReference<>(listener)); + } + } } /** @@ -504,6 +595,7 @@ public final class CameraManager { "Camera service is currently unavailable", e); } } + registerDeviceStateListener(characteristics); return characteristics; } @@ -1327,8 +1419,7 @@ public final class CameraManager { private ICameraService mCameraService; // Singleton, don't allow construction - private CameraManagerGlobal() { - } + private CameraManagerGlobal() { } public static final boolean sCameraServiceDisabled = SystemProperties.getBoolean("config.disable_cameraservice", false); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 88649392c23cd6e30b7245a0b8e95645da8f5af6..9b19fc4d3ef2a777e9eb2391527acb7fb6c90118 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -110,6 +110,11 @@ public class CameraDeviceImpl extends CameraDevice private int mRepeatingRequestId = REQUEST_ID_NONE; // Latest repeating request list's types private int[] mRepeatingRequestTypes; + + // Cache failed requests to process later in case of a repeating error callback + private int mFailedRepeatingRequestId = REQUEST_ID_NONE; + private int[] mFailedRepeatingRequestTypes; + // Map stream IDs to input/output configurations private SimpleEntry mConfiguredInput = new SimpleEntry<>(REQUEST_ID_NONE, null); @@ -1326,16 +1331,25 @@ public class CameraDeviceImpl extends CameraDevice int requestId = mRepeatingRequestId; mRepeatingRequestId = REQUEST_ID_NONE; + mFailedRepeatingRequestId = REQUEST_ID_NONE; int[] requestTypes = mRepeatingRequestTypes; mRepeatingRequestTypes = null; + mFailedRepeatingRequestTypes = null; long lastFrameNumber; try { lastFrameNumber = mRemoteDevice.cancelRequest(requestId); } catch (IllegalArgumentException e) { if (DEBUG) { - Log.v(TAG, "Repeating request was already stopped for request " + requestId); + Log.v(TAG, "Repeating request was already stopped for request " + + requestId); } + // Cache request id and request types in case of a race with + // "onRepeatingRequestError" which may no yet be scheduled on another thread + // or blocked by us. + mFailedRepeatingRequestId = requestId; + mFailedRepeatingRequestTypes = requestTypes; + // Repeating request was already stopped. Nothing more to do. return; } @@ -1965,7 +1979,17 @@ public class CameraDeviceImpl extends CameraDevice synchronized(mInterfaceLock) { // Camera is already closed or no repeating request is present. if (mRemoteDevice == null || mRepeatingRequestId == REQUEST_ID_NONE) { - return; // Camera already closed + if ((mFailedRepeatingRequestId == repeatingRequestId) && + (mFailedRepeatingRequestTypes != null) && (mRemoteDevice != null)) { + Log.v(TAG, "Resuming stop of failed repeating request with id: " + + mFailedRepeatingRequestId); + + checkEarlyTriggerSequenceCompleteLocked(mFailedRepeatingRequestId, + lastFrameNumber, mFailedRepeatingRequestTypes); + mFailedRepeatingRequestId = REQUEST_ID_NONE; + mFailedRepeatingRequestTypes = null; + } + return; } // Redirect device callback to the offline session in case we are in the middle diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 196134b397cbf822effdefa5918f0ea1ccc49aa5..e393a66eb73312e08e365944599bb1fba681e418 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -50,10 +50,10 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration; import android.hardware.camera2.marshal.impl.MarshalQueryableString; import android.hardware.camera2.params.Capability; +import android.hardware.camera2.params.DeviceStateSensorOrientationMap; import android.hardware.camera2.params.Face; import android.hardware.camera2.params.HighSpeedVideoConfiguration; import android.hardware.camera2.params.LensShadingMap; -import android.hardware.camera2.params.MeteringRectangle; import android.hardware.camera2.params.MandatoryStreamCombination; import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap; import android.hardware.camera2.params.OisSample; @@ -754,13 +754,22 @@ public class CameraMetadataNative implements Parcelable { }); sGetCommandMap.put( CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP.getNativeKey(), - new GetCommand() { + new GetCommand() { @Override @SuppressWarnings("unchecked") public T getValue(CameraMetadataNative metadata, Key key) { return (T) metadata.getLensShadingMap(); } }); + sGetCommandMap.put( + CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public T getValue(CameraMetadataNative metadata, Key key) { + return (T) metadata.getDeviceStateOrientationMap(); + } + }); sGetCommandMap.put( CaptureResult.STATISTICS_OIS_SAMPLES.getNativeKey(), new GetCommand() { @@ -994,6 +1003,18 @@ public class CameraMetadataNative implements Parcelable { return map; } + private DeviceStateSensorOrientationMap getDeviceStateOrientationMap() { + long[] mapArray = getBase(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS); + + // Do not warn if map is null while s is not. This is valid. + if (mapArray == null) { + return null; + } + + DeviceStateSensorOrientationMap map = new DeviceStateSensorOrientationMap(mapArray); + return map; + } + private Location getGpsLocation() { String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD); double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES); diff --git a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java new file mode 100644 index 0000000000000000000000000000000000000000..200409e9e000434da4c8e1eb2575bd27f030b19d --- /dev/null +++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.params; + +import android.annotation.LongDef; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.utils.HashCodeHelpers; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Objects; + +/** + * Immutable class that maps the device fold state to sensor orientation. + * + *

Some {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical} + * cameras on foldables can include physical sensors with different sensor orientation + * values. As a result, the values of the logical camera device can potentially change depending + * on the device fold state.

+ * + *

The device fold state to sensor orientation map will contain information about the + * respective logical camera sensor orientation given a device state. Clients + * can query the mapping for all possible supported folded states. + * + * @see CameraCharacteristics#SENSOR_ORIENTATION + */ +public final class DeviceStateSensorOrientationMap { + /** + * Needs to be kept in sync with the HIDL/AIDL DeviceState + */ + + /** + * The device is in its normal physical configuration. This is the default if the + * device does not support multiple different states. + */ + public static final long NORMAL = 0; + + /** + * The device is folded. If not set, the device is unfolded or does not + * support folding. + * + * The exact point when this status change happens during the folding + * operation is device-specific. + */ + public static final long FOLDED = 1 << 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @LongDef(prefix = {"DEVICE_STATE"}, value = + {NORMAL, + FOLDED }) + public @interface DeviceState {}; + + private final HashMap mDeviceStateOrientationMap = new HashMap<>(); + + /** + * Create a new immutable DeviceStateOrientationMap instance. + * + *

This constructor takes over the array; do not write to the array afterwards.

+ * + * @param elements + * An array of elements describing the map + * + * @throws IllegalArgumentException + * if the {@code elements} array length is invalid, not divisible by 2 or contains + * invalid element values + * @throws NullPointerException + * if {@code elements} is {@code null} + * + * @hide + */ + public DeviceStateSensorOrientationMap(final long[] elements) { + mElements = Objects.requireNonNull(elements, "elements must not be null"); + if ((elements.length % 2) != 0) { + throw new IllegalArgumentException("Device state sensor orientation map length " + + elements.length + " is not even!"); + } + + for (int i = 0; i < elements.length; i += 2) { + if ((elements[i+1] % 90) != 0) { + throw new IllegalArgumentException("Sensor orientation not divisible by 90: " + + elements[i+1]); + } + + mDeviceStateOrientationMap.put(elements[i], Math.toIntExact(elements[i + 1])); + } + } + + /** + * Return the logical camera sensor orientation given a specific device fold state. + * + * @param deviceState Device fold state + * + * @return Valid {@link android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION} for + * any supported device fold state + * + * @throws IllegalArgumentException if the given device state is invalid + */ + public int getSensorOrientation(@DeviceState long deviceState) { + if (!mDeviceStateOrientationMap.containsKey(deviceState)) { + throw new IllegalArgumentException("Invalid device state: " + deviceState); + } + + return mDeviceStateOrientationMap.get(deviceState); + } + + /** + * Check if this DeviceStateSensorOrientationMap is equal to another + * DeviceStateSensorOrientationMap. + * + *

Two device state orientation maps are equal if and only if all of their elements are + * {@link Object#equals equal}.

+ * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof DeviceStateSensorOrientationMap) { + final DeviceStateSensorOrientationMap other = (DeviceStateSensorOrientationMap) obj; + return Arrays.equals(mElements, other.mElements); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCodeGeneric(mElements); + } + + private final long[] mElements; +} diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index 52dad3efefb890c6e1cb108a5aa7cba52a0bb25a..b06d076fe08eb2bc497e31bee92503f0c8fd1c3b 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -75,27 +75,28 @@ public final class DeviceStateManager { /** * Submits a {@link DeviceStateRequest request} to modify the device state. *

- * By default, the request is kept active until a call to - * {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs: + * By default, the request is kept active until one of the following occurs: *

    + *
  • The system deems the request can no longer be honored, for example if the requested + * state becomes unsupported. + *
  • A call to {@link #cancelRequest(DeviceStateRequest)}. *
  • Another processes submits a request succeeding this request in which case the request * will be suspended until the interrupting request is canceled. - *
  • The requested state has become unsupported. - *
  • The process submitting the request dies. *
* However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}. * * @throws IllegalArgumentException if the requested state is unsupported. - * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} - * permission is not held. + * @throws SecurityException if the caller is neither the current top-focused activity nor if + * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. * * @see DeviceStateRequest */ - @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, + conditional = true) public void requestState(@NonNull DeviceStateRequest request, @Nullable @CallbackExecutor Executor executor, @Nullable DeviceStateRequest.Callback callback) { - mGlobal.requestState(request, callback, executor); + mGlobal.requestState(request, executor, callback); } /** @@ -105,10 +106,11 @@ public final class DeviceStateManager { * This method is noop if the {@code request} has not been submitted with a call to * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. * - * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} - * permission is not held. + * @throws SecurityException if the caller is neither the current top-focused activity nor if + * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. */ - @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, + conditional = true) public void cancelRequest(@NonNull DeviceStateRequest request) { mGlobal.cancelRequest(request); } diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index 904a54b00fa33e674e22849c4f28ee14e374e42e..85e70b0fb3e9505b1921e5b8c4a215e99d6d6249 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -117,7 +117,7 @@ public final class DeviceStateManagerGlobal { * @see DeviceStateRequest */ public void requestState(@NonNull DeviceStateRequest request, - @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) { + @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) { if (callback == null && executor != null) { throw new IllegalArgumentException("Callback must be supplied with executor."); } else if (executor == null && callback != null) { @@ -149,7 +149,7 @@ public final class DeviceStateManagerGlobal { /** * Cancels a {@link DeviceStateRequest request} previously submitted with a call to - * {@link #requestState(DeviceStateRequest, DeviceStateRequest.Callback, Executor)}. + * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. * * @see DeviceStateManager#cancelRequest(DeviceStateRequest) */ @@ -408,7 +408,7 @@ public final class DeviceStateManagerGlobal { return; } - mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest)); + mExecutor.execute(() -> mCallback.onRequestCanceled(mRequest)); } } } diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java index a1f7aa12264b9c4e407e133ddd7e6e7a909fafae..0b1ed65ef937a501c37283de023475f60d959f22 100644 --- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java +++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java @@ -24,6 +24,7 @@ import android.provider.Settings; import android.text.TextUtils; import com.android.internal.R; +import com.android.internal.util.ArrayUtils; /** * AmbientDisplayConfiguration encapsulates reading access to the configuration of ambient display. @@ -32,7 +33,7 @@ import com.android.internal.R; */ @TestApi public class AmbientDisplayConfiguration { - + private static final String TAG = "AmbientDisplayConfig"; private final Context mContext; private final boolean mAlwaysOnByDefault; @@ -87,7 +88,12 @@ public class AmbientDisplayConfiguration { /** {@hide} */ public boolean tapSensorAvailable() { - return !TextUtils.isEmpty(tapSensorType()); + for (String tapType : tapSensorTypeMapping()) { + if (!TextUtils.isEmpty(tapType)) { + return true; + } + } + return false; } /** {@hide} */ @@ -103,7 +109,10 @@ public class AmbientDisplayConfiguration { /** {@hide} */ public boolean quickPickupSensorEnabled(int user) { - return !TextUtils.isEmpty(quickPickupSensorType()) && !alwaysOnEnabled(user); + return boolSettingDefaultOn(Settings.Secure.DOZE_QUICK_PICKUP_GESTURE, user) + && !TextUtils.isEmpty(quickPickupSensorType()) + && pickupGestureEnabled(user) + && !alwaysOnEnabled(user); } /** {@hide} */ @@ -140,9 +149,18 @@ public class AmbientDisplayConfiguration { return mContext.getResources().getString(R.string.config_dozeDoubleTapSensorType); } - /** {@hide} */ - public String tapSensorType() { - return mContext.getResources().getString(R.string.config_dozeTapSensorType); + /** {@hide} + * May support multiple postures. + */ + public String[] tapSensorTypeMapping() { + String[] postureMapping = + mContext.getResources().getStringArray(R.array.config_dozeTapSensorPostureMapping); + if (ArrayUtils.isEmpty(postureMapping)) { + return new String[] { + mContext.getResources().getString(R.string.config_dozeTapSensorType) + }; + } + return postureMapping; } /** {@hide} */ diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index fc8337ac31552e174ebab407ffa179aef61535d2..d8f20391098c71f2982ea244eb3b477819f05450 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -864,7 +864,8 @@ public final class DisplayManager { if (surface != null) { builder.setSurface(surface); } - return createVirtualDisplay(null /* projection */, builder.build(), callback, handler); + return createVirtualDisplay(null /* projection */, builder.build(), callback, handler, + null /* windowContext */); } // TODO : Remove this hidden API after remove all callers. (Refer to MultiDisplayService) @@ -882,15 +883,17 @@ public final class DisplayManager { if (surface != null) { builder.setSurface(surface); } - return createVirtualDisplay(projection, builder.build(), callback, handler); + return createVirtualDisplay(projection, builder.build(), callback, handler, + null /* windowContext */); } /** @hide */ public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection, @NonNull VirtualDisplayConfig virtualDisplayConfig, - @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { + @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler, + @Nullable Context windowContext) { return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback, - handler); + handler, windowContext); } /** @@ -939,6 +942,34 @@ public final class DisplayManager { setBrightnessConfigurationForUser(c, mContext.getUserId(), mContext.getPackageName()); } + /** + * Sets the brightness configuration for the specified display. + * If the specified display doesn't exist, then this will return and do nothing. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) + public void setBrightnessConfigurationForDisplay(@NonNull BrightnessConfiguration c, + @NonNull String uniqueId) { + mGlobal.setBrightnessConfigurationForDisplay(c, uniqueId, mContext.getUserId(), + mContext.getPackageName()); + } + + /** + * Gets the brightness configuration for the specified display and default user. + * Returns the default configuration if unset or display is invalid. + * + * @hide + */ + @Nullable + @SystemApi + @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) + public BrightnessConfiguration getBrightnessConfigurationForDisplay( + @NonNull String uniqueId) { + return mGlobal.getBrightnessConfigurationForDisplay(uniqueId, mContext.getUserId()); + } + /** * Sets the global display brightness configuration for a specific user. * diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 07babb1a33b81d4bb6200ba4630ac9d3ebefc066..a18f1fbaec8144d67ada509607b5f14eb1775b47 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -583,7 +583,7 @@ public final class DisplayManagerGlobal { public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection, @NonNull VirtualDisplayConfig virtualDisplayConfig, VirtualDisplay.Callback callback, - Handler handler) { + Handler handler, @Nullable Context windowContext) { VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler); IMediaProjection projectionToken = projection != null ? projection.getProjection() : null; int displayId; @@ -609,7 +609,7 @@ public final class DisplayManagerGlobal { return null; } return new VirtualDisplay(this, display, callbackWrapper, - virtualDisplayConfig.getSurface()); + virtualDisplayConfig.getSurface(), windowContext); } public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) { @@ -709,6 +709,34 @@ public final class DisplayManagerGlobal { } } + /** + * Sets the brightness configuration for a given display. + * + * @hide + */ + public void setBrightnessConfigurationForDisplay(BrightnessConfiguration c, + String uniqueDisplayId, int userId, String packageName) { + try { + mDm.setBrightnessConfigurationForDisplay(c, uniqueDisplayId, userId, packageName); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Gets the brightness configuration for a given display or null if one hasn't been set. + * + * @hide + */ + public BrightnessConfiguration getBrightnessConfigurationForDisplay(String uniqueDisplayId, + int userId) { + try { + return mDm.getBrightnessConfigurationForDisplay(uniqueDisplayId, userId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + /** * Gets the global brightness configuration for a given user or null if one hasn't been set. * diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index abcc33c43c743cdccfade867a6f6561191ac60e0..4f205530ef0d7459224f7f7853dcdbe9d50e3060 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.graphics.Point; import android.hardware.SensorManager; import android.os.Handler; +import android.os.IBinder; import android.os.PowerManager; import android.util.IntArray; import android.util.Slog; @@ -339,6 +340,28 @@ public abstract class DisplayManagerInternal { */ public abstract List getRefreshRateLimitations(int displayId); + /** + * Returns the window token of the level of the WindowManager hierarchy to mirror. Returns null + * if layer mirroring by SurfaceFlinger should not be performed for the given displayId. + * For now, only used for mirroring started from MediaProjection. + */ + public abstract IBinder getWindowTokenClientToMirror(int displayId); + + /** + * For the given displayId, updates the window token of the level of the WindowManager hierarchy + * to mirror. If windowToken is null, then SurfaceFlinger performs no layer mirroring to the + * given display. + * For now, only used for mirroring started from MediaProjection. + */ + public abstract void setWindowTokenClientToMirror(int displayId, IBinder windowToken); + + /** + * Returns the default size of the surface associated with the display, or null if the surface + * is not provided for layer mirroring by SurfaceFlinger. + * For now, only used for mirroring started from MediaProjection. + */ + public abstract Point getDisplaySurfaceDefaultSize(int displayId); + /** * Describes the requested power state of the display. * diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 2303353ad101e945948b1d4443398a8e8c2acd3c..116214625e2686fd5a2930c07c82f230b5f88c37 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -118,6 +118,16 @@ interface IDisplayManager { void setBrightnessConfigurationForUser(in BrightnessConfiguration c, int userId, String packageName); + // Sets the global brightness configuration for a given display. Requires + // CONFIGURE_DISPLAY_BRIGHTNESS. + void setBrightnessConfigurationForDisplay(in BrightnessConfiguration c, String uniqueDisplayId, + int userId, String packageName); + + // Gets the brightness configuration for a given display. Requires + // CONFIGURE_DISPLAY_BRIGHTNESS. + BrightnessConfiguration getBrightnessConfigurationForDisplay(String uniqueDisplayId, + int userId); + // Gets the global brightness configuration for a given user. Requires // CONFIGURE_DISPLAY_BRIGHTNESS, and INTERACT_ACROSS_USER if the user is not // the same as the calling user. diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java index bf62c95cf6db5d93ea8f20e0041d8a3eb0ddfd89..71cbd20e800538759467b7cd2465598b8129ee2f 100644 --- a/core/java/android/hardware/display/VirtualDisplay.java +++ b/core/java/android/hardware/display/VirtualDisplay.java @@ -15,6 +15,8 @@ */ package android.hardware.display; +import android.annotation.Nullable; +import android.content.Context; import android.view.Display; import android.view.Surface; @@ -36,13 +38,19 @@ public final class VirtualDisplay { private final Display mDisplay; private IVirtualDisplayCallback mToken; private Surface mSurface; + /** + * Store the WindowContext in a field. If it is a local variable, and it is garbage collected + * during a MediaProjection session, the WindowContainer listener no longer exists. + */ + @Nullable private final Context mWindowContext; VirtualDisplay(DisplayManagerGlobal global, Display display, - IVirtualDisplayCallback token, Surface surface) { + IVirtualDisplayCallback token, Surface surface, Context windowContext) { mGlobal = global; mDisplay = display; mToken = token; mSurface = surface; + mWindowContext = windowContext; } /** diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java index 71688c7cc7e85e412aa856cda6dae4dc464082d0..0e86f43207aae5e037b8f6bea9067133394df9a7 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.media.projection.MediaProjection; import android.os.Handler; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.Surface; @@ -91,9 +92,16 @@ public final class VirtualDisplayConfig implements Parcelable { */ private int mDisplayIdToMirror = DEFAULT_DISPLAY; + /** + * The window token of the level of the WindowManager hierarchy to mirror, or null if mirroring + * should not be performed. + */ + @Nullable + private IBinder mWindowTokenClientToMirror = null; - // Code below generated by codegen v1.0.20. + + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -115,7 +123,8 @@ public final class VirtualDisplayConfig implements Parcelable { int flags, @Nullable Surface surface, @Nullable String uniqueId, - int displayIdToMirror) { + int displayIdToMirror, + @Nullable IBinder windowTokenClientToMirror) { this.mName = name; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mName); @@ -135,6 +144,7 @@ public final class VirtualDisplayConfig implements Parcelable { this.mSurface = surface; this.mUniqueId = uniqueId; this.mDisplayIdToMirror = displayIdToMirror; + this.mWindowTokenClientToMirror = windowTokenClientToMirror; // onConstructed(); // You can define this method to get a callback } @@ -212,6 +222,15 @@ public final class VirtualDisplayConfig implements Parcelable { return mDisplayIdToMirror; } + /** + * The window token of the level of the WindowManager hierarchy to mirror, or null if mirroring + * should not be performed. + */ + @DataClass.Generated.Member + public @Nullable IBinder getWindowTokenClientToMirror() { + return mWindowTokenClientToMirror; + } + @Override @DataClass.Generated.Member public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -221,6 +240,7 @@ public final class VirtualDisplayConfig implements Parcelable { int flg = 0; if (mSurface != null) flg |= 0x20; if (mUniqueId != null) flg |= 0x40; + if (mWindowTokenClientToMirror != null) flg |= 0x100; dest.writeInt(flg); dest.writeString(mName); dest.writeInt(mWidth); @@ -230,6 +250,7 @@ public final class VirtualDisplayConfig implements Parcelable { if (mSurface != null) dest.writeTypedObject(mSurface, flags); if (mUniqueId != null) dest.writeString(mUniqueId); dest.writeInt(mDisplayIdToMirror); + if (mWindowTokenClientToMirror != null) dest.writeStrongBinder(mWindowTokenClientToMirror); } @Override @@ -252,6 +273,7 @@ public final class VirtualDisplayConfig implements Parcelable { Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR); String uniqueId = (flg & 0x40) == 0 ? null : in.readString(); int displayIdToMirror = in.readInt(); + IBinder windowTokenClientToMirror = (flg & 0x100) == 0 ? null : (IBinder) in.readStrongBinder(); this.mName = name; com.android.internal.util.AnnotationValidations.validate( @@ -272,6 +294,7 @@ public final class VirtualDisplayConfig implements Parcelable { this.mSurface = surface; this.mUniqueId = uniqueId; this.mDisplayIdToMirror = displayIdToMirror; + this.mWindowTokenClientToMirror = windowTokenClientToMirror; // onConstructed(); // You can define this method to get a callback } @@ -305,6 +328,7 @@ public final class VirtualDisplayConfig implements Parcelable { private @Nullable Surface mSurface; private @Nullable String mUniqueId; private int mDisplayIdToMirror; + private @Nullable IBinder mWindowTokenClientToMirror; private long mBuilderFieldsSet = 0L; @@ -439,10 +463,22 @@ public final class VirtualDisplayConfig implements Parcelable { return this; } + /** + * The window token of the level of the WindowManager hierarchy to mirror, or null if mirroring + * should not be performed. + */ + @DataClass.Generated.Member + public @NonNull Builder setWindowTokenClientToMirror(@NonNull IBinder value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x100; + mWindowTokenClientToMirror = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull VirtualDisplayConfig build() { checkNotUsed(); - mBuilderFieldsSet |= 0x100; // Mark builder used + mBuilderFieldsSet |= 0x200; // Mark builder used if ((mBuilderFieldsSet & 0x10) == 0) { mFlags = 0; @@ -456,6 +492,9 @@ public final class VirtualDisplayConfig implements Parcelable { if ((mBuilderFieldsSet & 0x80) == 0) { mDisplayIdToMirror = DEFAULT_DISPLAY; } + if ((mBuilderFieldsSet & 0x100) == 0) { + mWindowTokenClientToMirror = null; + } VirtualDisplayConfig o = new VirtualDisplayConfig( mName, mWidth, @@ -464,12 +503,13 @@ public final class VirtualDisplayConfig implements Parcelable { mFlags, mSurface, mUniqueId, - mDisplayIdToMirror); + mDisplayIdToMirror, + mWindowTokenClientToMirror); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x100) != 0) { + if ((mBuilderFieldsSet & 0x200) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -477,10 +517,10 @@ public final class VirtualDisplayConfig implements Parcelable { } @DataClass.Generated( - time = 1604456298440L, - codegenVersion = "1.0.20", + time = 1620657851981L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java", - inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)") + inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate @android.annotation.Nullable android.os.IBinder mWindowTokenClientToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 480923e2b01daf9cceecfc22e271f75b3edc24c6..3cee6100e4f85d2c282f99458c5da4e2b3bde9b6 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -543,7 +543,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) { - authenticate(crypto, cancel, callback, handler, mContext.getUserId()); + authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, mContext.getUserId(), flags); } /** @@ -553,7 +553,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback, Handler handler, int userId) { - authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId); + authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId, 0 /* flags */); } /** @@ -562,7 +562,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, - @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId) { + @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId, + int flags) { FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED, AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE, @@ -578,6 +579,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return; } + final boolean ignoreEnrollmentState = flags == 0 ? false : true; + if (mService != null) { try { useHandler(handler); @@ -585,7 +588,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing mCryptoObject = crypto; final long operationId = crypto != null ? crypto.getOpId() : 0; final long authId = mService.authenticate(mToken, operationId, sensorId, userId, - mServiceReceiver, mContext.getOpPackageName()); + mServiceReceiver, mContext.getOpPackageName(), ignoreEnrollmentState); if (cancel != null) { cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId)); } diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java index 45c6b290be11ceaffb3d73a9b2a5a96151ab99c5..4bf9a740f971ea7362e74939f9c1272d18579b15 100644 --- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java +++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java @@ -21,7 +21,9 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC; import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.biometrics.ComponentInfoInternal; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.SensorProperties; import android.hardware.biometrics.SensorPropertiesInternal; import android.os.Parcel; @@ -38,34 +40,14 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna */ public final @FingerprintSensorProperties.SensorType int sensorType; - /** - * The location of the center of the sensor if applicable. For example, sensors of type - * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the - * distance in pixels, measured from the left edge of the screen. - */ - public final int sensorLocationX; - - /** - * The location of the center of the sensor if applicable. For example, sensors of type - * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the - * distance in pixels, measured from the top edge of the screen. - * - */ - public final int sensorLocationY; - - /** - * The radius of the sensor if applicable. For example, sensors of type - * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius - * of the sensor, in pixels. - */ - public final int sensorRadius; + private final List mSensorLocations; public FingerprintSensorPropertiesInternal(int sensorId, @SensorProperties.Strength int strength, int maxEnrollmentsPerUser, @NonNull List componentInfo, @FingerprintSensorProperties.SensorType int sensorType, - boolean resetLockoutRequiresHardwareAuthToken, int sensorLocationX, int sensorLocationY, - int sensorRadius) { + boolean resetLockoutRequiresHardwareAuthToken, + @NonNull List sensorLocations) { // IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not // required as it can only be generated/attested/verified by TEE components. // IFingerprint@1.0 handles lockout below the HAL, but does not require a challenge. See @@ -73,9 +55,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna super(sensorId, strength, maxEnrollmentsPerUser, componentInfo, resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */); this.sensorType = sensorType; - this.sensorLocationX = sensorLocationX; - this.sensorLocationY = sensorLocationY; - this.sensorRadius = sensorRadius; + this.mSensorLocations = List.copyOf(sensorLocations); } /** @@ -88,16 +68,15 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna boolean resetLockoutRequiresHardwareAuthToken) { // TODO(b/179175438): Value should be provided from the HAL this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType, - resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */, - 1636 /* sensorLocationY */, 130 /* sensorRadius */); + resetLockoutRequiresHardwareAuthToken, List.of(new SensorLocationInternal( + "" /* displayId */, 540 /* sensorLocationX */, 1636 /* sensorLocationY */, + 130 /* sensorRadius */))); } protected FingerprintSensorPropertiesInternal(Parcel in) { super(in); sensorType = in.readInt(); - sensorLocationX = in.readInt(); - sensorLocationY = in.readInt(); - sensorRadius = in.readInt(); + mSensorLocations = in.createTypedArrayList(SensorLocationInternal.CREATOR); } public static final Creator CREATOR = @@ -122,9 +101,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(sensorType); - dest.writeInt(sensorLocationX); - dest.writeInt(sensorLocationY); - dest.writeInt(sensorRadius); + dest.writeTypedList(mSensorLocations); } public boolean isAnyUdfpsType() { @@ -150,6 +127,44 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna } } + /** + * Get the default location. + * + * Use this method when the sensor's relationship to the displays on the device do not + * matter. + * @return + */ + @NonNull + public SensorLocationInternal getLocation() { + final SensorLocationInternal location = getLocation("" /* displayId */); + return location != null ? location : SensorLocationInternal.DEFAULT; + } + + /** + * Get the location of a sensor relative to a physical display layout. + * + * @param displayId stable display id + * @return location or null if none is specified + */ + @Nullable + public SensorLocationInternal getLocation(String displayId) { + for (SensorLocationInternal location : mSensorLocations) { + if (location.displayId.equals(displayId)) { + return location; + } + } + return null; + } + + /** + * Gets all locations relative to all supported display layouts. + * @return supported locations + */ + @NonNull + public List getAllLocations() { + return mSensorLocations; + } + @Override public String toString() { return "ID: " + sensorId + ", Strength: " + sensorStrength + ", Type: " + sensorType; diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index de94b2fbb5b5933f7dcaca7ca3ae2f2e3e4fd534..ba1dc6da62a64a56fbd55de2430e0684df680490 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -52,7 +52,8 @@ interface IFingerprintService { // permission. This is effectively deprecated, since it only comes through FingerprintManager // now. A requestId is returned that can be used to cancel this operation. long authenticate(IBinder token, long operationId, int sensorId, int userId, - IFingerprintServiceReceiver receiver, String opPackageName); + IFingerprintServiceReceiver receiver, String opPackageName, + boolean shouldIgnoreEnrollmentState); // Uses the fingerprint hardware to detect for the presence of a finger, without giving details // about accept/reject/lockout. A requestId is returned that can be used to cancel this diff --git a/core/java/android/hardware/fingerprint/ISidefpsController.aidl b/core/java/android/hardware/fingerprint/ISidefpsController.aidl index 00f40048cbae82f1631092b7fb16988d94a352d9..684f18f3fd940b55581d75c0f0ab89c415ac8234 100644 --- a/core/java/android/hardware/fingerprint/ISidefpsController.aidl +++ b/core/java/android/hardware/fingerprint/ISidefpsController.aidl @@ -21,9 +21,9 @@ package android.hardware.fingerprint; */ oneway interface ISidefpsController { - // Shows the overlay. - void show(); + // Shows the overlay for the given sensor with a reason from BiometricOverlayConstants. + void show(int sensorId, int reason); // Hides the overlay. - void hide(); + void hide(int sensorId); } diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl index f18360ff4108779dff51c9d3fe1003199d196099..648edda621712cad0abe37a5d51bcd74cd4098de 100644 --- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl +++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl @@ -22,14 +22,7 @@ import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; * @hide */ oneway interface IUdfpsOverlayController { - const int REASON_UNKNOWN = 0; - const int REASON_ENROLL_FIND_SENSOR = 1; - const int REASON_ENROLL_ENROLLING = 2; - const int REASON_AUTH_BP = 3; // BiometricPrompt - const int REASON_AUTH_FPM_KEYGUARD = 4; // FingerprintManager usage from Keyguard - const int REASON_AUTH_FPM_OTHER = 5; // Other FingerprintManager usage - - // Shows the overlay. + // Shows the overlay for the given sensor with a reason from BiometricOverlayConstants. void showUdfpsOverlay(int sensorId, int reason, IUdfpsOverlayControllerCallback callback); // Hides the overlay. diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java index 3cd13a212a4b8251acfc7a32fb3df3c56c1ef4ad..f8f0970ecfe4ec0fbd29daf8e0ce5d2ad8fa6d5b 100644 --- a/core/java/android/inputmethodservice/AbstractInputMethodService.java +++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java @@ -18,16 +18,23 @@ package android.inputmethodservice; import android.annotation.MainThread; import android.annotation.NonNull; -import android.app.Service; +import android.annotation.Nullable; +import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; import android.util.proto.ProtoOutputStream; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodSession; +import android.window.WindowProviderService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -44,9 +51,22 @@ import java.io.PrintWriter; * implement. This base class takes care of reporting your InputMethod from * the service when clients bind to it, but provides no standard implementation * of the InputMethod interface itself. Derived classes must implement that - * interface. + * interface.

+ * + *

After {@link android.os.Build.VERSION_CODES#S}, the maximum possible area to show the soft + * input may not be the entire screen. For example, some devices may support to show the soft input + * on only half of screen.

+ * + *

In that case, moving the soft input from one half screen to another will trigger a + * {@link android.content.res.Resources} update to match the new {@link Configuration} and + * this {@link AbstractInputMethodService} may also receive a + * {@link #onConfigurationChanged(Configuration)} callback if there's notable configuration changes + *

+ * + * @see android.content.ComponentCallbacks#onConfigurationChanged(Configuration) + * @see Context#isUiContext Context#isUiContext to see the concept of UI Context. */ -public abstract class AbstractInputMethodService extends Service +public abstract class AbstractInputMethodService extends WindowProviderService implements KeyEvent.Callback { private InputMethod mInputMethod; @@ -272,9 +292,33 @@ public abstract class AbstractInputMethodService extends Service public void notifyUserActionIfNecessary() { } + // TODO(b/149463653): remove it in T. We missed the API deadline in S. /** @hide */ @Override public final boolean isUiContext() { return true; } + + /** @hide */ + @Override + public final int getWindowType() { + return WindowManager.LayoutParams.TYPE_INPUT_METHOD; + } + + /** @hide */ + @Override + @Nullable + public final Bundle getWindowContextOptions() { + return super.getWindowContextOptions(); + } + + /** @hide */ + @Override + public final int getInitialDisplayId() { + try { + return WindowManagerGlobal.getWindowManagerService().getImeDisplayId(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 9198eb74d1f84df02fb5892c46a2545f65a76fbb..89612fe753df11714eaaf00e528264bf3f6bd52d 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -170,8 +170,8 @@ class IInputMethodWrapper extends IInputMethod.Stub case DO_INITIALIZE_INTERNAL: { SomeArgs args = (SomeArgs) msg.obj; try { - inputMethod.initializeInternal((IBinder) args.arg1, msg.arg1, - (IInputMethodPrivilegedOperations) args.arg2, (int) args.arg3); + inputMethod.initializeInternal((IBinder) args.arg1, + (IInputMethodPrivilegedOperations) args.arg2, msg.arg1); } finally { args.recycle(); } @@ -279,11 +279,10 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void initializeInternal(IBinder token, int displayId, - IInputMethodPrivilegedOperations privOps, int configChanges) { + public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, + int configChanges) { mCaller.executeOrSendMessage( - mCaller.obtainMessageIOOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps, - configChanges)); + mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, configChanges, token, privOps)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 74cb42db785835e7918622aeee585daf5c394fbf..9f798869e54ac475f1397e87bc160d4f6e19c053 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -395,7 +395,7 @@ public class InputMethodService extends AbstractInputMethodService { /** * @hide - * The IME is visible. + * The IME is perceptibly visible to the user. */ public static final int IME_VISIBLE = 0x2; @@ -406,6 +406,15 @@ public class InputMethodService extends AbstractInputMethodService { */ public static final int IME_INVISIBLE = 0x4; + /** + * @hide + * The IME is visible, but not yet perceptible to the user (e.g. fading in) + * by {@link android.view.WindowInsetsController}. + * + * @see InputMethodManager#reportPerceptible + */ + public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x8; + // Min and max values for back disposition. private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT; private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING; @@ -515,6 +524,7 @@ public class InputMethodService extends AbstractInputMethodService { private Handler mHandler; private boolean mImeSurfaceScheduledForRemoval; private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker(); + private boolean mDestroyed; /** * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput} @@ -589,17 +599,21 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public final void initializeInternal(@NonNull IBinder token, int displayId, + public final void initializeInternal(@NonNull IBinder token, IInputMethodPrivilegedOperations privilegedOperations, int configChanges) { if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) { Log.w(TAG, "The token has already registered, ignore this initialization."); return; } + if (mDestroyed) { + Log.i(TAG, "The InputMethodService has already onDestroyed()." + + "Ignore the initialization."); + return; + } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal"); mConfigTracker.onInitialize(configChanges); mPrivOps.set(privilegedOperations); InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps); - updateInputMethodDisplay(displayId); attachToken(token); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -629,27 +643,11 @@ public class InputMethodService extends AbstractInputMethodService { throw new IllegalStateException( "attachToken() must be called at most once. token=" + token); } + attachToWindowToken(token); mToken = token; mWindow.setToken(token); } - /** - * {@inheritDoc} - * @hide - */ - @MainThread - @Override - public void updateInputMethodDisplay(int displayId) { - if (getDisplayId() == displayId) { - return; - } - // Update display for adding IME window to the right display. - // TODO(b/111364446) Need to address context lifecycle issue if need to re-create - // for update resources & configuration correctly when show soft input - // in non-default display. - updateDisplay(displayId); - } - /** * {@inheritDoc} * @@ -807,11 +805,6 @@ public class InputMethodService extends AbstractInputMethodService { return; } - if (Trace.isEnabled()) { - Binder.enableTracing(); - } else { - Binder.disableTracing(); - } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput"); ImeTracing.getInstance().triggerServiceDump( "InputMethodService.InputMethodImpl#showSoftInput", InputMethodService.this, @@ -1416,6 +1409,7 @@ public class InputMethodService extends AbstractInputMethodService { } @Override public void onDestroy() { + mDestroyed = true; super.onDestroy(); mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index fb9911811da92c31a9daffa349162a73f8cd8598..79ec55482c50028e0fd825a94539f36d0794a240 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2302,6 +2302,38 @@ public abstract class BatteryStats implements Parcelable { */ public abstract Timer getScreenBrightnessTimer(int brightnessBin); + /** + * Returns the number of physical displays on the device. + * + * {@hide} + */ + public abstract int getDisplayCount(); + + /** + * Returns the time in microseconds that the screen has been on for a display while the + * device was running on battery. + * + * {@hide} + */ + public abstract long getDisplayScreenOnTime(int display, long elapsedRealtimeUs); + + /** + * Returns the time in microseconds that a display has been dozing while the device was + * running on battery. + * + * {@hide} + */ + public abstract long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs); + + /** + * Returns the time in microseconds that a display has been on with the given brightness + * level while the device was running on battery. + * + * {@hide} + */ + public abstract long getDisplayScreenBrightnessTime(int display, int brightnessBin, + long elapsedRealtimeUs); + /** * Returns the time in microseconds that power save mode has been enabled while the device was * running on battery. @@ -5038,6 +5070,71 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } + final int numDisplays = getDisplayCount(); + if (numDisplays > 1) { + pw.println(""); + pw.print(prefix); + sb.setLength(0); + sb.append(prefix); + sb.append(" MULTI-DISPLAY POWER SUMMARY START"); + pw.println(sb.toString()); + + for (int display = 0; display < numDisplays; display++) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Display "); + sb.append(display); + sb.append(" Statistics:"); + pw.println(sb.toString()); + + final long displayScreenOnTime = getDisplayScreenOnTime(display, rawRealtime); + sb.setLength(0); + sb.append(prefix); + sb.append(" Screen on: "); + formatTimeMs(sb, displayScreenOnTime / 1000); + sb.append("("); + sb.append(formatRatioLocked(displayScreenOnTime, whichBatteryRealtime)); + sb.append(") "); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(" Screen brightness levels:"); + didOne = false; + for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) { + final long timeUs = getDisplayScreenBrightnessTime(display, bin, rawRealtime); + if (timeUs == 0) { + continue; + } + didOne = true; + sb.append("\n "); + sb.append(prefix); + sb.append(SCREEN_BRIGHTNESS_NAMES[bin]); + sb.append(" "); + formatTimeMs(sb, timeUs / 1000); + sb.append("("); + sb.append(formatRatioLocked(timeUs, displayScreenOnTime)); + sb.append(")"); + } + if (!didOne) sb.append(" (no activity)"); + pw.println(sb.toString()); + + final long displayScreenDozeTimeUs = getDisplayScreenDozeTime(display, rawRealtime); + sb.setLength(0); + sb.append(prefix); + sb.append(" Screen Doze: "); + formatTimeMs(sb, displayScreenDozeTimeUs / 1000); + sb.append("("); + sb.append(formatRatioLocked(displayScreenDozeTimeUs, whichBatteryRealtime)); + sb.append(") "); + pw.println(sb.toString()); + } + pw.print(prefix); + sb.setLength(0); + sb.append(prefix); + sb.append(" MULTI-DISPLAY POWER SUMMARY END"); + pw.println(sb.toString()); + } + pw.println(""); pw.print(prefix); sb.setLength(0); diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 03492aa56631d93e0716cd1eb8c24cee9142385f..45cc324d7302d03d29d4efbd08f77f95eaa70431 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1160,7 +1160,6 @@ public class Build { * S V2. * * Once more unto the breach, dear friends, once more. - * */ public static final int S_V2 = 32; diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 2f2f65bbe9d2b23f4375fe0d5a15c75eb6c854d0..8d9e311b11cfc32e08475832c0203a29fb6d5c8e 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -190,13 +190,11 @@ public class Environment { } @UnsupportedAppUsage - @Deprecated public File getExternalStorageDirectory() { return getExternalDirs()[0]; } @UnsupportedAppUsage - @Deprecated public File getExternalStoragePublicDirectory(String type) { return buildExternalStoragePublicDirs(type)[0]; } @@ -696,14 +694,13 @@ public class Environment { *

* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java * monitor_storage} + *

+ * Note that alternatives such as {@link Context#getExternalFilesDir(String)} or + * {@link MediaStore} offer better performance. * * @see #getExternalStorageState() * @see #isExternalStorageRemovable() - * @deprecated Alternatives such as {@link Context#getExternalFilesDir(String)}, - * {@link MediaStore}, or {@link Intent#ACTION_OPEN_DOCUMENT} offer better - * performance. */ - @Deprecated public static File getExternalStorageDirectory() { throwIfUserRequired(); return sCurrentUser.getExternalDirs()[0]; @@ -1000,6 +997,9 @@ public class Environment { *

* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java * public_picture} + *

+ * Note that alternatives such as {@link Context#getExternalFilesDir(String)} or + * {@link MediaStore} offer better performance. * * @param type The type of storage directory to return. Should be one of * {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS}, @@ -1010,11 +1010,7 @@ public class Environment { * @return Returns the File path for the directory. Note that this directory * may not yet exist, so you must make sure it exists before using * it such as with {@link File#mkdirs File.mkdirs()}. - * @deprecated Alternatives such as {@link Context#getExternalFilesDir(String)}, - * {@link MediaStore}, or {@link Intent#ACTION_OPEN_DOCUMENT} offer better - * performance. */ - @Deprecated public static File getExternalStoragePublicDirectory(String type) { throwIfUserRequired(); return sCurrentUser.buildExternalStoragePublicDirs(type)[0]; diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 3aa0bcb6abee399522d01b974e1bb5ba3ed9d42a..4dae7c7c96d5b3d79f574e39a099b664b084944a 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -548,6 +548,7 @@ public final class PowerManager { WAKE_REASON_HDMI, WAKE_REASON_DISPLAY_GROUP_ADDED, WAKE_REASON_DISPLAY_GROUP_TURNED_ON, + WAKE_REASON_UNFOLD_DEVICE }) @Retention(RetentionPolicy.SOURCE) public @interface WakeReason{} @@ -646,6 +647,12 @@ public final class PowerManager { */ public static final int WAKE_REASON_DISPLAY_GROUP_TURNED_ON = 11; + /** + * Wake up reason code: Waking the device due to unfolding of a foldable device. + * @hide + */ + public static final int WAKE_REASON_UNFOLD_DEVICE = 12; + /** * Convert the wake reason to a string for debugging purposes. * @hide @@ -664,6 +671,7 @@ public final class PowerManager { case WAKE_REASON_LID: return "WAKE_REASON_LID"; case WAKE_REASON_DISPLAY_GROUP_ADDED: return "WAKE_REASON_DISPLAY_GROUP_ADDED"; case WAKE_REASON_DISPLAY_GROUP_TURNED_ON: return "WAKE_REASON_DISPLAY_GROUP_TURNED_ON"; + case WAKE_REASON_UNFOLD_DEVICE: return "WAKE_REASON_UNFOLD_DEVICE"; default: return Integer.toString(wakeReason); } } diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java index 46eb2ece435cb1a5ebe14957a92a59c5e30550ca..cba4423831a27a5b323431e80cf18154313689e9 100644 --- a/core/java/android/os/SharedMemory.java +++ b/core/java/android/os/SharedMemory.java @@ -63,7 +63,7 @@ public final class SharedMemory implements Parcelable, Closeable { mMemoryRegistration = new MemoryRegistration(mSize); mCleaner = Cleaner.create(mFileDescriptor, - new Closer(mFileDescriptor, mMemoryRegistration)); + new Closer(mFileDescriptor.getInt$(), mMemoryRegistration)); } /** @@ -276,6 +276,7 @@ public final class SharedMemory implements Parcelable, Closeable { */ @Override public void close() { + mFileDescriptor.setInt$(-1); if (mCleaner != null) { mCleaner.clean(); mCleaner = null; @@ -325,10 +326,10 @@ public final class SharedMemory implements Parcelable, Closeable { * Cleaner that closes the FD */ private static final class Closer implements Runnable { - private FileDescriptor mFd; + private int mFd; private MemoryRegistration mMemoryReference; - private Closer(FileDescriptor fd, MemoryRegistration memoryReference) { + private Closer(int fd, MemoryRegistration memoryReference) { mFd = fd; mMemoryReference = memoryReference; } @@ -336,7 +337,9 @@ public final class SharedMemory implements Parcelable, Closeable { @Override public void run() { try { - Os.close(mFd); + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(mFd); + Os.close(fd); } catch (ErrnoException e) { /* swallow error */ } mMemoryReference.release(); mMemoryReference = null; diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 55b1f940c764423e17179b6f841d8cf23a056b7c..07f40828eb56a013bec51ca16df6409d943a25f7 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -73,6 +73,15 @@ "[^/]*BatteryConsumer[^/]*\\.java" ], "name": "BatteryUsageStatsProtoTests" + }, + { + "file_patterns": ["SharedMemory[^/]*\\.java"], + "name": "CtsOsTestCases", + "options": [ + { + "include-filter": "android.os.cts.SharedMemoryTest" + } + ] } ], "postsubmit": [ diff --git a/core/java/android/os/incremental/OWNERS b/core/java/android/os/incremental/OWNERS index 363486905c93716d01854937849174237f478853..47eee640620669d9304a1603312fe6670a1c316c 100644 --- a/core/java/android/os/incremental/OWNERS +++ b/core/java/android/os/incremental/OWNERS @@ -1,5 +1,6 @@ # Bug component: 554432 alexbuy@google.com schfan@google.com +toddke@google.com zyy@google.com patb@google.com diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 63bcc9c89ca9d426735d45330260c28adc3f3465..c64aa4db561ab338f2ed274342c4f94eabb48f95 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -159,8 +159,6 @@ public final class PermissionManager { mPermissionManager = IPermissionManager.Stub.asInterface(ServiceManager.getServiceOrThrow( "permissionmgr")); mLegacyPermissionManager = context.getSystemService(LegacyPermissionManager.class); - //TODO ntmyren: there should be a way to only enable the watcher when requested - mUsageHelper = new PermissionUsageHelper(context); } /** @@ -871,6 +869,29 @@ public final class PermissionManager { return mSplitPermissionInfos; } + /** + * Initialize the PermissionUsageHelper, which will register active app op listeners + * + * @hide + */ + public void initializeUsageHelper() { + if (mUsageHelper == null) { + mUsageHelper = new PermissionUsageHelper(mContext); + } + } + + /** + * Teardown the PermissionUsageHelper, removing listeners + * + * @hide + */ + public void tearDownUsageHelper() { + if (mUsageHelper != null) { + mUsageHelper.tearDown(); + mUsageHelper = null; + } + } + /** * @return A list of permission groups currently or recently used by all apps by all users in * the current profile group. @@ -881,7 +902,7 @@ public final class PermissionManager { @NonNull @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS) public List getIndicatorAppOpUsageData() { - return mUsageHelper.getOpUsageData(new AudioManager().isMicrophoneMute()); + return getIndicatorAppOpUsageData(new AudioManager().isMicrophoneMute()); } /** @@ -896,9 +917,7 @@ public final class PermissionManager { @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS) public List getIndicatorAppOpUsageData(boolean micMuted) { // Lazily initialize the usage helper - if (mUsageHelper == null) { - mUsageHelper = new PermissionUsageHelper(mContext); - } + initializeUsageHelper(); return mUsageHelper.getOpUsageData(micMuted); } diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 19f204b377c8e61a55916d0976b8b4fefa74c553..cf2361a1026a0c2b5ae02adbeb4aa61378e4f861 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -176,6 +176,11 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis return mUserContexts.get(user); } + public void tearDown() { + mAppOpsManager.stopWatchingActive(this); + mAppOpsManager.stopWatchingStarted(this); + } + @Override public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, boolean active) { @@ -194,22 +199,24 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis // if any link in the chain is finished, remove the chain. Then, find any other chains that // contain this op/package/uid/tag combination, and remove them, as well. // TODO ntmyren: be smarter about this - mAttributionChains.remove(attributionChainId); - int numChains = mAttributionChains.size(); - ArrayList toRemove = new ArrayList<>(); - for (int i = 0; i < numChains; i++) { - int chainId = mAttributionChains.keyAt(i); - ArrayList chain = mAttributionChains.valueAt(i); - int chainSize = chain.size(); - for (int j = 0; j < chainSize; j++) { - AccessChainLink link = chain.get(j); - if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) { - toRemove.add(chainId); - break; + synchronized(mAttributionChains) { + mAttributionChains.remove(attributionChainId); + int numChains = mAttributionChains.size(); + ArrayList toRemove = new ArrayList<>(); + for (int i = 0; i < numChains; i++) { + int chainId = mAttributionChains.keyAt(i); + ArrayList chain = mAttributionChains.valueAt(i); + int chainSize = chain.size(); + for (int j = 0; j < chainSize; j++) { + AccessChainLink link = chain.get(j); + if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) { + toRemove.add(chainId); + break; + } } } + mAttributionChains.removeAll(toRemove); } - mAttributionChains.removeAll(toRemove); } @Override @@ -229,8 +236,10 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis // If this is not a successful start, or it is not a chain, or it is untrusted, return return; } - addLinkToChainIfNotPresent(AppOpsManager.opToPublicName(op), packageName, uid, - attributionTag, attributionFlags, attributionChainId); + synchronized(mAttributionChains) { + addLinkToChainIfNotPresent(AppOpsManager.opToPublicName(op), packageName, uid, + attributionTag, attributionFlags, attributionChainId); + } } private void addLinkToChainIfNotPresent(String op, String packageName, int uid, @@ -305,7 +314,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis String permGroup = usedPermGroups.get(permGroupNum); ArrayMap usagesWithLabels = - getUniqueUsagesWithLabels(rawUsages.get(permGroup)); + getUniqueUsagesWithLabels(permGroup, rawUsages.get(permGroup)); if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) { isPhone = true; @@ -426,7 +435,8 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis return ListFormatter.getInstance().format(labels); } - private ArrayMap getUniqueUsagesWithLabels(List usages) { + private ArrayMap getUniqueUsagesWithLabels(String permGroup, + List usages) { ArrayMap usagesAndLabels = new ArrayMap<>(); if (usages == null || usages.isEmpty()) { @@ -461,7 +471,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis // If this usage has a proxy, but is not a proxy, it is the end of a chain. // TODO remove once camera converted if (!proxies.containsKey(usageAttr) && usage.proxy != null - && !usage.op.equals(OPSTR_RECORD_AUDIO)) { + && !MICROPHONE.equals(permGroup)) { proxyLabels.put(usage, new ArrayList<>()); proxyPackages.add(usage.getPackageIdHash()); } @@ -533,48 +543,51 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis // TODO ntmyren: remove this proxy logic once camera is converted to AttributionSource // For now: don't add mic proxy usages - if (!start.op.equals(OPSTR_RECORD_AUDIO)) { + if (!MICROPHONE.equals(permGroup)) { usagesAndLabels.put(start, proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList)); } } - for (int i = 0; i < mAttributionChains.size(); i++) { - List usageList = mAttributionChains.valueAt(i); - int lastVisible = usageList.size() - 1; - // TODO ntmyren: remove this mic code once camera is converted to AttributionSource - // if the list is empty or incomplete, do not show it. - if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd() - || !usageList.get(0).isStart() - || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) { - continue; - } + synchronized (mAttributionChains) { + for (int i = 0; i < mAttributionChains.size(); i++) { + List usageList = mAttributionChains.valueAt(i); + int lastVisible = usageList.size() - 1; + // TODO ntmyren: remove this mic code once camera is converted to AttributionSource + // if the list is empty or incomplete, do not show it. + if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd() + || !usageList.get(0).isStart() + || !permGroup.equals(getGroupForOp(usageList.get(0).usage.op)) + || !MICROPHONE.equals(permGroup)) { + continue; + } - //TODO ntmyren: remove once camera etc. etc. - for (AccessChainLink link: usageList) { - proxyPackages.add(link.usage.getPackageIdHash()); - } + //TODO ntmyren: remove once camera etc. etc. + for (AccessChainLink link : usageList) { + proxyPackages.add(link.usage.getPackageIdHash()); + } - AccessChainLink start = usageList.get(0); - AccessChainLink lastVisibleLink = usageList.get(lastVisible); - while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) { - lastVisible--; - lastVisibleLink = usageList.get(lastVisible); - } - String proxyLabel = null; - if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) { - try { - PackageManager userPkgManager = - getUserContext(lastVisibleLink.usage.getUser()).getPackageManager(); - ApplicationInfo appInfo = userPkgManager.getApplicationInfo( - lastVisibleLink.usage.packageName, 0); - proxyLabel = appInfo.loadLabel(userPkgManager).toString(); - } catch (PackageManager.NameNotFoundException e) { - // do nothing + AccessChainLink start = usageList.get(0); + AccessChainLink lastVisibleLink = usageList.get(lastVisible); + while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) { + lastVisible--; + lastVisibleLink = usageList.get(lastVisible); } + String proxyLabel = null; + if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) { + try { + PackageManager userPkgManager = + getUserContext(lastVisibleLink.usage.getUser()).getPackageManager(); + ApplicationInfo appInfo = userPkgManager.getApplicationInfo( + lastVisibleLink.usage.packageName, 0); + proxyLabel = appInfo.loadLabel(userPkgManager).toString(); + } catch (PackageManager.NameNotFoundException e) { + // do nothing + } + } + usagesAndLabels.put(start.usage, proxyLabel); } - usagesAndLabels.put(start.usage, proxyLabel); } for (int packageHash : mostRecentUsages.keySet()) { diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6644f1e91f1db11317301ff65dd93c9c36746c46..2d402199e196cd99df60d50137376f698dad250b 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -637,6 +637,14 @@ public final class DeviceConfig { @TestApi public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis"; + /** + * Namespace for App Compat Overrides related features. + * + * @hide + */ + @TestApi + public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap> sListeners = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e79cc32ac99dd87ea1dfcbcbd0bcdd11a96e4bb9..4372f19e421ab204ea4f6a417b639865660d789c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -28,6 +28,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; +import android.app.Activity; import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.Application; @@ -391,6 +392,21 @@ public final class Settings { public static final String ACTION_REDUCE_BRIGHT_COLORS_SETTINGS = "android.settings.REDUCE_BRIGHT_COLORS_SETTINGS"; + /** + * Activity Action: Show settings to allow configuration of Color inversion. + *

+ * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + *

+ * Input: Nothing. + *

+ * Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_COLOR_INVERSION_SETTINGS = + "android.settings.COLOR_INVERSION_SETTINGS"; + /** * Activity Action: Show settings to control access to usage information. *

@@ -950,6 +966,22 @@ public final class Settings { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_LOCKSCREEN_SETTINGS = "android.settings.LOCK_SCREEN_SETTINGS"; + /** + * Activity Action: Show settings to allow pairing bluetooth devices. + *

+ * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + *

+ * Input: Nothing. + *

+ * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_BLUETOOTH_PAIRING_SETTINGS = + "android.settings.BLUETOOTH_PAIRING_SETTINGS"; + /** * Activity Action: Show settings to configure input methods, in particular * allowing the user to enable input methods. @@ -6344,6 +6376,27 @@ public final class Settings { @Readable public static final String ALLOW_MOCK_LOCATION = "mock_location"; + /** + * This is used by Bluetooth Manager to store adapter name + * @hide + */ + @Readable(maxTargetSdk = Build.VERSION_CODES.S) + public static final String BLUETOOTH_NAME = "bluetooth_name"; + + /** + * This is used by Bluetooth Manager to store adapter address + * @hide + */ + @Readable(maxTargetSdk = Build.VERSION_CODES.S) + public static final String BLUETOOTH_ADDRESS = "bluetooth_address"; + + /** + * This is used by Bluetooth Manager to store whether adapter address is valid + * @hide + */ + @Readable(maxTargetSdk = Build.VERSION_CODES.S) + public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid"; + /** * Setting to indicate that on device captions are enabled. * @@ -8991,6 +9044,15 @@ public final class Settings { @Readable public static final String UNSAFE_VOLUME_MUSIC_ACTIVE_MS = "unsafe_volume_music_active_ms"; + /** + * Indicates whether the spatial audio feature was enabled for this user. + * + * Type : int (0 disabled, 1 enabled) + * + * @hide + */ + public static final String SPATIAL_AUDIO_ENABLED = "spatial_audio_enabled"; + /** * Indicates whether notification display on the lock screen is enabled. *

@@ -9606,6 +9668,14 @@ public final class Settings { */ public static final String LOCKSCREEN_SHOW_WALLET = "lockscreen_show_wallet"; + /** + * Whether to use the lockscreen double-line clock + * + * @hide + */ + public static final String LOCKSCREEN_USE_DOUBLE_LINE_CLOCK = + "lockscreen_use_double_line_clock"; + /** * Specifies whether the web action API is enabled. * @@ -9992,15 +10062,18 @@ public final class Settings { /** * Controls the accessibility button mode. System will force-set the value to {@link - * #ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU} if {@link #NAVIGATION_MODE} is fully - * gestural. + * #ACCESSIBILITY_BUTTON_MODE_GESTURE} if {@link #NAVIGATION_MODE} is button; force-set the + * value to {@link ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} if {@link #NAVIGATION_MODE} is + * gestural; otherwise, remain the option. *

    *
  • 0 = button in navigation bar
  • *
  • 1 = button floating on the display
  • + *
  • 2 = button using gesture to trigger
  • *
* * @see #ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR * @see #ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU + * @see #ACCESSIBILITY_BUTTON_MODE_GESTURE * @hide */ public static final String ACCESSIBILITY_BUTTON_MODE = @@ -10022,6 +10095,14 @@ public final class Settings { */ public static final int ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU = 0x1; + /** + * Accessibility button mode value that specifying the accessibility service or feature to + * be toggled via the gesture. + * + * @hide + */ + public static final int ACCESSIBILITY_BUTTON_MODE_GESTURE = 0x2; + /** * The size of the accessibility floating menu. *
    @@ -10136,6 +10217,61 @@ public final class Settings { @Readable public static final String GAME_DASHBOARD_ALWAYS_ON = "game_dashboard_always_on"; + + /** + * For this device state, no specific auto-rotation lock setting should be applied. + * If the user toggles the auto-rotate lock in this state, the setting will apply to the + * previously valid device state. + * @hide + */ + public static final int DEVICE_STATE_ROTATION_LOCK_IGNORED = 0; + /** + * For this device state, the setting for auto-rotation is locked. + * @hide + */ + public static final int DEVICE_STATE_ROTATION_LOCK_LOCKED = 1; + /** + * For this device state, the setting for auto-rotation is unlocked. + * @hide + */ + public static final int DEVICE_STATE_ROTATION_LOCK_UNLOCKED = 2; + + /** + * The different settings that can be used as values with + * {@link #DEVICE_STATE_ROTATION_LOCK}. + * @hide + */ + @IntDef(prefix = {"DEVICE_STATE_ROTATION_LOCK_"}, value = { + DEVICE_STATE_ROTATION_LOCK_IGNORED, + DEVICE_STATE_ROTATION_LOCK_LOCKED, + DEVICE_STATE_ROTATION_LOCK_UNLOCKED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface DeviceStateRotationLockSetting { + } + + /** + * Rotation lock setting keyed on device state. + * + * This holds a serialized map using int keys that represent Device States and value of + * {@link DeviceStateRotationLockSetting} representing the rotation lock setting for that + * device state. + * + * Serialized as key0:value0:key1:value1:...:keyN:valueN. + * + * Example: "0:1:1:2:2:1" + * This example represents a map of: + *
      + *
    • 0 -> DEVICE_STATE_ROTATION_LOCK_LOCKED
    • + *
    • 1 -> DEVICE_STATE_ROTATION_LOCK_UNLOCKED
    • + *
    • 2 -> DEVICE_STATE_ROTATION_LOCK_IGNORED
    • + *
    + * + * @hide + */ + public static final String DEVICE_STATE_ROTATION_LOCK = + "device_state_rotation_lock"; + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. @@ -16911,6 +17047,44 @@ public final class Settings { public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; + /** + * Activity Action: For system or preinstalled apps to show their {@link Activity} embedded + * in Settings app on large screen devices. + *

    + * Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to + * specify the intent for the activity which will be embedded in Settings app. + * It's an intent URI string from {@code intent.toUri(Intent.URI_INTENT_SCHEME)}. + * + * Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY} must be included to + * specify a key that indicates the menu item which will be highlighted on settings home menu. + *

    + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = + "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY"; + + /** + * Activity Extra: Specify the intent for the {@link Activity} which will be embedded in + * Settings app. It's an intent URI string from + * {@code intent.toUri(Intent.URI_INTENT_SCHEME)}. + *

    + * This must be passed as an extra field to + * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}. + */ + public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI = + "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI"; + + /** + * Activity Extra: Specify a key that indicates the menu item which should be highlighted on + * settings home menu. + *

    + * This must be passed as an extra field to + * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}. + */ + public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY = + "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY"; + /** * Performs a strict and comprehensive check of whether a calling package is allowed to * write/modify system settings, as the condition differs for pre-M, M+, and diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 71f90fd28e3f4d28d3ced7aaf091c831357ec89a..c94595468aec22ad078a3716d059492f98c52a00 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -83,11 +83,11 @@ import java.util.Objects; * </intent-filter> * <meta-data * android:name="android.service.notification.default_filter_types" - * android:value="conversations,alerting"> + * android:value="conversations|alerting"> * </meta-data> * <meta-data * android:name="android.service.notification.disabled_filter_types" - * android:value="ongoing,silent"> + * android:value="ongoing|silent"> * </meta-data> * </service> * @@ -112,8 +112,9 @@ public abstract class NotificationListenerService extends Service { private final String TAG = getClass().getSimpleName(); /** - * The name of the {@code meta-data} tag containing a comma separated list of default - * integer notification types that should be provided to this listener. See + * The name of the {@code meta-data} tag containing a pipe separated list of default + * integer notification types or "ongoing", "conversations", "alerting", or "silent" + * that should be provided to this listener. See * {@link #FLAG_FILTER_TYPE_ONGOING}, * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING), * and {@link #FLAG_FILTER_TYPE_SILENT}. @@ -1698,7 +1699,7 @@ public abstract class NotificationListenerService extends Service { private ArrayList mSmartActions; private ArrayList mSmartReplies; private boolean mCanBubble; - private boolean mVisuallyInterruptive; + private boolean mIsTextChanged; private boolean mIsConversation; private ShortcutInfo mShortcutInfo; private @RankingAdjustment int mRankingAdjustment; @@ -1735,7 +1736,7 @@ public abstract class NotificationListenerService extends Service { out.writeTypedList(mSmartActions, flags); out.writeCharSequenceList(mSmartReplies); out.writeBoolean(mCanBubble); - out.writeBoolean(mVisuallyInterruptive); + out.writeBoolean(mIsTextChanged); out.writeBoolean(mIsConversation); out.writeParcelable(mShortcutInfo, flags); out.writeInt(mRankingAdjustment); @@ -1773,7 +1774,7 @@ public abstract class NotificationListenerService extends Service { mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR); mSmartReplies = in.readCharSequenceList(); mCanBubble = in.readBoolean(); - mVisuallyInterruptive = in.readBoolean(); + mIsTextChanged = in.readBoolean(); mIsConversation = in.readBoolean(); mShortcutInfo = in.readParcelable(cl); mRankingAdjustment = in.readInt(); @@ -1976,8 +1977,8 @@ public abstract class NotificationListenerService extends Service { } /** @hide */ - public boolean visuallyInterruptive() { - return mVisuallyInterruptive; + public boolean isTextChanged() { + return mIsTextChanged; } /** @hide */ @@ -2032,7 +2033,7 @@ public abstract class NotificationListenerService extends Service { int userSentiment, boolean hidden, long lastAudiblyAlertedMs, boolean noisy, ArrayList smartActions, ArrayList smartReplies, boolean canBubble, - boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo, + boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo, int rankingAdjustment, boolean isBubble) { mKey = key; mRank = rank; @@ -2054,7 +2055,7 @@ public abstract class NotificationListenerService extends Service { mSmartActions = smartActions; mSmartReplies = smartReplies; mCanBubble = canBubble; - mVisuallyInterruptive = visuallyInterruptive; + mIsTextChanged = isTextChanged; mIsConversation = isConversation; mShortcutInfo = shortcutInfo; mRankingAdjustment = rankingAdjustment; @@ -2095,7 +2096,7 @@ public abstract class NotificationListenerService extends Service { other.mSmartActions, other.mSmartReplies, other.mCanBubble, - other.mVisuallyInterruptive, + other.mIsTextChanged, other.mIsConversation, other.mShortcutInfo, other.mRankingAdjustment, @@ -2152,7 +2153,7 @@ public abstract class NotificationListenerService extends Service { == (other.mSmartActions == null ? 0 : other.mSmartActions.size())) && Objects.equals(mSmartReplies, other.mSmartReplies) && Objects.equals(mCanBubble, other.mCanBubble) - && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive) + && Objects.equals(mIsTextChanged, other.mIsTextChanged) && Objects.equals(mIsConversation, other.mIsConversation) // Shortcutinfo doesn't have equals either; use id && Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()), diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index c142a53e047e69c8dfa9fd652b4be40beb9d70fd..59f1e8eed89c550f2de06f0d5c05da6fe9cdd6e5 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -22,6 +22,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.os.IBinder; +import android.service.voice.VisibleActivityInfo; import com.android.internal.app.IVoiceInteractionSessionShowCallback; @@ -39,4 +40,5 @@ oneway interface IVoiceInteractionSession { void closeSystemDialogs(); void onLockscreenShown(); void destroy(); + void updateVisibleActivityInfo(in VisibleActivityInfo visibleActivityInfo, int type); } diff --git a/core/java/android/service/voice/VisibleActivityInfo.aidl b/core/java/android/service/voice/VisibleActivityInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..34bd57c154561300a52816aaa1aa448192436124 --- /dev/null +++ b/core/java/android/service/voice/VisibleActivityInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.voice; + +parcelable VisibleActivityInfo; diff --git a/core/java/android/service/voice/VisibleActivityInfo.java b/core/java/android/service/voice/VisibleActivityInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..139544c76a504e19e339525586d9381704828628 --- /dev/null +++ b/core/java/android/service/voice/VisibleActivityInfo.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.voice; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * The class is used to represent a visible activity information. The system provides this to + * services that need to know {@link android.service.voice.VoiceInteractionSession.ActivityId}. + */ +@DataClass( + genConstructor = false, + genEqualsHashCode = true, + genHiddenConstDefs = false, + genGetters = false, + genToString = true +) +public final class VisibleActivityInfo implements Parcelable { + + /** + * Indicates that it is a new visible activity. + * + * @hide + */ + public static final int TYPE_ACTIVITY_ADDED = 1; + + /** + * Indicates that it has become a invisible activity. + * + * @hide + */ + public static final int TYPE_ACTIVITY_REMOVED = 2; + + /** + * The identifier of the task this activity is in. + */ + private final int mTaskId; + + /** + * Token for targeting this activity for assist purposes. + */ + @NonNull + private final IBinder mAssistToken; + + /** @hide */ + @TestApi + public VisibleActivityInfo( + int taskId, + @NonNull IBinder assistToken) { + Objects.requireNonNull(assistToken); + mTaskId = taskId; + mAssistToken = assistToken; + } + + /** + * Returns the {@link android.service.voice.VoiceInteractionSession.ActivityId} of this + * visible activity which can be used to interact with an activity, for example through + * {@link VoiceInteractionSession#requestDirectActions(VoiceInteractionSession.ActivityId, + * CancellationSignal, Executor, Consumer)}. + */ + public @NonNull VoiceInteractionSession.ActivityId getActivityId() { + return new VoiceInteractionSession.ActivityId(mTaskId, mAssistToken); + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/VisibleActivityInfo.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "VisibleActivityInfo { " + + "taskId = " + mTaskId + ", " + + "assistToken = " + mAssistToken + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(VisibleActivityInfo other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + VisibleActivityInfo that = (VisibleActivityInfo) o; + //noinspection PointlessBooleanExpression + return true + && mTaskId == that.mTaskId + && Objects.equals(mAssistToken, that.mAssistToken); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mTaskId; + _hash = 31 * _hash + Objects.hashCode(mAssistToken); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mTaskId); + dest.writeStrongBinder(mAssistToken); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ VisibleActivityInfo(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int taskId = in.readInt(); + IBinder assistToken = (IBinder) in.readStrongBinder(); + + this.mTaskId = taskId; + this.mAssistToken = assistToken; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAssistToken); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public VisibleActivityInfo[] newArray(int size) { + return new VisibleActivityInfo[size]; + } + + @Override + public VisibleActivityInfo createFromParcel(@NonNull Parcel in) { + return new VisibleActivityInfo(in); + } + }; + + @DataClass.Generated( + time = 1632383555284L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/voice/VisibleActivityInfo.java", + inputSignatures = "public static final int TYPE_ACTIVITY_ADDED\npublic static final int TYPE_ACTIVITY_REMOVED\nprivate final int mTaskId\nprivate final @android.annotation.NonNull android.os.IBinder mAssistToken\npublic @android.annotation.NonNull android.service.voice.VoiceInteractionSession.ActivityId getActivityId()\nclass VisibleActivityInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genEqualsHashCode=true, genHiddenConstDefs=false, genGetters=false, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java index c048286545c341438f81d390e0a94b29989e7480..c80640910bc2bdcfe9a03a449e82a6133468bd96 100644 --- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java +++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java @@ -22,7 +22,6 @@ import android.os.IBinder; import com.android.internal.annotations.Immutable; - /** * @hide * Private interface to the VoiceInteractionManagerService for use by ActivityManagerService. diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 725e20f2a74d39adc6b6f0d6487b6864124a017d..ee32ce43821cc161f6a39e77955235a88db3b5bc 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -74,9 +74,11 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -177,6 +179,10 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall ICancellationSignal mKillCallback; + private final Map mVisibleActivityCallbacks = + new ArrayMap<>(); + private final List mVisibleActivityInfos = new ArrayList<>(); + final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { @Override public IVoiceInteractorRequest startConfirmation(String callingPackage, @@ -352,6 +358,13 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall public void destroy() { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY)); } + + @Override + public void updateVisibleActivityInfo(VisibleActivityInfo visibleActivityInfo, int type) { + mHandlerCaller.sendMessage( + mHandlerCaller.obtainMessageIO(MSG_UPDATE_VISIBLE_ACTIVITY_INFO, type, + visibleActivityInfo)); + } }; /** @@ -843,6 +856,9 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall static final int MSG_SHOW = 106; static final int MSG_HIDE = 107; static final int MSG_ON_LOCKSCREEN_SHOWN = 108; + static final int MSG_UPDATE_VISIBLE_ACTIVITY_INFO = 109; + static final int MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK = 110; + static final int MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK = 111; class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback { @Override @@ -928,6 +944,27 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall if (DEBUG) Log.d(TAG, "onLockscreenShown"); onLockscreenShown(); break; + case MSG_UPDATE_VISIBLE_ACTIVITY_INFO: + if (DEBUG) { + Log.d(TAG, "doUpdateVisibleActivityInfo: visibleActivityInfo=" + msg.obj + + " type=" + msg.arg1); + } + doUpdateVisibleActivityInfo((VisibleActivityInfo) msg.obj, msg.arg1); + break; + case MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK: + if (DEBUG) { + Log.d(TAG, "doRegisterVisibleActivityCallback"); + } + args = (SomeArgs) msg.obj; + doRegisterVisibleActivityCallback((Executor) args.arg1, + (VisibleActivityCallback) args.arg2); + break; + case MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK: + if (DEBUG) { + Log.d(TAG, "doUnregisterVisibleActivityCallback"); + } + doUnregisterVisibleActivityCallback((VisibleActivityCallback) msg.obj); + break; } if (args != null) { args.recycle(); @@ -1122,6 +1159,86 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } } + private void doUpdateVisibleActivityInfo(VisibleActivityInfo visibleActivityInfo, int type) { + + if (mVisibleActivityCallbacks.isEmpty()) { + return; + } + + switch (type) { + case VisibleActivityInfo.TYPE_ACTIVITY_ADDED: + informVisibleActivityChanged(visibleActivityInfo, type); + mVisibleActivityInfos.add(visibleActivityInfo); + break; + case VisibleActivityInfo.TYPE_ACTIVITY_REMOVED: + informVisibleActivityChanged(visibleActivityInfo, type); + mVisibleActivityInfos.remove(visibleActivityInfo); + break; + } + } + + private void doRegisterVisibleActivityCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull VisibleActivityCallback callback) { + if (mVisibleActivityCallbacks.containsKey(callback)) { + if (DEBUG) { + Log.d(TAG, "doRegisterVisibleActivityCallback: callback has registered"); + } + return; + } + + int preCallbackCount = mVisibleActivityCallbacks.size(); + mVisibleActivityCallbacks.put(callback, executor); + + if (preCallbackCount == 0) { + try { + mSystemService.startListeningVisibleActivityChanged(mToken); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } else { + for (int i = 0; i < mVisibleActivityInfos.size(); i++) { + final VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfos.get(i); + executor.execute(() -> callback.onVisible(visibleActivityInfo)); + } + } + } + + private void doUnregisterVisibleActivityCallback(@NonNull VisibleActivityCallback callback) { + mVisibleActivityCallbacks.remove(callback); + + if (mVisibleActivityCallbacks.size() == 0) { + mVisibleActivityInfos.clear(); + try { + mSystemService.stopListeningVisibleActivityChanged(mToken); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + private void informVisibleActivityChanged(VisibleActivityInfo visibleActivityInfo, int type) { + for (Map.Entry e : + mVisibleActivityCallbacks.entrySet()) { + final Executor executor = e.getValue(); + final VisibleActivityCallback visibleActivityCallback = e.getKey(); + + switch (type) { + case VisibleActivityInfo.TYPE_ACTIVITY_ADDED: + Binder.withCleanCallingIdentity(() -> { + executor.execute( + () -> visibleActivityCallback.onVisible(visibleActivityInfo)); + }); + break; + case VisibleActivityInfo.TYPE_ACTIVITY_REMOVED: + Binder.withCleanCallingIdentity(() -> { + executor.execute(() -> visibleActivityCallback.onInvisible( + visibleActivityInfo.getActivityId())); + }); + break; + } + } + } + void ensureWindowCreated() { if (mInitialized) { return; @@ -1633,8 +1750,9 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall /** * Called when there has been a failure transferring the {@link AssistStructure} to * the assistant. This may happen, for example, if the data is too large and results - * in an out of memory exception, or the client has provided corrupt data. This will - * be called immediately before {@link #onHandleAssist} and the AssistStructure supplied + * in an out of memory exception, the data has been cleared during transferring due to + * the new incoming assist data, or the client has provided corrupt data. This will be + * called immediately before {@link #onHandleAssist} and the AssistStructure supplied * there afterwards will be null. * * @param failure The failure exception that was thrown when building the @@ -1672,7 +1790,8 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall * Called to receive data from the application that the user was currently viewing when * an assist session is started. If the original show request did not specify * {@link #SHOW_WITH_ASSIST}, {@link AssistState} parameter will only provide - * {@link ActivityId}. + * {@link ActivityId}. If there was a failure to write the assist data to + * {@link AssistStructure}, the {@link AssistState#getAssistStructure()} will return null. * *

    This method is called for all activities along with an index and count that indicates * which activity the data is for. {@code index} will be between 0 and {@code count}-1 and @@ -1925,6 +2044,49 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall public void onCancelRequest(Request request) { } + /** + * Registers a callback that will be notified when visible activities have been changed. + * + * Note: The {@link VisibleActivityCallback#onVisible(VisibleActivityInfo)} will be called + * immediately with current visible activities when the callback is registered for the first + * time. If the callback is already registered, this method does nothing. + * + * @param executor The executor which will be used to invoke the callback. + * @param callback The callback to receive the response. + * + * @throws IllegalStateException if calling this method before onCreate(). + */ + public final void registerVisibleActivityCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull VisibleActivityCallback callback) { + if (DEBUG) { + Log.d(TAG, "registerVisibleActivityCallback"); + } + if (mToken == null) { + throw new IllegalStateException("Can't call before onCreate()"); + } + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + mHandlerCaller.sendMessage( + mHandlerCaller.obtainMessageOO(MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK, executor, + callback)); + } + + /** + * Unregisters the callback. + * + * @param callback The callback to receive the response. + */ + public final void unregisterVisibleActivityCallback(@NonNull VisibleActivityCallback callback) { + if (DEBUG) { + Log.d(TAG, "unregisterVisibleActivityCallback"); + } + Objects.requireNonNull(callback); + + mHandlerCaller.sendMessage( + mHandlerCaller.obtainMessageO(MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK, callback)); + } + /** * Print the Service's state into the given stream. This gets invoked by * {@link VoiceInteractionSessionService} when its Service @@ -1974,6 +2136,17 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } } + /** + * Callback interface for receiving visible activity changes used for assistant usage. + */ + public interface VisibleActivityCallback { + /** Callback to inform that an activity has become visible. */ + default void onVisible(@NonNull VisibleActivityInfo activityInfo) {} + + /** Callback to inform that a visible activity has gone. */ + default void onInvisible(@NonNull ActivityId activityId) {} + } + /** * Represents assist state captured when this session was started. * It contains the various assist data objects and a reference to @@ -2043,7 +2216,8 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall * @return If available, the structure definition of all windows currently * displayed by the app. May be null if assist data has been disabled by the user * or device policy; will be null if the original show request did not specify - * {@link #SHOW_WITH_ASSIST}; will be an empty stub if the application has disabled assist + * {@link #SHOW_WITH_ASSIST} or the assist data has been corrupt when writing the data to + * {@link AssistStructure}; will be an empty stub if the application has disabled assist * by marking its window as secure. */ public @Nullable AssistStructure getAssistStructure() { diff --git a/core/java/android/service/wallpaper/EngineWindowPage.java b/core/java/android/service/wallpaper/EngineWindowPage.java index 5ed0ad6f2aebec3dc216e618179a027a1f910b8c..006e3cdf03f81a647f931f3c83a59d422961cfa3 100644 --- a/core/java/android/service/wallpaper/EngineWindowPage.java +++ b/core/java/android/service/wallpaper/EngineWindowPage.java @@ -24,7 +24,6 @@ import android.util.ArraySet; import java.util.Map; import java.util.Set; -import java.util.function.Consumer; /** * This class represents a page of a launcher page used by the wallpaper @@ -84,11 +83,6 @@ public class EngineWindowPage { return mCallbackAreas; } - /** run operations on this page */ - public synchronized void execSync(Consumer run) { - run.accept(this); - } - /** nullify the area color */ public void removeColor(RectF colorArea) { mRectFColors.remove(colorArea); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index c9a0121936dce5818cf3d5b0362a1826e4683e1c..b68717a9b45763c24f7f2c75b8dcc291331a767b 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -16,6 +16,8 @@ package android.service.wallpaper; +import static android.app.WallpaperManager.COMMAND_FREEZE; +import static android.app.WallpaperManager.COMMAND_UNFREEZE; import static android.graphics.Matrix.MSCALE_X; import static android.graphics.Matrix.MSCALE_Y; import static android.graphics.Matrix.MSKEW_X; @@ -42,12 +44,14 @@ import android.content.res.TypedArray; import android.graphics.BLASTBufferQueue; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Build; @@ -56,6 +60,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -73,6 +78,7 @@ import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.MotionEvent; import android.view.PixelCopy; import android.view.Surface; @@ -190,7 +196,7 @@ public abstract class WallpaperService extends Service { EngineWindowPage[] mWindowPages = new EngineWindowPage[1]; Bitmap mLastScreenshot; int mLastWindowPage = -1; - float mLastPageOffset = 0; + private boolean mResetWindowPages; // Copies from mIWallpaperEngine. HandlerCaller mCaller; @@ -201,6 +207,12 @@ public abstract class WallpaperService extends Service { boolean mVisible; boolean mReportedVisible; boolean mDestroyed; + // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false + // after receiving WallpaperManager#COMMAND_UNFREEZE. COMMAND_FREEZE is fully applied once + // mScreenshotSurfaceControl isn't null. When this happens, then Engine is notified through + // doVisibilityChanged that main wallpaper surface is no longer visible and the wallpaper + // host receives onVisibilityChanged(false) callback. + private boolean mFrozenRequested = false; // Current window state. boolean mCreated; @@ -226,10 +238,9 @@ public abstract class WallpaperService extends Service { final ClientWindowFrames mWinFrames = new ClientWindowFrames(); final Rect mDispatchedContentInsets = new Rect(); final Rect mDispatchedStableInsets = new Rect(); - final Rect mFinalSystemInsets = new Rect(); - final Rect mFinalStableInsets = new Rect(); DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT; final InsetsState mInsetsState = new InsetsState(); + final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0]; final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); private final Point mSurfaceSize = new Point(); @@ -266,6 +277,8 @@ public abstract class WallpaperService extends Service { SurfaceControl mSurfaceControl = new SurfaceControl(); SurfaceControl mBbqSurfaceControl; BLASTBufferQueue mBlastBufferQueue; + private SurfaceControl mScreenshotSurfaceControl; + private Point mScreenshotSize = new Point(); final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { { @@ -774,7 +787,7 @@ public abstract class WallpaperService extends Service { Log.w(TAG, "Can't notify system because wallpaper connection " + "was not established."); } - resetWindowPages(); + mResetWindowPages = true; processLocalColors(mPendingXOffset, mPendingXOffsetStep); } catch (RemoteException e) { Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e); @@ -966,6 +979,7 @@ public abstract class WallpaperService extends Service { void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) { if (mDestroyed) { Log.w(TAG, "Ignoring updateSurface due to destroyed"); + return; } boolean fixedSize = false; @@ -1032,8 +1046,8 @@ public abstract class WallpaperService extends Service { InputChannel inputChannel = new InputChannel(); if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE, - mDisplay.getDisplayId(), mInsetsState, inputChannel, mInsetsState, - mTempControls) < 0) { + mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel, + mInsetsState, mTempControls) < 0) { Log.w(TAG, "Failed to add window while updating wallpaper surface."); return; } @@ -1383,11 +1397,15 @@ public abstract class WallpaperService extends Service { if (!mDestroyed) { mVisible = visible; reportVisibility(); - if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); + if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); } } void reportVisibility() { + if (mScreenshotSurfaceControl != null && mVisible) { + if (DEBUG) Log.v(TAG, "Frozen so don't report visibility change"); + return; + } if (!mDestroyed) { mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getState(); boolean visible = mVisible && mDisplayState != Display.STATE_OFF; @@ -1404,6 +1422,10 @@ public abstract class WallpaperService extends Service { updateSurface(true, false, false); } onVisibilityChanged(visible); + if (mReportedVisible && mFrozenRequested) { + if (DEBUG) Log.v(TAG, "Freezing wallpaper after visibility update"); + freeze(); + } } } } @@ -1468,7 +1490,7 @@ public abstract class WallpaperService extends Service { //below is the default implementation if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN || !mSurfaceHolder.getSurface().isValid()) return; - int xPage; + int xCurrentPage; int xPages; if (!validStep(xOffsetStep)) { if (DEBUG) { @@ -1476,30 +1498,35 @@ public abstract class WallpaperService extends Service { } xOffset = 0; xOffsetStep = 1; - xPage = 0; + xCurrentPage = 0; xPages = 1; } else { xPages = Math.round(1 / xOffsetStep) + 1; xOffsetStep = (float) 1 / (float) xPages; float shrink = (float) (xPages - 1) / (float) xPages; xOffset *= shrink; - xPage = Math.round(xOffset / xOffsetStep); + xCurrentPage = Math.round(xOffset / xOffsetStep); } if (DEBUG) { - Log.d(TAG, "xPages " + xPages + " xPage " + xPage); + Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage); Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset); } - EngineWindowPage current; - synchronized (mLock) { + + float finalXOffsetStep = xOffsetStep; + float finalXOffset = xOffset; + mHandler.post(() -> { + resetWindowPages(); + int xPage = xCurrentPage; + EngineWindowPage current; if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) { mWindowPages = new EngineWindowPage[xPages]; - initWindowPages(mWindowPages, xOffsetStep); + initWindowPages(mWindowPages, finalXOffsetStep); } if (mLocalColorsToAdd.size() != 0) { for (RectF colorArea : mLocalColorsToAdd) { if (!isValid(colorArea)) continue; mLocalColorAreas.add(colorArea); - int colorPage = getRectFPage(colorArea, xOffsetStep); + int colorPage = getRectFPage(colorArea, finalXOffsetStep); EngineWindowPage currentPage = mWindowPages[colorPage]; if (currentPage == null) { currentPage = new EngineWindowPage(); @@ -1517,7 +1544,8 @@ public abstract class WallpaperService extends Service { Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage); Log.e(TAG, "error on page " + xPage + " out of " + xPages); Log.e(TAG, - "error on xOffsetStep " + xOffsetStep + " xOffset " + xOffset); + "error on xOffsetStep " + finalXOffsetStep + + " xOffset " + finalXOffset); } xPage = mWindowPages.length - 1; } @@ -1525,13 +1553,14 @@ public abstract class WallpaperService extends Service { if (current == null) { if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages); if (DEBUG) { - Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset); + Log.d(TAG, "xOffsetStep " + finalXOffsetStep + " xOffset " + + finalXOffset); } current = new EngineWindowPage(); mWindowPages[xPage] = current; } - } - updatePage(current, xPage, xPages, xOffsetStep); + updatePage(current, xPage, xPages, finalXOffsetStep); + }); } private void initWindowPages(EngineWindowPage[] windowPages, float step) { @@ -1553,7 +1582,7 @@ public abstract class WallpaperService extends Service { void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages, float xOffsetStep) { // to save creating a runnable, check twice - long current = SystemClock.elapsedRealtime(); + long current = System.currentTimeMillis(); long lapsed = current - currentPage.getLastUpdateTime(); // Always update the page when the last update time is <= 0 // This is important especially when the device first boots @@ -1581,10 +1610,8 @@ public abstract class WallpaperService extends Service { if (DEBUG) Log.d(TAG, "result of pixel copy is " + res); if (res != PixelCopy.SUCCESS) { Bitmap lastBitmap = currentPage.getBitmap(); - currentPage.execSync((p) -> { - // assign the last bitmap taken for now - p.setBitmap(mLastScreenshot); - }); + // assign the last bitmap taken for now + currentPage.setBitmap(mLastScreenshot); Bitmap lastScreenshot = mLastScreenshot; if (lastScreenshot != null && !lastScreenshot.isRecycled() && !Objects.equals(lastBitmap, lastScreenshot)) { @@ -1593,10 +1620,8 @@ public abstract class WallpaperService extends Service { } else { mLastScreenshot = finalScreenShot; // going to hold this lock for a while - currentPage.execSync((p) -> { - p.setBitmap(finalScreenShot); - p.setLastUpdateTime(current); - }); + currentPage.setBitmap(finalScreenShot); + currentPage.setLastUpdateTime(current); updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); } }, mHandler); @@ -1675,15 +1700,13 @@ public abstract class WallpaperService extends Service { private void resetWindowPages() { if (supportsLocalColorExtraction()) return; + if (!mResetWindowPages) return; + mResetWindowPages = false; mLastWindowPage = -1; - synchronized (mLock) { - for (int i = 0; i < mWindowPages.length; i++) { - EngineWindowPage page = mWindowPages[i]; - if (page != null) { - page.execSync((p) -> { - p.setLastUpdateTime(0L); - }); - } + for (int i = 0; i < mWindowPages.length; i++) { + EngineWindowPage page = mWindowPages[i]; + if (page != null) { + page.setLastUpdateTime(0L); } } } @@ -1708,44 +1731,12 @@ public abstract class WallpaperService extends Service { if (DEBUG) { Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions); } - float step = mPendingXOffsetStep; + mHandler.post(() -> { + mLocalColorsToAdd.addAll(regions); + processLocalColors(mPendingXOffset, mPendingYOffset); + }); - List colors = getLocalWallpaperColors(regions); - synchronized (mLock) { - if (!validStep(step)) { - step = 0; - } - for (int i = 0; i < regions.size(); i++) { - RectF area = regions.get(i); - if (!isValid(area)) continue; - int pageInx = getRectFPage(area, step); - // no page should be null - EngineWindowPage page = mWindowPages[pageInx]; - - if (page != null) { - mLocalColorAreas.add(area); - page.addArea(area); - WallpaperColors color = colors.get(i); - if (color != null && !color.equals(page.getColors(area))) { - page.execSync(p -> { - p.addWallpaperColors(area, color); - }); - } - } else { - mLocalColorsToAdd.add(area); - } - } - } - for (int i = 0; i < colors.size() && colors.get(i) != null; i++) { - try { - mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i), - mDisplayContext.getDisplayId()); - } catch (RemoteException e) { - Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); - return; - } - } } /** @@ -1755,95 +1746,20 @@ public abstract class WallpaperService extends Service { */ public void removeLocalColorsAreas(@NonNull List regions) { if (supportsLocalColorExtraction()) return; - synchronized (mLock) { + mHandler.post(() -> { float step = mPendingXOffsetStep; mLocalColorsToAdd.removeAll(regions); mLocalColorAreas.removeAll(regions); if (!validStep(step)) { return; } - for (int i = 0; i < regions.size(); i++) { - RectF area = regions.get(i); - if (!isValid(area)) continue; - int pageInx = getRectFPage(area, step); - // no page should be null - EngineWindowPage page = mWindowPages[pageInx]; - if (page != null) { - page.execSync(p -> { - p.removeArea(area); - }); - } - } - } - } - - private @NonNull List getLocalWallpaperColors(@NonNull List areas) { - ArrayList colors = new ArrayList<>(areas.size()); - float step = mPendingXOffsetStep; - if (!validStep(step)) { - if (DEBUG) Log.d(TAG, "invalid step size " + step); - step = 1.0f; - } - for (int i = 0; i < areas.size(); i++) { - RectF currentArea = areas.get(i); - if (currentArea == null || !isValid(currentArea)) { - Log.wtf(TAG, "invalid local area " + currentArea); - continue; - } - EngineWindowPage page; - RectF area; - int pageIndx; - synchronized (mLock) { - pageIndx = getRectFPage(currentArea, step); - if (mWindowPages.length == 0 || pageIndx < 0 - || pageIndx > mWindowPages.length || !isValid(currentArea)) { - colors.add(null); - continue; - } - area = generateSubRect(currentArea, pageIndx, mWindowPages.length); - page = mWindowPages[pageIndx]; - } - if (page == null) { - colors.add(null); - continue; - } - float finalStep = step; - int finalPageIndx = pageIndx; - Bitmap screenShot = page.getBitmap(); - if (screenShot == null) screenShot = mLastScreenshot; - if (screenShot == null || screenShot.isRecycled()) { - if (DEBUG) { - Log.d(TAG, "invalid bitmap " + screenShot - + " for page " + finalPageIndx); + for (int i = 0; i < mWindowPages.length; i++) { + for (int j = 0; j < regions.size(); j++) { + EngineWindowPage page = mWindowPages[i]; + if (page != null) page.removeArea(regions.get(j)); } - page.setLastUpdateTime(0); - colors.add(null); - continue; } - Bitmap b = screenShot; - Rect subImage = new Rect( - Math.round(area.left * b.getWidth() / finalStep), - Math.round(area.top * b.getHeight()), - Math.round(area.right * b.getWidth() / finalStep), - Math.round(area.bottom * b.getHeight()) - ); - subImage = fixRect(b, subImage); - if (DEBUG) { - Log.d(TAG, "getting subbitmap of " + subImage.toString() - + " for RectF " + area.toString() - + " screenshot width " + screenShot.getWidth() + " height " - + screenShot.getHeight()); - } - Bitmap colorImg = Bitmap.createBitmap(screenShot, - subImage.left, subImage.top, subImage.width(), subImage.height()); - if (DEBUG) { - Log.d(TAG, "created bitmap " + colorImg.getWidth() + ", " - + colorImg.getHeight()); - } - WallpaperColors color = WallpaperColors.fromBitmap(colorImg); - colors.add(color); - } - return colors; + }); } // fix the rect to be included within the bounds of the bitmap @@ -1864,6 +1780,9 @@ public abstract class WallpaperService extends Service { void doCommand(WallpaperCommand cmd) { Bundle result; if (!mDestroyed) { + if (COMMAND_FREEZE.equals(cmd.action) || COMMAND_UNFREEZE.equals(cmd.action)) { + updateFrozenState(/* frozenRequested= */ !COMMAND_UNFREEZE.equals(cmd.action)); + } result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z, cmd.extras, cmd.sync); } else { @@ -1878,6 +1797,159 @@ public abstract class WallpaperService extends Service { } } + private void updateFrozenState(boolean frozenRequested) { + if (mIWallpaperEngine.mWallpaperManager.getWallpaperInfo() == null + // Procees the unfreeze command in case the wallaper became static while + // being paused. + && frozenRequested) { + if (DEBUG) Log.v(TAG, "Ignoring the freeze command for static wallpapers"); + return; + } + mFrozenRequested = frozenRequested; + boolean isFrozen = mScreenshotSurfaceControl != null; + if (mFrozenRequested == isFrozen) { + return; + } + if (mFrozenRequested) { + freeze(); + } else { + unfreeze(); + } + } + + private void freeze() { + if (!mReportedVisible || mDestroyed) { + // Screenshot can't be taken until visibility is reported to the wallpaper host. + return; + } + if (!showScreenshotOfWallpaper()) { + return; + } + // Prevent a wallpaper host from rendering wallpaper behind a screeshot. + doVisibilityChanged(false); + // Remember that visibility is requested since it's not guaranteed that + // mWindow#dispatchAppVisibility will be called when letterboxed application with + // wallpaper background transitions to the Home screen. + mVisible = true; + } + + private void unfreeze() { + cleanUpScreenshotSurfaceControl(); + if (mVisible) { + doVisibilityChanged(true); + } + } + + private void cleanUpScreenshotSurfaceControl() { + // TODO(b/194399558): Add crossfade transition. + if (mScreenshotSurfaceControl != null) { + new SurfaceControl.Transaction() + .remove(mScreenshotSurfaceControl) + .show(mBbqSurfaceControl) + .apply(); + mScreenshotSurfaceControl = null; + } + } + + void scaleAndCropScreenshot() { + if (mScreenshotSurfaceControl == null) { + return; + } + if (mScreenshotSize.x <= 0 || mScreenshotSize.y <= 0) { + Log.w(TAG, "Unexpected screenshot size: " + mScreenshotSize); + return; + } + // Don't scale down and using the same scaling factor for both dimensions to + // avoid stretching wallpaper image. + float scaleFactor = Math.max(1, Math.max( + ((float) mSurfaceSize.x) / mScreenshotSize.x, + ((float) mSurfaceSize.y) / mScreenshotSize.y)); + int diffX = ((int) (mScreenshotSize.x * scaleFactor)) - mSurfaceSize.x; + int diffY = ((int) (mScreenshotSize.y * scaleFactor)) - mSurfaceSize.y; + if (DEBUG) { + Log.v(TAG, "Adjusting screenshot: scaleFactor=" + scaleFactor + + " diffX=" + diffX + " diffY=" + diffY + " mSurfaceSize=" + mSurfaceSize + + " mScreenshotSize=" + mScreenshotSize); + } + new SurfaceControl.Transaction() + .setMatrix( + mScreenshotSurfaceControl, + /* dsdx= */ scaleFactor, /* dtdx= */ 0, + /* dtdy= */ 0, /* dsdy= */ scaleFactor) + .setWindowCrop( + mScreenshotSurfaceControl, + new Rect( + /* left= */ diffX / 2, + /* top= */ diffY / 2, + /* right= */ diffX / 2 + mScreenshotSize.x, + /* bottom= */ diffY / 2 + mScreenshotSize.y)) + .setPosition(mScreenshotSurfaceControl, -diffX / 2, -diffY / 2) + .apply(); + } + + private boolean showScreenshotOfWallpaper() { + if (mDestroyed || mSurfaceControl == null || !mSurfaceControl.isValid()) { + if (DEBUG) Log.v(TAG, "Failed to screenshot wallpaper: surface isn't valid"); + return false; + } + + final Rect bounds = new Rect(0, 0, mSurfaceSize.x, mSurfaceSize.y); + if (bounds.isEmpty()) { + Log.w(TAG, "Failed to screenshot wallpaper: surface bounds are empty"); + return false; + } + + if (mScreenshotSurfaceControl != null) { + Log.e(TAG, "Screenshot is unexpectedly not null"); + // Destroying previous screenshot since it can have different size. + cleanUpScreenshotSurfaceControl(); + } + + SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = + SurfaceControl.captureLayers( + new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl) + // Needed because SurfaceFlinger#validateScreenshotPermissions + // uses this parameter to check whether a caller only attempts + // to screenshot itself when call doesn't come from the system. + .setUid(Process.myUid()) + .setChildrenOnly(false) + .setSourceCrop(bounds) + .build()); + + if (screenshotBuffer == null) { + Log.w(TAG, "Failed to screenshot wallpaper: screenshotBuffer is null"); + return false; + } + + final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); + + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + + // TODO(b/194399558): Add crossfade transition. + mScreenshotSurfaceControl = new SurfaceControl.Builder() + .setName("Wallpaper snapshot for engine " + this) + .setFormat(hardwareBuffer.getFormat()) + .setParent(mSurfaceControl) + .setSecure(screenshotBuffer.containsSecureLayers()) + .setCallsite("WallpaperService.Engine.showScreenshotOfWallpaper") + .setBLASTLayer() + .build(); + + mScreenshotSize.set(mSurfaceSize.x, mSurfaceSize.y); + + GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(hardwareBuffer); + + t.setBuffer(mScreenshotSurfaceControl, graphicBuffer); + t.setColorSpace(mScreenshotSurfaceControl, screenshotBuffer.getColorSpace()); + // Place on top everything else. + t.setLayer(mScreenshotSurfaceControl, Integer.MAX_VALUE); + t.show(mScreenshotSurfaceControl); + t.hide(mBbqSurfaceControl); + t.apply(); + + return true; + } + void reportSurfaceDestroyed() { if (mSurfaceCreated) { mSurfaceCreated = false; @@ -2202,6 +2274,7 @@ public abstract class WallpaperService extends Service { final boolean reportDraw = message.arg1 != 0; mEngine.updateSurface(true, false, reportDraw); mEngine.doOffsetsChanged(true); + mEngine.scaleAndCropScreenshot(); } break; case MSG_WINDOW_MOVED: { // Do nothing. What does it mean for a Wallpaper to move? diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java index bdfa700215f8aa1837b6499116fcbc543450e90a..9cdd54c16a42b6d068fba032c938efdb0b5b8561 100644 --- a/core/java/android/text/style/StyleSpan.java +++ b/core/java/android/text/style/StyleSpan.java @@ -17,8 +17,10 @@ package android.text.style; import android.annotation.NonNull; +import android.content.res.Configuration; import android.graphics.Paint; import android.graphics.Typeface; +import android.graphics.fonts.FontStyle; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; @@ -45,6 +47,7 @@ import android.text.TextUtils; public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { private final int mStyle; + private final int mFontWeightAdjustment; /** * Creates a {@link StyleSpan} from a style. @@ -54,7 +57,24 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { * in {@link Typeface}. */ public StyleSpan(int style) { + this(style, Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED); + } + + /** + * Creates a {@link StyleSpan} from a style and font weight adjustment. + * + * @param style An integer constant describing the style for this span. Examples + * include bold, italic, and normal. Values are constants defined + * in {@link Typeface}. + * @param fontWeightAdjustment An integer describing the adjustment to be made to the font + * weight. + * @see Configuration#fontWeightAdjustment This is the adjustment in text font weight + * that is used to reflect the current user's preference for increasing font weight. + * @hide + */ + public StyleSpan(@Typeface.Style int style, int fontWeightAdjustment) { mStyle = style; + mFontWeightAdjustment = fontWeightAdjustment; } /** @@ -64,6 +84,7 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { */ public StyleSpan(@NonNull Parcel src) { mStyle = src.readInt(); + mFontWeightAdjustment = src.readInt(); } @Override @@ -91,6 +112,7 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { @Override public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mStyle); + dest.writeInt(mFontWeightAdjustment); } /** @@ -100,17 +122,25 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { return mStyle; } + /** + * Returns the font weight adjustment specified by this span. + * @hide + */ + public int getFontWeightAdjustment() { + return mFontWeightAdjustment; + } + @Override public void updateDrawState(TextPaint ds) { - apply(ds, mStyle); + apply(ds, mStyle, mFontWeightAdjustment); } @Override public void updateMeasureState(TextPaint paint) { - apply(paint, mStyle); + apply(paint, mStyle, mFontWeightAdjustment); } - private static void apply(Paint paint, int style) { + private static void apply(Paint paint, int style, int fontWeightAdjustment) { int oldStyle; Typeface old = paint.getTypeface(); @@ -129,6 +159,18 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { tf = Typeface.create(old, want); } + // Base typeface may already be bolded by auto bold. Bold further. + if ((style & Typeface.BOLD) != 0) { + if (fontWeightAdjustment != 0 + && fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { + int newWeight = Math.min( + Math.max(tf.getWeight() + fontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN), + FontStyle.FONT_WEIGHT_MAX); + boolean italic = (want & Typeface.ITALIC) != 0; + tf = Typeface.create(tf, newWeight, italic); + } + } + int fake = want & ~tf.getStyle(); if ((fake & Typeface.BOLD) != 0) { diff --git a/core/java/android/util/DisplayUtils.java b/core/java/android/util/DisplayUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..4fe7f8369f734c4de779a1f10d234b671dd88b79 --- /dev/null +++ b/core/java/android/util/DisplayUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.content.res.Resources; + +import com.android.internal.R; + +/** + * Utils for loading resources for multi-display. + * + * @hide + */ +public class DisplayUtils { + + /** + * Gets the index of the given display unique id in {@link R.array#config_displayUniqueIdArray} + * which is used to get the related cutout configs for that display. + * + * For multi-display device, {@link R.array#config_displayUniqueIdArray} should be set for each + * display if there are different type of cutouts on each display. + * For single display device, {@link R.array#config_displayUniqueIdArray} should not to be set + * and the system will load the default configs for main built-in display. + */ + public static int getDisplayUniqueIdConfigIndex(Resources res, String displayUniqueId) { + int index = -1; + if (displayUniqueId == null || displayUniqueId.isEmpty()) { + return index; + } + final String[] ids = res.getStringArray(R.array.config_displayUniqueIdArray); + final int size = ids.length; + for (int i = 0; i < size; i++) { + if (displayUniqueId.equals(ids[i])) { + index = i; + break; + } + } + return index; + } +} diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 546d6de82935f3c2846d13e5bab09cefa189e35a..e08b913fe248aebcd1d5b708ddfacba168ddea48 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -51,6 +51,9 @@ public class FeatureFlagUtils { /** @hide */ public static final String SETTINGS_ENABLE_SECURITY_HUB = "settings_enable_security_hub"; + /** @hide */ + public static final String SETTINGS_SUPPORT_LARGE_SCREEN = "settings_support_large_screen"; + /** @hide */ public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS = "settings_enable_monitor_phantom_procs"; @@ -76,6 +79,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "true"); DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true"); + DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); } @@ -83,6 +87,7 @@ public class FeatureFlagUtils { static { PERSISTENT_FLAGS = new HashSet<>(); PERSISTENT_FLAGS.add(SETTINGS_PROVIDER_MODEL); + PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); } diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java index 971e16185815be0fdcaba4073475f5dc53625a5c..aecde4415117f334e7ae3bcd98f3a7b48cfe10fd 100644 --- a/core/java/android/util/MathUtils.java +++ b/core/java/android/util/MathUtils.java @@ -165,6 +165,10 @@ public final class MathUtils { return start + (stop - start) * amount; } + public static float lerp(int start, int stop, float amount) { + return lerp((float) start, (float) stop, amount); + } + /** * Returns the interpolation scalar (s) that satisfies the equation: {@code value = }{@link * #lerp}{@code (a, b, s)} diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java index 06e4c5002776e7fa8dbcb1062eb5f56568874d93..d605430bcf148fb91ce2f4eabfad16ab149ea7b6 100644 --- a/core/java/android/util/imetracing/ImeTracingServerImpl.java +++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java @@ -41,9 +41,9 @@ import java.io.PrintWriter; */ class ImeTracingServerImpl extends ImeTracing { private static final String TRACE_DIRNAME = "/data/misc/wmtrace/"; - private static final String TRACE_FILENAME_CLIENTS = "ime_trace_clients.pb"; - private static final String TRACE_FILENAME_IMS = "ime_trace_service.pb"; - private static final String TRACE_FILENAME_IMMS = "ime_trace_managerservice.pb"; + private static final String TRACE_FILENAME_CLIENTS = "ime_trace_clients.winscope"; + private static final String TRACE_FILENAME_IMS = "ime_trace_service.winscope"; + private static final String TRACE_FILENAME_IMMS = "ime_trace_managerservice.winscope"; private static final int BUFFER_CAPACITY = 4096 * 1024; // Needed for winscope to auto-detect the dump type. Explained further in diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index bcc5b56459bbd706167b6e8258baa2af9d56a8a4..69af2a5ce7fb4cef2dec015f133e9f5b4d51a0d7 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; +import android.hardware.HardwareBuffer; /** * Provides an interface to the root-Surface of a View Hierarchy or Window. This @@ -53,4 +54,74 @@ public interface AttachedSurfaceControl { * to the View hierarchy you may need to call {@link android.view.View#invalidate} */ boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t); + + /** + * The transform hint can be used by a buffer producer to pre-rotate the rendering such that the + * final transformation in the system composer is identity. This can be very useful when used in + * conjunction with the h/w composer HAL in situations where it cannot handle rotations or + * handle them with an additional power cost. + * + * The transform hint should be used with ASurfaceControl APIs when submitting buffers. + * Example usage: + * + * 1. After a configuration change, before dequeuing a buffer, the buffer producer queries the + * function for the transform hint. + * + * 2. The desired buffer width and height is rotated by the transform hint. + * + * 3. The producer dequeues a buffer of the new pre-rotated size. + * + * 4. The producer renders to the buffer such that the image is already transformed, that is + * applying the transform hint to the rendering. + * + * 5. The producer applies the inverse transform hint to the buffer it just rendered. + * + * 6. The producer queues the pre-transformed buffer with the buffer transform. + * + * 7. The composer combines the buffer transform with the display transform. If the buffer + * transform happens to cancel out the display transform then no rotation is needed and there + * will be no performance penalties. + * + * Note, when using ANativeWindow APIs in conjunction with a NativeActivity Surface or + * SurfaceView Surface, the buffer producer will already have access to the transform hint and + * no additional work is needed. + * + * @see HardwareBuffer + */ + default @SurfaceControl.BufferTransform int getBufferTransformHint() { + return SurfaceControl.BUFFER_TRANSFORM_IDENTITY; + } + + /** + * Buffer transform hint change listener. + * @see #getBufferTransformHint + */ + @UiThread + interface OnBufferTransformHintChangedListener { + /** + * @param hint new surface transform hint + * @see #getBufferTransformHint + */ + void onBufferTransformHintChanged(@SurfaceControl.BufferTransform int hint); + } + + /** + * Registers a {@link OnBufferTransformHintChangedListener} to receive notifications about when + * the transform hint changes. + * + * @see #getBufferTransformHint + * @see #removeOnBufferTransformHintChangedListener + */ + default void addOnBufferTransformHintChangedListener( + @NonNull OnBufferTransformHintChangedListener listener) { + } + + /** + * Unregisters a {@link OnBufferTransformHintChangedListener}. + * + * @see #addOnBufferTransformHintChangedListener + */ + default void removeOnBufferTransformHintChangedListener( + @NonNull OnBufferTransformHintChangedListener listener) { + } } diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java index 7023e4bd0134ad09f9f9e2e8130ff9424f20eb77..1ed12f74ba2c263ffcc5135f3a24f0c563ba376e 100644 --- a/core/java/android/view/BatchedInputEventReceiver.java +++ b/core/java/android/view/BatchedInputEventReceiver.java @@ -17,6 +17,7 @@ package android.view; import android.compat.annotation.UnsupportedAppUsage; +import android.os.Handler; import android.os.Looper; /** @@ -27,6 +28,13 @@ public class BatchedInputEventReceiver extends InputEventReceiver { private Choreographer mChoreographer; private boolean mBatchingEnabled; private boolean mBatchedInputScheduled; + private final Handler mHandler; + private final Runnable mConsumeBatchedInputEvents = new Runnable() { + @Override + public void run() { + consumeBatchedInputEvents(-1); + } + }; @UnsupportedAppUsage public BatchedInputEventReceiver( @@ -34,6 +42,7 @@ public class BatchedInputEventReceiver extends InputEventReceiver { super(inputChannel, looper); mChoreographer = choreographer; mBatchingEnabled = true; + mHandler = new Handler(looper); } @Override @@ -57,10 +66,15 @@ public class BatchedInputEventReceiver extends InputEventReceiver { * @hide */ public void setBatchingEnabled(boolean batchingEnabled) { + if (mBatchingEnabled == batchingEnabled) { + return; + } + mBatchingEnabled = batchingEnabled; + mHandler.removeCallbacks(mConsumeBatchedInputEvents); if (!batchingEnabled) { unscheduleBatchedInput(); - consumeBatchedInputEvents(-1); + mHandler.post(mConsumeBatchedInputEvents); } } diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index e1a4402d89644c8f2e73aeddf02ea23a7982eeac..c1a5636b7b349b807122f9295024d4d12f20588c 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -31,6 +31,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Path; @@ -38,6 +39,7 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.DisplayUtils; import android.util.Pair; import android.util.RotationUtils; import android.util.proto.ProtoOutputStream; @@ -872,6 +874,110 @@ public final class DisplayCutout { false /* copyArguments */); } + /** + * Gets the display cutout by the given display unique id. + * + * Loads the default config {@link R.string#config_mainBuiltInDisplayCutout) if + * {@link R.array#config_displayUniqueIdArray} is not set. + */ + private static String getDisplayCutoutPath(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final String[] array = res.getStringArray(R.array.config_displayCutoutPathArray); + if (index >= 0 && index < array.length) { + return array[index]; + } + return res.getString(R.string.config_mainBuiltInDisplayCutout); + } + + /** + * Gets the display cutout approximation rect by the given display unique id. + * + * Loads the default config {@link R.string#config_mainBuiltInDisplayCutoutRectApproximation} if + * {@link R.array#config_displayUniqueIdArray} is not set. + */ + private static String getDisplayCutoutApproximationRect(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final String[] array = res.getStringArray( + R.array.config_displayCutoutApproximationRectArray); + if (index >= 0 && index < array.length) { + return array[index]; + } + return res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation); + } + + /** + * Gets whether to mask a built-in display cutout of a display which is determined by the + * given display unique id. + * + * Loads the default config {@link R.bool#config_maskMainBuiltInDisplayCutout} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static boolean getMaskBuiltInDisplayCutout(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray); + boolean maskCutout; + if (index >= 0 && index < array.length()) { + maskCutout = array.getBoolean(index, false); + } else { + maskCutout = res.getBoolean(R.bool.config_maskMainBuiltInDisplayCutout); + } + array.recycle(); + return maskCutout; + } + + /** + * Gets whether to fill a built-in display cutout of a display which is determined by the + * given display unique id. + * + * Loads the default config{@link R.bool#config_fillMainBuiltInDisplayCutout} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static boolean getFillBuiltInDisplayCutout(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray(R.array.config_fillBuiltInDisplayCutoutArray); + boolean fillCutout; + if (index >= 0 && index < array.length()) { + fillCutout = array.getBoolean(index, false); + } else { + fillCutout = res.getBoolean(R.bool.config_fillMainBuiltInDisplayCutout); + } + array.recycle(); + return fillCutout; + } + + /** + * Gets the waterfall cutout by the given display unique id. + * + * Loads the default waterfall dimens if {@link R.array#config_displayUniqueIdArray} is not set. + * {@link R.dimen#waterfall_display_left_edge_size}, + * {@link R.dimen#waterfall_display_top_edge_size}, + * {@link R.dimen#waterfall_display_right_edge_size}, + * {@link R.dimen#waterfall_display_bottom_edge_size} + */ + private static Insets getWaterfallInsets(Resources res, String displayUniqueId) { + Insets insets; + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray(R.array.config_waterfallCutoutArray); + if (index >= 0 && index < array.length() && array.getResourceId(index, 0) > 0) { + final int resourceId = array.getResourceId(index, 0); + final TypedArray waterfall = res.obtainTypedArray(resourceId); + insets = Insets.of( + waterfall.getDimensionPixelSize(0 /* waterfall left edge size */, 0), + waterfall.getDimensionPixelSize(1 /* waterfall top edge size */, 0), + waterfall.getDimensionPixelSize(2 /* waterfall right edge size */, 0), + waterfall.getDimensionPixelSize(3 /* waterfall bottom edge size */, 0)); + waterfall.recycle(); + } else { + insets = loadWaterfallInset(res); + } + array.recycle(); + return insets; + } + /** * Creates the display cutout according to * @android:string/config_mainBuiltInDisplayCutoutRectApproximation, which is the closest @@ -879,12 +985,12 @@ public final class DisplayCutout { * * @hide */ - public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth, - int displayHeight) { - return pathAndDisplayCutoutFromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout), - res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation), + public static DisplayCutout fromResourcesRectApproximation(Resources res, + String displayUniqueId, int displayWidth, int displayHeight) { + return pathAndDisplayCutoutFromSpec(getDisplayCutoutPath(res, displayUniqueId), + getDisplayCutoutApproximationRect(res, displayUniqueId), displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT, - loadWaterfallInset(res)).second; + getWaterfallInsets(res, displayUniqueId)).second; } /** @@ -892,11 +998,11 @@ public final class DisplayCutout { * * @hide */ - public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) { - return pathAndDisplayCutoutFromSpec( - res.getString(R.string.config_mainBuiltInDisplayCutout), null, + public static Path pathFromResources(Resources res, String displayUniqueId, int displayWidth, + int displayHeight) { + return pathAndDisplayCutoutFromSpec(getDisplayCutoutPath(res, displayUniqueId), null, displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT, - loadWaterfallInset(res)).first; + getWaterfallInsets(res, displayUniqueId)).first; } /** diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl index 610e0f866b43da34fea5b7c00d12e99c9a784c1e..f95d6b3492213292e58e0268d55a4829a5919d0e 100644 --- a/core/java/android/view/IDisplayWindowListener.aidl +++ b/core/java/android/view/IDisplayWindowListener.aidl @@ -32,7 +32,8 @@ import android.content.res.Configuration; oneway interface IDisplayWindowListener { /** - * Called when a display is added to the WM hierarchy. + * Called when a new display is added to the WM hierarchy. The existing display ids are returned + * when this listener is registered with WM via {@link #registerDisplayWindowListener}. */ void onDisplayAdded(int displayId); diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl index 811175566a90f417f342afe33d71a63071b4e024..c7fd38092ec79a4713b1adda923aeab3f451973a 100644 --- a/core/java/android/view/IRecentsAnimationRunner.aidl +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -35,15 +35,17 @@ oneway interface IRecentsAnimationRunner { * wallpaper not drawing in time, or the handler not finishing the animation within a predefined * amount of time. * - * @param taskSnapshot If the snapshot is null, the animation will be cancelled and the leash - * will be inactive immediately. Otherwise, the contents of the task will be - * replaced with {@param taskSnapshot}, such that the runner's leash is - * still active. As soon as the runner doesn't need the leash anymore, it - * must call {@link IRecentsAnimationController#cleanupScreenshot). + * @param taskIds Indicates tasks with cancelling snapshot. + * @param taskSnapshots If the snapshots is null, the animation will be cancelled and the leash + * will be inactive immediately. Otherwise, the contents of the tasks will + * be replaced with {@param taskSnapshots}, such that the runner's leash is + * still active. As soon as the runner doesn't need the leash anymore, it + * must call {@link IRecentsAnimationController#cleanupScreenshot). * * @see {@link RecentsAnimationController#cleanupScreenshot} */ - void onAnimationCanceled(in @nullable TaskSnapshot taskSnapshot) = 1; + void onAnimationCanceled(in @nullable int[] taskIds, + in @nullable TaskSnapshot[] taskSnapshots) = 1; /** * Called when the system is ready for the handler to start animating all the visible tasks. @@ -61,5 +63,5 @@ oneway interface IRecentsAnimationRunner { * Called when the task of an activity that has been started while the recents animation * was running becomes ready for control. */ - void onTaskAppeared(in RemoteAnimationTarget app) = 3; + void onTasksAppeared(in RemoteAnimationTarget[] app) = 3; } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index a7ecf1f2a81dca1c224fce4b0b559aab85059e39..b64d25a5b72cb3b98df5d6caef6c95d5baac112a 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -53,12 +53,14 @@ import android.view.IWindowSessionCallback; import android.view.KeyEvent; import android.view.InputEvent; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.InputChannel; import android.view.InputDevice; import android.view.IInputFilter; import android.view.AppTransitionAnimationSpec; +import android.view.TaskTransitionSpec; import android.view.WindowContentFrameStats; import android.view.WindowManager; import android.view.SurfaceControl; @@ -344,6 +346,14 @@ interface IWindowManager */ Bitmap screenshotWallpaper(); + /** + * Mirrors the wallpaper for the given display. + * + * @param displayId ID of the display for the wallpaper. + * @return A SurfaceControl for the parent of the mirrored wallpaper. + */ + SurfaceControl mirrorWallpaperSurface(int displayId); + /** * Registers a wallpaper visibility listener. * @return Current visibility. @@ -520,9 +530,10 @@ interface IWindowManager void unregisterDisplayFoldListener(IDisplayFoldListener listener); /** - * Registers an IDisplayContainerListener + * Registers an IDisplayContainerListener, and returns the set of existing display ids. The + * listener's onDisplayAdded() will not be called for the displays returned. */ - void registerDisplayWindowListener(IDisplayWindowListener listener); + int[] registerDisplayWindowListener(IDisplayWindowListener listener); /** * Unregisters an IDisplayContainerListener. @@ -720,14 +731,15 @@ interface IWindowManager int displayId, in IDisplayWindowInsetsController displayWindowInsetsController); /** - * Called when a remote process modifies insets on a display window container. + * Called when a remote process updates the requested visibilities of insets on a display window + * container. */ - void modifyDisplayWindowInsets(int displayId, in InsetsState state); + void updateDisplayWindowRequestedVisibilities(int displayId, in InsetsVisibilities vis); /** * Called to get the expected window insets. * - * @return {@code true} if system bars are always comsumed. + * @return {@code true} if system bars are always consumed. */ boolean getWindowInsets(in WindowManager.LayoutParams attrs, int displayId, out InsetsState outInsetsState); @@ -841,6 +853,23 @@ interface IWindowManager */ void attachWindowContextToWindowToken(IBinder clientToken, IBinder token); + /** + * Attaches a {@code clientToken} to associate with DisplayContent. + *

    + * Note that this API should be invoked after calling + * {@link android.window.WindowTokenClient#attachContext(Context)} + *

    + * + * @param clientToken {@link android.window.WindowContext#getWindowContextToken() + * the WindowContext's token} + * @param displayId The display associated with the window context + * + * @return the DisplayContent's {@link android.app.res.Configuration} if the Context is + * attached to the DisplayContent successfully. {@code null}, otherwise. + * @throws android.view.WindowManager.InvalidDisplayException if the display ID is invalid + */ + Configuration attachToDisplayContent(IBinder clientToken, int displayId); + /** * Detaches {@link android.window.WindowContext} from the window manager node it's currently * attached to. It is no-op if the WindowContext is not attached to a window manager node. @@ -866,4 +895,24 @@ interface IWindowManager void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener); boolean isTaskSnapshotSupported(); + + /** + * Returns the preferred display ID to show software keyboard. + * + * @see android.window.WindowProviderService#getLaunchedDisplayId + */ + int getImeDisplayId(); + + /** + * Customized the task transition animation with a task transition spec. + * + * @param spec the spec that will be used to customize the task animations + */ + void setTaskTransitionSpec(in TaskTransitionSpec spec); + + /** + * Clears any task transition spec that has been previously set and + * reverts to using the default task transition with no spec changes. + */ + void clearTaskTransitionSpec(); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 7bad5cbfbdc3f819a044686f69d40034a59e50f3..9da50889e43f9c59b1e5f3142fd6bcd4f4310fee 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -32,6 +32,7 @@ import android.view.MotionEvent; import android.view.WindowManager; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -46,12 +47,12 @@ import java.util.List; */ interface IWindowSession { int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs, - in int viewVisibility, in int layerStackId, in InsetsState requestedVisibility, + in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel, out InsetsState insetsState, out InsetsSourceControl[] activeControls); int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, in int userId, - in InsetsState requestedVisibility, out InputChannel outInputChannel, + in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel, out InsetsState insetsState, out InsetsSourceControl[] activeControls); int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out InsetsState insetsState); @@ -173,6 +174,11 @@ interface IWindowSession { IBinder performDrag(IWindow window, int flags, in SurfaceControl surface, int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data); + /** + * Drops the content of the current drag operation for accessibility + */ + boolean dropForAccessibility(IWindow window, int x, int y); + /** * Report the result of a drop action targeted to the given window. * consumed is 'true' when the drop was accepted by a valid recipient, @@ -285,10 +291,9 @@ interface IWindowSession { oneway void updateTapExcludeRegion(IWindow window, in Region region); /** - * Called when the client has changed the local insets state, and now the server should reflect - * that new state. + * Updates the requested visibilities of insets. */ - oneway void insetsModified(IWindow window, in InsetsState state); + oneway void updateRequestedVisibilities(IWindow window, in InsetsVisibilities visibilities); /** * Called when the system gesture exclusion has changed. diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 06861046f2c7cf13d27cf1695285b8aaaac37544..d609fb8eb2342684a2851947028bc8cb3267e1f7 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -124,6 +124,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public void setControl(@Nullable InsetsSourceControl control, int[] showTypes, int[] hideTypes) { super.setControl(control, showTypes, hideTypes); + // TODO(b/204524304): clean-up how to deal with the timing issues of hiding IME: + // 1) Already requested show IME, in the meantime of WM callback the control but got null + // control when relayout comes first + // 2) Make sure no regression on some implicit request IME visibility calls (e.g. + // toggleSoftInput) if (control == null && !mIsRequestedVisibleAwaitingControl) { hide(); removeSurface(); diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index 5a34a92a4b1a0cb47a7fcf8698ace7b67d197fec..139bff4b0118dc3e550340d63d1c5e1e6a7c2b8c 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -19,9 +19,10 @@ package android.view; import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; +import android.graphics.Matrix; import android.graphics.Region; +import android.gui.TouchOcclusionMode; import android.os.IBinder; -import android.os.TouchOcclusionMode; import java.lang.ref.WeakReference; @@ -43,6 +44,17 @@ public final class InputWindowHandle { // channel and the server input channel will both contain this token. public IBinder token; + /** + * The {@link IWindow} handle if InputWindowHandle is associated with a window, null otherwise. + */ + @Nullable + private IBinder windowToken; + /** + * Used to cache IWindow from the windowToken so we don't need to convert every time getWindow + * is called. + */ + private IWindow window; + // The window name. public String name; @@ -122,6 +134,12 @@ public final class InputWindowHandle { */ public boolean replaceTouchableRegionWithCrop; + /** + * The transform that should be applied to the Window to get it from screen coordinates to + * window coordinates + */ + public Matrix transform; + private native void nativeDispose(); public InputWindowHandle(InputApplicationHandle inputApplicationHandle, int displayId) { @@ -136,6 +154,9 @@ public final class InputWindowHandle { .append(frameRight).append(",").append(frameBottom).append("]") .append(", touchableRegion=").append(touchableRegion) .append(", visible=").append(visible) + .append(", scaleFactor=").append(scaleFactor) + .append(", transform=").append(transform) + .append(", windowToken=").append(windowToken) .toString(); } @@ -167,4 +188,17 @@ public final class InputWindowHandle { public void setTouchableRegionCrop(@Nullable SurfaceControl bounds) { touchableRegionSurfaceControl = new WeakReference<>(bounds); } + + public void setWindowToken(IWindow iwindow) { + windowToken = iwindow.asBinder(); + window = iwindow; + } + + public IWindow getWindow() { + if (window != null) { + return window; + } + window = IWindow.Stub.asInterface(windowToken); + return window; + } } diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 3431c3ecc310f166ddcba675397e94ff4723fc85..04bb6091672b3936850098a27691c8abcb5f5b89 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -20,7 +20,8 @@ import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; /** - * Provide an interface to let InsetsAnimationControlImpl call back into its owner. + * Provide an interface to let InsetsAnimationControlImpl and InsetsResizeAnimationRunner call back + * into its owner. * @hide */ public interface InsetsAnimationControlCallbacks { @@ -34,10 +35,9 @@ public interface InsetsAnimationControlCallbacks { *
  • Dispatch {@link WindowInsetsAnimationControlListener#onReady}
  • *
*/ - void startAnimation(InsetsAnimationControlImpl controller, - WindowInsetsAnimationControlListener listener, int types, - WindowInsetsAnimation animation, - Bounds bounds); + + void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types, + WindowInsetsAnimation animation, Bounds bounds); /** * Schedule the apply by posting the animation callback. diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 17b3020001d48ef81b7cac49d54cf3629a01c42d..805727c871b21ceac1d2445039724569eb689be0 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -69,7 +69,7 @@ import java.util.Objects; * @hide */ @VisibleForTesting -public class InsetsAnimationControlImpl implements WindowInsetsAnimationController, +public class InsetsAnimationControlImpl implements InternalInsetsAnimationController, InsetsAnimationControlRunner { private static final String TAG = "InsetsAnimationCtrlImpl"; @@ -105,7 +105,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll private float mCurrentAlpha = 1.0f; private float mPendingAlpha = 1.0f; @VisibleForTesting(visibility = PACKAGE) - public boolean mReadyDispatched; + private boolean mReadyDispatched; private Boolean mPerceptible; @VisibleForTesting @@ -169,6 +169,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return mHasZeroInsetsIme; } + @Override + public void setReadyDispatched(boolean dispatched) { + mReadyDispatched = dispatched; + } + @Override public Insets getHiddenStateInsets() { return mHiddenInsets; @@ -279,8 +284,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mShownOnFinish, mCurrentAlpha, mCurrentInsets)); mController.notifyFinished(this, mShownOnFinish); releaseLeashes(); + if (DEBUG) Log.d(TAG, "Animation finished abruptly."); } - if (DEBUG) Log.d(TAG, "Animation finished abruptly."); return mFinished; } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 691e638f36699735f8df186522901072d9ba4e19..fc97541bd34d4061a9a1eec880b49ff9bb24cce7 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -54,8 +54,8 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro @Override @UiThread - public void startAnimation(InsetsAnimationControlImpl controller, - WindowInsetsAnimationControlListener listener, int types, + public + void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types, WindowInsetsAnimation animation, Bounds bounds) { // Animation will be started in constructor already. } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 6f915c9182d2343667736564c1f65248e1d8eb66..9bf71ec8099870ec0dd38acf5f84b76974fbcf28 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -107,9 +107,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean hasControl); /** - * Called when insets have been modified by the client and should be reported back to WM. + * Called when the requested visibilities of insets have been modified by the client. + * The visibilities should be reported back to WM. + * + * @param visibilities A collection of the requested visibilities. */ - void onInsetsModified(InsetsState insetsState); + void updateRequestedVisibilities(InsetsVisibilities visibilities); /** * @return Whether the host has any callbacks it wants to synchronize the animations with. @@ -202,6 +205,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private static final int ANIMATION_DURATION_FADE_IN_MS = 500; private static final int ANIMATION_DURATION_FADE_OUT_MS = 1500; + /** Visible for WindowManagerWrapper */ + public static final int ANIMATION_DURATION_RESIZE = 300; + private static final int ANIMATION_DELAY_DIM_MS = 500; private static final int ANIMATION_DURATION_SYNC_IME_MS = 285; @@ -232,6 +238,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR = new PathInterpolator(0.4f, 0f, 1f, 1f); + /** Visible for WindowManagerWrapper */ + public static final Interpolator RESIZE_INTERPOLATOR = new LinearInterpolator(); + /** The amount IME will move up/down when animating in floating mode. */ private static final int FLOATING_IME_BOTTOM_INSET_DP = -80; @@ -285,9 +294,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public static final int ANIMATION_TYPE_USER = 2; + /** Running animation will resize insets */ + @VisibleForTesting + public static final int ANIMATION_TYPE_RESIZE = 3; + @Retention(RetentionPolicy.SOURCE) @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE, - ANIMATION_TYPE_USER}) + ANIMATION_TYPE_USER, ANIMATION_TYPE_RESIZE}) @interface AnimationType { } @@ -317,7 +330,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final boolean mDisable; private final int mFloatingImeBottomInset; - private ThreadLocal mSfAnimationHandlerThreadLocal = + private final ThreadLocal mSfAnimationHandlerThreadLocal = new ThreadLocal() { @Override protected AnimationHandler initialValue() { @@ -536,10 +549,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** The state dispatched from server */ private final InsetsState mLastDispatchedState = new InsetsState(); - // TODO: Use other class to represent the requested visibility of each type, because the - // display frame and the frame in each source are not used. /** The requested visibilities sent to server */ - private final InsetsState mRequestedState = new InsetsState(); + private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); private final Rect mFrame = new Rect(); private final BiFunction mConsumerCreator; @@ -549,7 +560,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final SparseArray mTmpControlArray = new SparseArray<>(); private final ArrayList mRunningAnimations = new ArrayList<>(); - private final ArrayList mTmpFinishedControls = new ArrayList<>(); private final ArraySet mRequestedVisibilityChanged = new ArraySet<>(); private WindowInsets mLastInsets; @@ -569,7 +579,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private int mCaptionInsetsHeight = 0; private boolean mAnimationsDisabled; - private Runnable mPendingControlTimeout = this::abortPendingImeControlRequest; + private final Runnable mPendingControlTimeout = this::abortPendingImeControlRequest; private final ArrayList mControllableInsetsChangedListeners = new ArrayList<>(); @@ -579,7 +589,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** Set of inset types which cannot be controlled by the user animation */ private @InsetsType int mDisabledUserAnimationInsetsTypes; - private Runnable mInvokeControllableInsetsChangedListeners = + private final Runnable mInvokeControllableInsetsChangedListeners = this::invokeControllableInsetsChangedListeners; public InsetsController(Host host) { @@ -607,23 +617,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } final List runningAnimations = new ArrayList<>(); + final List finishedAnimations = new ArrayList<>(); final InsetsState state = new InsetsState(mState, true /* copySources */); for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { RunningAnimation runningAnimation = mRunningAnimations.get(i); if (DEBUG) Log.d(TAG, "Running animation type: " + runningAnimation.type); - InsetsAnimationControlRunner runner = runningAnimation.runner; - if (runner instanceof InsetsAnimationControlImpl) { - InsetsAnimationControlImpl control = (InsetsAnimationControlImpl) runner; + final InsetsAnimationControlRunner runner = runningAnimation.runner; + if (runner instanceof WindowInsetsAnimationController) { // Keep track of running animation to be dispatched. Aggregate it here such that // if it gets finished within applyChangeInsets we still dispatch it to // onProgress. if (runningAnimation.startDispatched) { - runningAnimations.add(control.getAnimation()); + runningAnimations.add(runner.getAnimation()); } - if (control.applyChangeInsets(state)) { - mTmpFinishedControls.add(control); + if (((InternalInsetsAnimationController) runner).applyChangeInsets(state)) { + finishedAnimations.add(runner.getAnimation()); } } } @@ -641,10 +651,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) { - dispatchAnimationEnd(mTmpFinishedControls.get(i).getAnimation()); + for (int i = finishedAnimations.size() - 1; i >= 0; i--) { + dispatchAnimationEnd(finishedAnimations.get(i)); } - mTmpFinishedControls.clear(); }; } @@ -690,15 +699,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation true /* excludeInvisibleIme */)) { if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged"); mHost.notifyInsetsChanged(); + startResizingAnimationIfNeeded(lastState); } return true; } private void updateState(InsetsState newState) { - mState.setDisplayFrame(newState.getDisplayFrame()); - mState.setDisplayCutout(newState.getDisplayCutout()); - mState.setRoundedCorners(newState.getRoundedCorners()); - mState.setPrivacyIndicatorBounds(newState.getPrivacyIndicatorBounds()); + mState.set(newState, 0 /* types */); @InsetsType int disabledUserAnimationTypes = 0; @InsetsType int[] cancelledUserAnimationTypes = {0}; for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { @@ -763,6 +770,39 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return false; } + private void startResizingAnimationIfNeeded(InsetsState fromState) { + if (!fromState.getDisplayFrame().equals(mState.getDisplayFrame())) { + return; + } + @InsetsType int types = 0; + InsetsState toState = null; + final ArraySet internalTypes = InsetsState.toInternalType(Type.systemBars()); + for (int i = internalTypes.size() - 1; i >= 0; i--) { + final @InternalInsetsType int type = internalTypes.valueAt(i); + final InsetsSource fromSource = fromState.peekSource(type); + final InsetsSource toSource = mState.peekSource(type); + if (fromSource != null && toSource != null + && fromSource.isVisible() && toSource.isVisible() + && !fromSource.getFrame().equals(toSource.getFrame()) + && (Rect.intersects(mFrame, fromSource.getFrame()) + || Rect.intersects(mFrame, toSource.getFrame()))) { + types |= InsetsState.toPublicType(toSource.getType()); + if (toState == null) { + toState = new InsetsState(); + } + toState.addSource(new InsetsSource(toSource)); + } + } + if (types == 0) { + return; + } + cancelExistingControllers(types); + final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner( + mFrame, fromState, toState, RESIZE_INTERPOLATOR, ANIMATION_DURATION_RESIZE, types, + this); + mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType())); + } + /** * @see InsetsState#calculateInsets */ @@ -784,7 +824,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** * @see InsetsState#calculateVisibleInsets(Rect, int) */ - public Rect calculateVisibleInsets(@SoftInputModeFlags int softInputMode) { + public Insets calculateVisibleInsets(@SoftInputModeFlags int softInputMode) { return mState.calculateVisibleInsets(mFrame, softInputMode); } @@ -801,7 +841,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - boolean requestedStateStale = false; + boolean requestedVisibilityStale = false; final int[] showTypes = new int[1]; final int[] hideTypes = new int[1]; @@ -822,20 +862,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final InsetsSourceConsumer consumer = getSourceConsumer(type); consumer.setControl(control, showTypes, hideTypes); - if (!requestedStateStale) { + if (!requestedVisibilityStale) { final boolean requestedVisible = consumer.isRequestedVisible(); // We might have changed our requested visibilities while we don't have the control, // so we need to update our requested state once we have control. Otherwise, our // requested state at the server side might be incorrect. final boolean requestedVisibilityChanged = - requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type); + requestedVisible != mRequestedVisibilities.getVisibility(type); // The IME client visibility will be reset by insets source provider while updating // control, so if IME is requested visible, we need to send the request to server. final boolean imeRequestedVisible = type == ITYPE_IME && requestedVisible; - requestedStateStale = requestedVisibilityChanged || imeRequestedVisible; + requestedVisibilityStale = requestedVisibilityChanged || imeRequestedVisible; } } @@ -861,7 +901,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } // InsetsSourceConsumer#setControl might change the requested visibility. - updateRequestedVisibility(); + updateRequestedVisibilities(); } @Override @@ -1015,7 +1055,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (types == 0) { // nothing to animate. listener.onCancelled(null); - updateRequestedVisibility(); + updateRequestedVisibilities(); if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked"); return; } @@ -1051,7 +1091,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } }); } - updateRequestedVisibility(); + updateRequestedVisibilities(); Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0); return; } @@ -1059,7 +1099,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (typesReady == 0) { if (DEBUG) Log.d(TAG, "No types ready. onCancelled()"); listener.onCancelled(null); - updateRequestedVisibility(); + updateRequestedVisibilities(); return; } @@ -1091,7 +1131,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } else { hideDirectly(types, false /* animationFinished */, animationType, fromIme); } - updateRequestedVisibility(); + updateRequestedVisibilities(); } /** @@ -1226,6 +1266,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) { cancelAnimation(runner, false /* invokeCallback */); if (DEBUG) Log.d(TAG, "notifyFinished. shown: " + shown); + if (runner.getAnimationType() == ANIMATION_TYPE_RESIZE) { + // The resize animation doesn't show or hide the insets. We shouldn't change the + // requested visibility. + return; + } if (shown) { showDirectly(runner.getTypes(), true /* fromIme */); } else { @@ -1348,7 +1393,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** * Sends the requested visibilities to window manager if any of them is changed. */ - private void updateRequestedVisibility() { + private void updateRequestedVisibilities() { boolean changed = false; for (int i = mRequestedVisibilityChanged.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mRequestedVisibilityChanged.valueAt(i); @@ -1357,8 +1402,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation continue; } final boolean requestedVisible = consumer.isRequestedVisible(); - if (requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type)) { - mRequestedState.getSource(type).setVisible(requestedVisible); + if (mRequestedVisibilities.getVisibility(type) != requestedVisible) { + mRequestedVisibilities.setVisibility(type, requestedVisible); changed = true; } } @@ -1366,11 +1411,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (!changed) { return; } - mHost.onInsetsModified(mRequestedState); + mHost.updateRequestedVisibilities(mRequestedVisibilities); } - InsetsState getRequestedVisibility() { - return mRequestedState; + InsetsVisibilities getRequestedVisibilities() { + return mRequestedVisibilities; } @VisibleForTesting @@ -1425,7 +1470,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation for (int i = internalTypes.size() - 1; i >= 0; i--) { getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType); } - updateRequestedVisibility(); + updateRequestedVisibilities(); if (fromIme) { Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0); @@ -1441,7 +1486,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation for (int i = internalTypes.size() - 1; i >= 0; i--) { getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */); } - updateRequestedVisibility(); + updateRequestedVisibilities(); if (fromIme) { Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0); @@ -1473,12 +1518,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting @Override - public void startAnimation(InsetsAnimationControlImpl controller, - WindowInsetsAnimationControlListener listener, int types, + public + void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types, WindowInsetsAnimation animation, Bounds bounds) { mHost.dispatchWindowInsetsAnimationPrepare(animation); mHost.addOnPreDrawRunnable(() -> { - if (controller.isCancelled()) { + if (runner.isCancelled()) { if (WARN) Log.w(TAG, "startAnimation canceled before preDraw"); return; } @@ -1486,15 +1531,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation "InsetsAnimation: " + WindowInsets.Type.toString(types), types); for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { RunningAnimation runningAnimation = mRunningAnimations.get(i); - if (runningAnimation.runner == controller) { + if (runningAnimation.runner == runner) { runningAnimation.startDispatched = true; } } Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0); mHost.dispatchWindowInsetsAnimationStart(animation, bounds); mStartingAnimation = true; - controller.mReadyDispatched = true; - listener.onReady(controller, types); + runner.setReadyDispatched(true); + listener.onReady(runner, types); mStartingAnimation = false; }); } diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java new file mode 100644 index 0000000000000000000000000000000000000000..edcfc95fe4e43584d3553973b01b99d1bb252696 --- /dev/null +++ b/core/java/android/view/InsetsResizeAnimationRunner.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static android.view.InsetsAnimationControlImplProto.CURRENT_ALPHA; +import static android.view.InsetsAnimationControlImplProto.IS_CANCELLED; +import static android.view.InsetsAnimationControlImplProto.IS_FINISHED; +import static android.view.InsetsAnimationControlImplProto.PENDING_ALPHA; +import static android.view.InsetsAnimationControlImplProto.PENDING_FRACTION; +import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS; +import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH; +import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX; +import static android.view.InsetsController.ANIMATION_TYPE_RESIZE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.graphics.Insets; +import android.graphics.Rect; +import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; +import android.view.InsetsState.InternalInsetsType; +import android.view.WindowInsets.Type.InsetsType; +import android.view.WindowInsetsAnimation.Bounds; +import android.view.animation.Interpolator; + +/** + * Runs a fake animation of resizing insets to produce insets animation callbacks. + * @hide + */ +public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner, + InternalInsetsAnimationController, WindowInsetsAnimationControlListener { + + private final InsetsState mFromState; + private final InsetsState mToState; + private final @InsetsType int mTypes; + private final WindowInsetsAnimation mAnimation; + private final InsetsAnimationControlCallbacks mController; + private ValueAnimator mAnimator; + private boolean mCancelled; + private boolean mFinished; + + public InsetsResizeAnimationRunner(Rect frame, InsetsState fromState, InsetsState toState, + Interpolator interpolator, long duration, @InsetsType int types, + InsetsAnimationControlCallbacks controller) { + mFromState = fromState; + mToState = toState; + mTypes = types; + mController = controller; + mAnimation = new WindowInsetsAnimation(types, interpolator, duration); + mAnimation.setAlpha(1f); + final Insets fromInsets = fromState.calculateInsets( + frame, types, false /* ignoreVisibility */); + final Insets toInsets = toState.calculateInsets( + frame, types, false /* ignoreVisibility */); + controller.startAnimation(this, this, types, mAnimation, + new Bounds(Insets.min(fromInsets, toInsets), Insets.max(fromInsets, toInsets))); + } + + @Override + public int getTypes() { + return mTypes; + } + + @Override + public int getControllingTypes() { + return mTypes; + } + + @Override + public WindowInsetsAnimation getAnimation() { + return mAnimation; + } + + @Override + public int getAnimationType() { + return ANIMATION_TYPE_RESIZE; + } + + @Override + public void cancel() { + if (mCancelled || mFinished) { + return; + } + mCancelled = true; + if (mAnimator != null) { + mAnimator.cancel(); + } + } + + @Override + public boolean isCancelled() { + return mCancelled; + } + + @Override + public void onReady(WindowInsetsAnimationController controller, int types) { + if (mCancelled) { + return; + } + mAnimator = ValueAnimator.ofFloat(0f, 1f); + mAnimator.setDuration(mAnimation.getDurationMillis()); + mAnimator.addUpdateListener(animation -> { + mAnimation.setFraction(animation.getAnimatedFraction()); + mController.scheduleApplyChangeInsets(InsetsResizeAnimationRunner.this); + }); + mAnimator.addListener(new AnimatorListenerAdapter() { + + @Override + public void onAnimationEnd(Animator animation) { + mFinished = true; + mController.scheduleApplyChangeInsets(InsetsResizeAnimationRunner.this); + } + }); + mAnimator.start(); + } + + @Override + public boolean applyChangeInsets(InsetsState outState) { + if (mCancelled) { + return false; + } + final float fraction = mAnimation.getInterpolatedFraction(); + for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { + final InsetsSource fromSource = mFromState.peekSource(type); + final InsetsSource toSource = mToState.peekSource(type); + if (fromSource == null || toSource == null) { + continue; + } + final Rect fromFrame = fromSource.getFrame(); + final Rect toFrame = toSource.getFrame(); + final Rect frame = new Rect( + (int) (fromFrame.left + fraction * (toFrame.left - fromFrame.left)), + (int) (fromFrame.top + fraction * (toFrame.top - fromFrame.top)), + (int) (fromFrame.right + fraction * (toFrame.right - fromFrame.right)), + (int) (fromFrame.bottom + fraction * (toFrame.bottom - fromFrame.bottom))); + final InsetsSource source = new InsetsSource(type); + source.setFrame(frame); + source.setVisible(toSource.isVisible()); + outState.addSource(source); + } + if (mFinished) { + mController.notifyFinished(this, true /* shown */); + } + return mFinished; + } + + @Override + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(IS_CANCELLED, mCancelled); + proto.write(IS_FINISHED, mFinished); + proto.write(TMP_MATRIX, "null"); + proto.write(PENDING_INSETS, "null"); + proto.write(PENDING_FRACTION, mAnimation.getInterpolatedFraction()); + proto.write(SHOWN_ON_FINISH, true); + proto.write(CURRENT_ALPHA, 1f); + proto.write(PENDING_ALPHA, 1f); + proto.end(token); + } + + @Override + public Insets getHiddenStateInsets() { + return Insets.NONE; + } + + @Override + public Insets getShownStateInsets() { + return Insets.NONE; + } + + @Override + public Insets getCurrentInsets() { + return Insets.NONE; + } + + @Override + public float getCurrentFraction() { + return 0; + } + + @Override + public float getCurrentAlpha() { + return 0; + } + + @Override + public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) { + } + + @Override + public void finish(boolean shown) { + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public void notifyControlRevoked(int types) { + } + + @Override + public void updateSurfacePosition(SparseArray controls) { + } + + @Override + public boolean hasZeroInsetsIme() { + return false; + } + + @Override + public void setReadyDispatched(boolean dispatched) { + } + + @Override + public void onFinished(WindowInsetsAnimationController controller) { + } + + @Override + public void onCancelled(WindowInsetsAnimationController controller) { + } +} diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 1506ee4c2c7a40661da4862339b51472e5508bdc..9d98a3e4b0e19449a460c0244a029495c47c5f88 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -185,6 +185,15 @@ public class InsetsSourceControl implements Parcelable { return result; } + @Override + public String toString() { + return "InsetsSourceControl: {" + + "type=" + InsetsState.typeToString(mType) + + ", mSurfacePosition=" + mSurfacePosition + + ", mInsetsHint=" + mInsetsHint + + "}"; + } + public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("InsetsSourceControl type="); pw.print(InsetsState.typeToString(mType)); diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 37101b757e66ef22fcdb32cb11b43fbe9b73efa9..75b69cb12d32eb6975fd766a2cae9de91cf4fefa 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -301,7 +301,7 @@ public class InsetsState implements Parcelable { return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom); } - public Rect calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) { + public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) { Insets insets = Insets.NONE; for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { InsetsSource source = mSources[type]; @@ -314,10 +314,10 @@ public class InsetsState implements Parcelable { } insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets); } - return insets.toRect(); + return insets; } - public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) { + public Insets calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) { Insets insets = Insets.NONE; for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { InsetsSource source = mSources[type]; @@ -332,7 +332,7 @@ public class InsetsState implements Parcelable { } insets = Insets.max(source.calculateVisibleInsets(frame), insets); } - return insets.toRect(); + return insets; } /** @@ -878,16 +878,5 @@ public class InsetsState implements Parcelable { + ", mSources= { " + joiner + " }"; } - - public @NonNull String toSourceVisibilityString() { - StringJoiner joiner = new StringJoiner(", "); - for (int i = 0; i < SIZE; i++) { - InsetsSource source = mSources[i]; - if (source != null) { - joiner.add(typeToString(i) + ": " + (source.isVisible() ? "visible" : "invisible")); - } - } - return joiner.toString(); - } } diff --git a/packages/SystemUI/res/values/arrays_tv.xml b/core/java/android/view/InsetsVisibilities.aidl similarity index 70% rename from packages/SystemUI/res/values/arrays_tv.xml rename to core/java/android/view/InsetsVisibilities.aidl index 9c7707791180f0f9ce6501a2240ea723753ab5e0..bd573ea7bc35772674fae878a2fa11f1c0113517 100644 --- a/packages/SystemUI/res/values/arrays_tv.xml +++ b/core/java/android/view/InsetsVisibilities.aidl @@ -1,7 +1,5 @@ - - - - - - + +package android.view; + +parcelable InsetsVisibilities; diff --git a/core/java/android/view/InsetsVisibilities.java b/core/java/android/view/InsetsVisibilities.java new file mode 100644 index 0000000000000000000000000000000000000000..7d259fb9163430cba1ed996dca82a5d6bb71d22c --- /dev/null +++ b/core/java/android/view/InsetsVisibilities.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.StringJoiner; + +/** + * A collection of visibilities of insets. This is used for carrying the requested visibilities. + * @hide + */ +public class InsetsVisibilities implements Parcelable { + + private static final int UNSPECIFIED = 0; + private static final int VISIBLE = 1; + private static final int INVISIBLE = -1; + + private final int[] mVisibilities = new int[InsetsState.SIZE]; + + public InsetsVisibilities() { + } + + public InsetsVisibilities(InsetsVisibilities other) { + set(other); + } + + public InsetsVisibilities(Parcel in) { + in.readIntArray(mVisibilities); + } + + /** + * Copies from another {@link InsetsVisibilities}. + * + * @param other an instance of {@link InsetsVisibilities}. + */ + public void set(InsetsVisibilities other) { + System.arraycopy(other.mVisibilities, InsetsState.FIRST_TYPE, mVisibilities, + InsetsState.FIRST_TYPE, InsetsState.SIZE); + } + + /** + * Sets a visibility to a type. + * + * @param type The {@link @InsetsState.InternalInsetsType}. + * @param visible {@code true} represents visible; {@code false} represents invisible. + */ + public void setVisibility(@InsetsState.InternalInsetsType int type, boolean visible) { + mVisibilities[type] = visible ? VISIBLE : INVISIBLE; + } + + /** + * Returns the specified insets visibility of the type. If it has never been specified, + * this returns the default visibility. + * + * @param type The {@link @InsetsState.InternalInsetsType}. + * @return The specified visibility or the default one if it is not specified. + */ + public boolean getVisibility(@InsetsState.InternalInsetsType int type) { + final int visibility = mVisibilities[type]; + return visibility == UNSPECIFIED + ? InsetsState.getDefaultVisibility(type) + : visibility == VISIBLE; + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(", "); + for (int type = InsetsState.FIRST_TYPE; type <= InsetsState.LAST_TYPE; type++) { + final int visibility = mVisibilities[type]; + if (visibility != UNSPECIFIED) { + joiner.add(InsetsState.typeToString(type) + ": " + + (visibility == VISIBLE ? "visible" : "invisible")); + } + } + return joiner.toString(); + } + + @Override + public int hashCode() { + return Arrays.hashCode(mVisibilities); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof InsetsVisibilities)) { + return false; + } + return Arrays.equals(mVisibilities, ((InsetsVisibilities) other).mVisibilities); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeIntArray(mVisibilities); + } + + public void readFromParcel(@NonNull Parcel in) { + in.readIntArray(mVisibilities); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + + public InsetsVisibilities createFromParcel(Parcel in) { + return new InsetsVisibilities(in); + } + + public InsetsVisibilities[] newArray(int size) { + return new InsetsVisibilities[size]; + } + }; +} diff --git a/core/java/android/view/InternalInsetsAnimationController.java b/core/java/android/view/InternalInsetsAnimationController.java new file mode 100644 index 0000000000000000000000000000000000000000..d7f3e20b80c5412355f175319fe441f99372060b --- /dev/null +++ b/core/java/android/view/InternalInsetsAnimationController.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +/** + * An internal interface which provides methods that will be only used by the framework. + * @hide + */ +public interface InternalInsetsAnimationController extends WindowInsetsAnimationController { + + /** + * Flags whether {@link WindowInsetsAnimationControlListener#onReady( + * WindowInsetsAnimationController, int)} has been invoked. + * @hide + */ + void setReadyDispatched(boolean dispatched); + + /** + * Returns the {@link InsetsState} based on the current animation progress. + * + * @param outState the insets state which matches the current animation progress. + * @return {@code true} if the animation has been finished; {@code false} otherwise. + * @hide + */ + boolean applyChangeInsets(InsetsState outState); +} + diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 69ff64f3d6a581929c429dde8f6dc3107bab321f..80ffd40b564effe025237242a63abd671db61f0a 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -1860,7 +1860,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { float x, float y, float pressure, float size, int metaState, float xPrecision, float yPrecision, int deviceId, int edgeFlags) { return obtain(downTime, eventTime, action, x, y, pressure, size, metaState, - xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_UNKNOWN, + xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_CLASS_POINTER, DEFAULT_DISPLAY); } @@ -4007,6 +4007,22 @@ public final class MotionEvent extends InputEvent implements Parcelable { */ public float orientation; + /** + * The movement of x position of a motion event. + * + * @see MotionEvent#AXIS_RELATIVE_X + * @hide + */ + public float relativeX; + + /** + * The movement of y position of a motion event. + * + * @see MotionEvent#AXIS_RELATIVE_Y + * @hide + */ + public float relativeY; + /** * Clears the contents of this object. * Resets all axes to zero. @@ -4023,6 +4039,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { toolMajor = 0; toolMinor = 0; orientation = 0; + relativeX = 0; + relativeY = 0; } /** @@ -4053,6 +4071,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { toolMajor = other.toolMajor; toolMinor = other.toolMinor; orientation = other.orientation; + relativeX = other.relativeX; + relativeY = other.relativeY; } /** @@ -4084,6 +4104,10 @@ public final class MotionEvent extends InputEvent implements Parcelable { return toolMinor; case AXIS_ORIENTATION: return orientation; + case AXIS_RELATIVE_X: + return relativeX; + case AXIS_RELATIVE_Y: + return relativeY; default: { if (axis < 0 || axis > 63) { throw new IllegalArgumentException("Axis out of range."); @@ -4137,6 +4161,12 @@ public final class MotionEvent extends InputEvent implements Parcelable { case AXIS_ORIENTATION: orientation = value; break; + case AXIS_RELATIVE_X: + relativeX = value; + break; + case AXIS_RELATIVE_Y: + relativeY = value; + break; default: { if (axis < 0 || axis > 63) { throw new IllegalArgumentException("Axis out of range."); diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java index a78036fba0940bdaa5d661bf136af6155738e9de..e1cc60491f72ddfb6eec2b118523298d9a34b1ec 100644 --- a/core/java/android/view/RemoteAnimationAdapter.java +++ b/core/java/android/view/RemoteAnimationAdapter.java @@ -17,6 +17,7 @@ package android.view; import android.app.ActivityOptions; +import android.app.IApplicationThread; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -58,6 +59,9 @@ public class RemoteAnimationAdapter implements Parcelable { private int mCallingPid; private int mCallingUid; + /** @see #getCallingApplication */ + private IApplicationThread mCallingApplication; + /** * @param runner The interface that gets notified when we actually need to start the animation. * @param duration The duration of the animation. @@ -81,11 +85,19 @@ public class RemoteAnimationAdapter implements Parcelable { this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */); } + @UnsupportedAppUsage + public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration, + long statusBarTransitionDelay, IApplicationThread callingApplication) { + this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */); + mCallingApplication = callingApplication; + } + public RemoteAnimationAdapter(Parcel in) { mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder()); mDuration = in.readLong(); mStatusBarTransitionDelay = in.readLong(); mChangeNeedsSnapshot = in.readBoolean(); + mCallingApplication = IApplicationThread.Stub.asInterface(in.readStrongBinder()); } public IRemoteAnimationRunner getRunner() { @@ -126,6 +138,15 @@ public class RemoteAnimationAdapter implements Parcelable { return mCallingUid; } + /** + * Gets the ApplicationThread that will run the animation. Instead it is intended to pass the + * calling information among client processes (eg. shell + launcher) through one-way binder + * calls (where binder itself doesn't track calling information). + */ + public IApplicationThread getCallingApplication() { + return mCallingApplication; + } + @Override public int describeContents() { return 0; @@ -137,6 +158,7 @@ public class RemoteAnimationAdapter implements Parcelable { dest.writeLong(mDuration); dest.writeLong(mStatusBarTransitionDelay); dest.writeBoolean(mChangeNeedsSnapshot); + dest.writeStrongInterface(mCallingApplication); } public static final @android.annotation.NonNull Creator CREATOR diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index cdc099b8e2eaf0c6c014bf6538c88abb29bde173..2dac81c66d2a6a385ec6eeb6d1e3fbac88b00948 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -129,7 +129,11 @@ public class RemoteAnimationTarget implements Parcelable { * The index of the element in the tree in prefix order. This should be used for z-layering * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to * happen. + * @deprecated WindowManager may set a z-order different from the prefix order, and has set the + * correct layer for the animation leash already, so this should not be used for + * layer any more. */ + @Deprecated @UnsupportedAppUsage public final int prefixOrderIndex; @@ -196,20 +200,36 @@ public class RemoteAnimationTarget implements Parcelable { */ public ActivityManager.RunningTaskInfo taskInfo; + /** + * {@code true} if picture-in-picture permission is granted in {@link android.app.AppOpsManager} + */ + @UnsupportedAppUsage + public boolean allowEnterPip; + /** * The {@link android.view.WindowManager.LayoutParams.WindowType} of this window. It's only used * for non-app window. */ public final @WindowManager.LayoutParams.WindowType int windowType; + /** + * {@code true} if its parent is also a {@link RemoteAnimationTarget} in the same transition. + * + * For example, when a TaskFragment is resizing while one of its children is open/close, both + * windows will be animation targets. This value will be {@code true} for the child, so that + * the handler can choose to handle it differently. + */ + public boolean hasAnimatingParent; + public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position, Rect localBounds, Rect screenSpaceBounds, WindowConfiguration windowConfig, boolean isNotInRecents, - SurfaceControl startLeash, Rect startBounds, ActivityManager.RunningTaskInfo taskInfo) { + SurfaceControl startLeash, Rect startBounds, ActivityManager.RunningTaskInfo taskInfo, + boolean allowEnterPip) { this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash, - startBounds, taskInfo, INVALID_WINDOW_TYPE); + startBounds, taskInfo, allowEnterPip, INVALID_WINDOW_TYPE); } public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, @@ -217,7 +237,7 @@ public class RemoteAnimationTarget implements Parcelable { Rect localBounds, Rect screenSpaceBounds, WindowConfiguration windowConfig, boolean isNotInRecents, SurfaceControl startLeash, Rect startBounds, - ActivityManager.RunningTaskInfo taskInfo, + ActivityManager.RunningTaskInfo taskInfo, boolean allowEnterPip, @WindowManager.LayoutParams.WindowType int windowType) { this.mode = mode; this.taskId = taskId; @@ -226,7 +246,7 @@ public class RemoteAnimationTarget implements Parcelable { this.clipRect = new Rect(clipRect); this.contentInsets = new Rect(contentInsets); this.prefixOrderIndex = prefixOrderIndex; - this.position = new Point(position); + this.position = position == null ? new Point() : new Point(position); this.localBounds = new Rect(localBounds); this.sourceContainerBounds = new Rect(screenSpaceBounds); this.screenSpaceBounds = new Rect(screenSpaceBounds); @@ -235,6 +255,7 @@ public class RemoteAnimationTarget implements Parcelable { this.startLeash = startLeash; this.startBounds = startBounds == null ? null : new Rect(startBounds); this.taskInfo = taskInfo; + this.allowEnterPip = allowEnterPip; this.windowType = windowType; } @@ -255,7 +276,9 @@ public class RemoteAnimationTarget implements Parcelable { startLeash = in.readTypedObject(SurfaceControl.CREATOR); startBounds = in.readTypedObject(Rect.CREATOR); taskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); + allowEnterPip = in.readBoolean(); windowType = in.readInt(); + hasAnimatingParent = in.readBoolean(); } @Override @@ -281,7 +304,9 @@ public class RemoteAnimationTarget implements Parcelable { dest.writeTypedObject(startLeash, 0 /* flags */); dest.writeTypedObject(startBounds, 0 /* flags */); dest.writeTypedObject(taskInfo, 0 /* flags */); + dest.writeBoolean(allowEnterPip); dest.writeInt(windowType); + dest.writeBoolean(hasAnimatingParent); } public void dump(PrintWriter pw, String prefix) { @@ -299,7 +324,9 @@ public class RemoteAnimationTarget implements Parcelable { pw.print(prefix); pw.print("windowConfiguration="); pw.println(windowConfiguration); pw.print(prefix); pw.print("leash="); pw.println(leash); pw.print(prefix); pw.print("taskInfo="); pw.println(taskInfo); + pw.print(prefix); pw.print("allowEnterPip="); pw.println(allowEnterPip); pw.print(prefix); pw.print("windowType="); pw.print(windowType); + pw.print(prefix); pw.print("hasAnimatingParent="); pw.print(hasAnimatingParent); } public void dumpDebug(ProtoOutputStream proto, long fieldId) { diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java index 623d9692ac8067d75201f447a5a937ccba8badf9..6079d8e3f11894924e2cd0d9d6327d3b97f01abe 100644 --- a/core/java/android/view/RoundedCorners.java +++ b/core/java/android/view/RoundedCorners.java @@ -27,9 +27,11 @@ import static android.view.Surface.ROTATION_90; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Point; import android.os.Parcel; import android.os.Parcelable; +import android.util.DisplayUtils; import android.util.Pair; import android.view.RoundedCorner.Position; @@ -94,8 +96,8 @@ public class RoundedCorners implements Parcelable { * @android:dimen/rounded_corner_radius_top and @android:dimen/rounded_corner_radius_bottom */ public static RoundedCorners fromResources( - Resources res, int displayWidth, int displayHeight) { - return fromRadii(loadRoundedCornerRadii(res), displayWidth, displayHeight); + Resources res, String displayUniqueId, int displayWidth, int displayHeight) { + return fromRadii(loadRoundedCornerRadii(res, displayUniqueId), displayWidth, displayHeight); } /** @@ -140,14 +142,16 @@ public class RoundedCorners implements Parcelable { * Loads the rounded corner radii from resources. * * @param res + * @param displayUniqueId the display unique id. * @return a Pair of radius. The first is the top rounded corner radius and second is the * bottom corner radius. */ @Nullable - private static Pair loadRoundedCornerRadii(Resources res) { - final int radiusDefault = res.getDimensionPixelSize(R.dimen.rounded_corner_radius); - final int radiusTop = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top); - final int radiusBottom = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom); + private static Pair loadRoundedCornerRadii( + Resources res, String displayUniqueId) { + final int radiusDefault = getRoundedCornerRadius(res, displayUniqueId); + final int radiusTop = getRoundedCornerTopRadius(res, displayUniqueId); + final int radiusBottom = getRoundedCornerBottomRadius(res, displayUniqueId); if (radiusDefault == 0 && radiusTop == 0 && radiusBottom == 0) { return null; } @@ -157,6 +161,164 @@ public class RoundedCorners implements Parcelable { return radii; } + /** + * Gets the default rounded corner radius of a display which is determined by the + * given display unique id. + * + * Loads the default dimen{@link R.dimen#rounded_corner_radius} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static int getRoundedCornerRadius(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray(R.array.config_roundedCornerRadiusArray); + int radius; + if (index >= 0 && index < array.length()) { + radius = array.getDimensionPixelSize(index, 0); + } else { + radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius); + } + array.recycle(); + return radius; + } + + /** + * Gets the top rounded corner radius of a display which is determined by the + * given display unique id. + * + * Loads the default dimen{@link R.dimen#rounded_corner_radius_top} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static int getRoundedCornerTopRadius(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray); + int radius; + if (index >= 0 && index < array.length()) { + radius = array.getDimensionPixelSize(index, 0); + } else { + radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top); + } + array.recycle(); + return radius; + } + + /** + * Gets the bottom rounded corner radius of a display which is determined by the + * given display unique id. + * + * Loads the default dimen{@link R.dimen#rounded_corner_radius_bottom} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static int getRoundedCornerBottomRadius(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray( + R.array.config_roundedCornerBottomRadiusArray); + int radius; + if (index >= 0 && index < array.length()) { + radius = array.getDimensionPixelSize(index, 0); + } else { + radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom); + } + array.recycle(); + return radius; + } + + /** + * Gets the rounded corner radius adjustment of a display which is determined by the + * given display unique id. + * + * Loads the default dimen{@link R.dimen#rounded_corner_radius_adjustment} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static int getRoundedCornerRadiusAdjustment(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray( + R.array.config_roundedCornerRadiusAdjustmentArray); + int radius; + if (index >= 0 && index < array.length()) { + radius = array.getDimensionPixelSize(index, 0); + } else { + radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_adjustment); + } + array.recycle(); + return radius; + } + + /** + * Gets the rounded corner top radius adjustment of a display which is determined by the + * given display unique id. + * + * Loads the default dimen{@link R.dimen#rounded_corner_radius_top_adjustment} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static int getRoundedCornerRadiusTopAdjustment(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray( + R.array.config_roundedCornerTopRadiusAdjustmentArray); + int radius; + if (index >= 0 && index < array.length()) { + radius = array.getDimensionPixelSize(index, 0); + } else { + radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top_adjustment); + } + array.recycle(); + return radius; + } + + /** + * Gets the rounded corner bottom radius adjustment of a display which is determined by the + * given display unique id. + * + * Loads the default dimen{@link R.dimen#rounded_corner_radius_bottom_adjustment} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static int getRoundedCornerRadiusBottomAdjustment( + Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray( + R.array.config_roundedCornerBottomRadiusAdjustmentArray); + int radius; + if (index >= 0 && index < array.length()) { + radius = array.getDimensionPixelSize(index, 0); + } else { + radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom_adjustment); + } + array.recycle(); + return radius; + } + + /** + * Gets whether a built-in display is round. + * + * Loads the default config{@link R.bool#config_mainBuiltInDisplayIsRound} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static boolean getBuiltInDisplayIsRound(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray); + boolean isRound; + if (index >= 0 && index < array.length()) { + isRound = array.getBoolean(index, false); + } else { + isRound = res.getBoolean(R.bool.config_mainBuiltInDisplayIsRound); + } + array.recycle(); + return isRound; + } + /** * Insets the reference frame of the rounded corners. * diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index fa7330fb84eb858c00a31f5d6724828d6026b236..904aa73f6ac463b8553655711342b6129e67f9cb 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -29,6 +29,7 @@ import android.graphics.Canvas; import android.graphics.ColorSpace; import android.graphics.HardwareRenderer; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; @@ -411,6 +412,20 @@ public class Surface implements Parcelable { } } + /** + * Returns the default size of this Surface provided by the consumer of the surface. + * Should only be used by the producer of the surface. + * + * @hide + */ + @NonNull + public Point getDefaultSize() { + synchronized (mLock) { + checkNotReleasedLocked(); + return new Point(nativeGetWidth(mNativeObject), nativeGetHeight(mNativeObject)); + } + } + /** * Gets a {@link Canvas} for drawing into this surface. * diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index ffce4617eec62e122261d2338d238fe404740395..960d23d7afb09eb308e68ba816a6feadf2da8b00 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -23,6 +23,7 @@ import static android.graphics.Matrix.MSKEW_Y; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; import static android.view.SurfaceControlProto.HASH_CODE; +import static android.view.SurfaceControlProto.LAYER_ID; import static android.view.SurfaceControlProto.NAME; import android.annotation.FloatRange; @@ -41,6 +42,7 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.gui.DropInputMode; import android.hardware.HardwareBuffer; import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayedContentSample; @@ -150,7 +152,8 @@ public final class SurfaceControl implements Parcelable { float childRelativeTop, float childRelativeRight, float childRelativeBottom); private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject, boolean isTrustedOverlay); - + private static native void nativeSetDropInputMode( + long transactionObj, long nativeObject, int flags); private static native boolean nativeClearContentFrameStats(long nativeObject); private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats); private static native boolean nativeClearAnimationFrameStats(); @@ -165,6 +168,8 @@ public final class SurfaceControl implements Parcelable { IBinder displayToken, long nativeSurfaceObject); private static native void nativeSetDisplayLayerStack(long transactionObj, IBinder displayToken, int layerStack); + private static native void nativeSetDisplayFlags(long transactionObj, + IBinder displayToken, int flags); private static native void nativeSetDisplayProjection(long transactionObj, IBinder displayToken, int orientation, int l, int t, int r, int b, @@ -236,8 +241,80 @@ public final class SurfaceControl implements Parcelable { private static native void nativeRemoveJankDataListener(long nativeListener); private static native long nativeCreateJankDataListenerWrapper(OnJankDataListener listener); private static native int nativeGetGPUContextPriority(); - private static native void nativeSetTransformHint(long nativeObject, int transformHint); + private static native void nativeSetTransformHint(long nativeObject, + @SurfaceControl.BufferTransform int transformHint); private static native int nativeGetTransformHint(long nativeObject); + private static native int nativeGetLayerId(long nativeObject); + + /** + * Transforms that can be applied to buffers as they are displayed to a window. + * + * Supported transforms are any combination of horizontal mirror, vertical mirror, and + * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are made up + * of those basic transforms. + * Mirrors {@code ANativeWindowTransform} definitions. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"BUFFER_TRANSFORM_"}, + value = {BUFFER_TRANSFORM_IDENTITY, BUFFER_TRANSFORM_MIRROR_HORIZONTAL, + BUFFER_TRANSFORM_MIRROR_VERTICAL, BUFFER_TRANSFORM_ROTATE_90, + BUFFER_TRANSFORM_ROTATE_180, BUFFER_TRANSFORM_ROTATE_270, + BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_ROTATE_90, + BUFFER_TRANSFORM_MIRROR_VERTICAL | BUFFER_TRANSFORM_ROTATE_90}) + public @interface BufferTransform { + } + + /** + * Identity transform. + * + * These transforms that can be applied to buffers as they are displayed to a window. + * @see HardwareBuffer + * + * Supported transforms are any combination of horizontal mirror, vertical mirror, and + * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are + * made up of those basic transforms. + */ + public static final int BUFFER_TRANSFORM_IDENTITY = 0x00; + /** + * Mirror horizontally. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL} + * and {@link #BUFFER_TRANSFORM_ROTATE_90}. + */ + public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 0x01; + /** + * Mirror vertically. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_HORIZONTAL} + * and {@link #BUFFER_TRANSFORM_ROTATE_90}. + */ + public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 0x02; + /** + * Rotate 90 degrees clock-wise. Can be combined with {@link + * #BUFFER_TRANSFORM_MIRROR_HORIZONTAL} and {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL}. + */ + public static final int BUFFER_TRANSFORM_ROTATE_90 = 0x04; + /** + * Rotate 180 degrees clock-wise. Cannot be combined with other transforms. + */ + public static final int BUFFER_TRANSFORM_ROTATE_180 = + BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_MIRROR_VERTICAL; + /** + * Rotate 270 degrees clock-wise. Cannot be combined with other transforms. + */ + public static final int BUFFER_TRANSFORM_ROTATE_270 = + BUFFER_TRANSFORM_ROTATE_180 | BUFFER_TRANSFORM_ROTATE_90; + + /** + * @hide + */ + public static @BufferTransform int rotationToBufferTransform(@Surface.Rotation int rotation) { + switch (rotation) { + case Surface.ROTATION_0: return BUFFER_TRANSFORM_IDENTITY; + case Surface.ROTATION_90: return BUFFER_TRANSFORM_ROTATE_90; + case Surface.ROTATION_180: return BUFFER_TRANSFORM_ROTATE_180; + case Surface.ROTATION_270: return BUFFER_TRANSFORM_ROTATE_270; + } + Log.e(TAG, "Trying to convert unknown rotation=" + rotation); + return BUFFER_TRANSFORM_IDENTITY; + } @Nullable @GuardedBy("mLock") @@ -353,8 +430,6 @@ public final class SurfaceControl implements Parcelable { @GuardedBy("mLock") private int mHeight; - private int mTransformHint; - private WeakReference mLocalOwnerView; static GlobalTransactionWrapper sGlobalTransaction; @@ -551,6 +626,15 @@ public final class SurfaceControl implements Parcelable { */ private static final int SURFACE_OPAQUE = 0x02; + /* flags used with setDisplayFlags() (keep in sync with DisplayDevice.h) */ + + /** + * DisplayDevice flag: This display's transform is sent to inputflinger and used for input + * dispatch. This flag is used to disambiguate displays which share a layerstack. + * @hide + */ + public static final int DISPLAY_RECEIVES_INPUT = 0x01; + // Display power modes. /** * Display power mode off: used while blanking the screen. @@ -1528,6 +1612,7 @@ public final class SurfaceControl implements Parcelable { final long token = proto.start(fieldId); proto.write(HASH_CODE, System.identityHashCode(this)); proto.write(NAME, mName); + proto.write(LAYER_ID, getLayerId()); proto.end(token); } @@ -3176,6 +3261,17 @@ public final class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ + public Transaction setDisplayFlags(IBinder displayToken, int flags) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + nativeSetDisplayFlags(mNativeObject, displayToken, flags); + return this; + } + /** * @hide */ @@ -3436,7 +3532,18 @@ public final class SurfaceControl implements Parcelable { return this; } - /** + /** + * Sets the input event drop mode on this SurfaceControl and its children. The caller must + * hold the ACCESS_SURFACE_FLINGER permission. See {@code InputEventDropMode}. + * @hide + */ + public Transaction setDropInputMode(SurfaceControl sc, @DropInputMode int mode) { + checkPreconditions(sc); + nativeSetDropInputMode(mNativeObject, sc.mNativeObject, mode); + return this; + } + + /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. * @@ -3623,7 +3730,7 @@ public final class SurfaceControl implements Parcelable { /** * @hide */ - public int getTransformHint() { + public @SurfaceControl.BufferTransform int getTransformHint() { checkNotReleased(); return nativeGetTransformHint(mNativeObject); } @@ -3637,7 +3744,18 @@ public final class SurfaceControl implements Parcelable { * with the same size. * @hide */ - public void setTransformHint(@Surface.Rotation int transformHint) { + public void setTransformHint(@SurfaceControl.BufferTransform int transformHint) { nativeSetTransformHint(mNativeObject, transformHint); } + + /** + * @hide + */ + public int getLayerId() { + if (mNativeObject != 0) { + return nativeGetLayerId(mNativeObject); + } + + return -1; + } } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 11b161ad3cb264452f305d9f7392e51216762823..a6c5042db275bfbd73ececce6a44f8408502ff80 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -292,11 +292,18 @@ public class SurfaceControlViewHost { */ @TestApi public void relayout(WindowManager.LayoutParams attrs) { + relayout(attrs, SurfaceControl.Transaction::apply); + } + + /** + * Forces relayout and draw and allows to set a custom callback when it is finished + * @hide + */ + public void relayout(WindowManager.LayoutParams attrs, + WindowlessWindowManager.ResizeCompleteCallback callback) { mViewRoot.setLayoutParams(attrs, false); mViewRoot.setReportNextDraw(); - mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), (SurfaceControl.Transaction t) -> { - t.apply(); - }); + mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback); } /** diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 4b8b607de08959f48432b8b0ef55f5663f472074..0e49cc9676567247b9a068669b68f332fa3d4a05 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -138,20 +138,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private boolean mDisableBackgroundLayer = false; /** - * We use this lock in SOME cases when reading or writing SurfaceControl, - * but use the following model so that the RenderThread can run locklessly - * in the position up-date case. - * - * 1. UI Thread can read from mSurfaceControl (use in Transactions) without - * holding the lock. - * 2. UI Thread will hold the lock when writing to mSurfaceControl (calling release - * or remove). - * 3. Render thread will also hold the lock when writing to mSurfaceControl (e.g. - * calling release from positionLost). - * 3. RenderNode.PositionUpdateListener::positionChanged will only be called - * when the UI thread is paused (blocked on the Render thread). - * 4. positionChanged thus will not be required to hold the lock as the - * UI thread is blocked, and the other writer is the RT itself. + * We use this lock to protect access to mSurfaceControl and + * SurfaceViewPositionUpdateListener#mPositionChangedTransaction. Both are accessed on the UI + * thread and the render thread. */ final Object mSurfaceControlLock = new Object(); final Rect mTmpRect = new Rect(); @@ -162,8 +151,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) boolean mIsCreating = false; - private volatile boolean mRtHandlingPositionUpdates = false; - private volatile boolean mRtReleaseSurfaces = false; private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener = this::updateSurface; @@ -214,7 +201,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) final Rect mSurfaceFrame = new Rect(); int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; - int mTransformHint = 0; + @SurfaceControl.BufferTransform int mTransformHint = 0; private boolean mGlobalListenersAdded; private boolean mAttachedToWindow; @@ -909,13 +896,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mBlastBufferQueue = null; } - if (mRtHandlingPositionUpdates) { - mRtReleaseSurfaces = true; - return; + ViewRootImpl viewRoot = getViewRootImpl(); + Transaction transaction = new Transaction(); + releaseSurfaces(transaction); + if (viewRoot != null) { + viewRoot.applyTransactionOnDraw(transaction); + } else { + transaction.apply(); } - - releaseSurfaces(mTmpTransaction); - mTmpTransaction.apply(); } } @@ -943,9 +931,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // When the listener is updated, we will get at least a single position update call so we can // guarantee any changes we post will be applied. private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight, - @Nullable Transaction geometryTransaction) { + Transaction geometryTransaction) { if (mPositionListener != null) { mRenderNode.removePositionUpdateListener(mPositionListener); + synchronized (mSurfaceControlLock) { + geometryTransaction = mPositionListener.getTransaction().merge(geometryTransaction); + } } mPositionListener = new SurfaceViewPositionUpdateListener(surfaceWidth, surfaceHeight, geometryTransaction); @@ -953,7 +944,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator, - boolean creating, boolean sizeChanged, boolean hintChanged) { + boolean creating, boolean sizeChanged, boolean hintChanged, + Transaction geometryTransaction) { boolean realSizeChanged = false; mSurfaceLock.lock(); @@ -974,9 +966,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId(); if (mViewVisibility) { - mTmpTransaction.show(mSurfaceControl); + geometryTransaction.show(mSurfaceControl); } else { - mTmpTransaction.hide(mSurfaceControl); + geometryTransaction.hide(mSurfaceControl); } if (mSurfacePackage != null) { @@ -991,10 +983,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfaceAlpha = alpha; } - // While creating the surface, we will set it's initial - // geometry. Outside of that though, we should generally - // leave it to the RenderThread. - Transaction geometryTransaction = new Transaction(); geometryTransaction.setCornerRadius(mSurfaceControl, mCornerRadius); if ((sizeChanged || hintChanged) && !creating) { setBufferSize(geometryTransaction); @@ -1017,20 +1005,18 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfaceHeight); } - boolean applyChangesOnRenderThread = - sizeChanged && !creating && isHardwareAccelerated(); if (isHardwareAccelerated()) { // This will consume the passed in transaction and the transaction will be // applied on a render worker thread. replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight, - applyChangesOnRenderThread ? geometryTransaction : null); + geometryTransaction); } if (DEBUG_POSITION) { Log.d(TAG, String.format( - "%d updateSurfacePosition %s" + "%d performSurfaceTransaction %s " + "position = [%d, %d, %d, %d] surfaceSize = %dx%d", System.identityHashCode(this), - applyChangesOnRenderThread ? "RenderWorker" : "UiThread", + isHardwareAccelerated() ? "RenderWorker" : "UI Thread", mScreenRect.left, mScreenRect.top, mScreenRect.right, mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight)); } @@ -1104,7 +1090,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall || mWindowSpaceTop != mLocation[1]; final boolean layoutSizeChanged = getWidth() != mScreenRect.width() || getHeight() != mScreenRect.height(); - final boolean hintChanged = (viewRoot.getSurfaceTransformHint() != mTransformHint) + final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint) && mRequestedVisible; if (creating || formatChanged || sizeChanged || visibleChanged || @@ -1130,7 +1116,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfaceHeight = myHeight; mFormat = mRequestedFormat; mLastWindowVisibility = mWindowVisibility; - mTransformHint = viewRoot.getSurfaceTransformHint(); + mTransformHint = viewRoot.getBufferTransformHint(); mScreenRect.left = mWindowSpaceLeft; mScreenRect.top = mWindowSpaceTop; @@ -1142,12 +1128,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets; mScreenRect.offset(surfaceInsets.left, surfaceInsets.top); - + // Collect all geometry changes and apply these changes on the RenderThread worker + // via the RenderNode.PositionUpdateListener. + final Transaction geometryTransaction = new Transaction(); if (creating) { updateOpaqueFlag(); final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]"; if (mUseBlastAdapter) { - createBlastSurfaceControls(viewRoot, name); + createBlastSurfaceControls(viewRoot, name, geometryTransaction); } else { mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name); } @@ -1156,7 +1144,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } final boolean realSizeChanged = performSurfaceTransaction(viewRoot, - translator, creating, sizeChanged, hintChanged); + translator, creating, sizeChanged, hintChanged, geometryTransaction); final boolean redrawNeeded = sizeChanged || creating || hintChanged || (mVisible && !mDrawFinished); @@ -1323,7 +1311,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // is still alive, the old buffers will continue to be presented until replaced by buffers from // the new adapter. This means we do not need to track the old surface control and destroy it // after the client has drawn to avoid any flickers. - private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) { + private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name, + Transaction geometryTransaction) { if (mSurfaceControl == null) { mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) .setName(name) @@ -1362,10 +1351,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (mBlastBufferQueue != null) { mBlastBufferQueue.destroy(); } - mTransformHint = viewRoot.getSurfaceTransformHint(); + mTransformHint = viewRoot.getBufferTransformHint(); mBlastSurfaceControl.setTransformHint(mTransformHint); - mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth, - mSurfaceHeight, mFormat); + mBlastBufferQueue = new BLASTBufferQueue(name); + mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat, + geometryTransaction); } private void onDrawFinished() { @@ -1464,54 +1454,49 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionChanged(long frameNumber, int left, int top, int right, int bottom) { - if (mSurfaceControl == null) { - return; - } - - // TODO: This is teensy bit racey in that a brand new SurfaceView moving on - // its 2nd frame if RenderThread is running slowly could potentially see - // this as false, enter the branch, get pre-empted, then this comes along - // and reports a new position, then the UI thread resumes and reports - // its position. This could therefore be de-sync'd in that interval, but - // the synchronization would violate the rule that RT must never block - // on the UI thread which would open up potential deadlocks. The risk of - // a single-frame desync is therefore preferable for now. synchronized(mSurfaceControlLock) { - mRtHandlingPositionUpdates = true; - } - if (mRTLastReportedPosition.left == left - && mRTLastReportedPosition.top == top - && mRTLastReportedPosition.right == right - && mRTLastReportedPosition.bottom == bottom - && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth - && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight - && !mPendingTransaction) { - return; - } - try { - if (DEBUG_POSITION) { - Log.d(TAG, String.format( - "%d updateSurfacePosition RenderWorker, frameNr = %d, " - + "position = [%d, %d, %d, %d] surfaceSize = %dx%d", - System.identityHashCode(SurfaceView.this), frameNumber, - left, top, right, bottom, mRtSurfaceWidth, mRtSurfaceHeight)); + if (mSurfaceControl == null) { + return; } - mRTLastReportedPosition.set(left, top, right, bottom); - mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight); - onSetSurfacePositionAndScaleRT(mPositionChangedTransaction, mSurfaceControl, - mRTLastReportedPosition.left /*positionLeft*/, - mRTLastReportedPosition.top /*positionTop*/, - mRTLastReportedPosition.width() / (float) mRtSurfaceWidth /*postScaleX*/, - mRTLastReportedPosition.height() / (float) mRtSurfaceHeight /*postScaleY*/); - if (mViewVisibility) { - mPositionChangedTransaction.show(mSurfaceControl); + if (mRTLastReportedPosition.left == left + && mRTLastReportedPosition.top == top + && mRTLastReportedPosition.right == right + && mRTLastReportedPosition.bottom == bottom + && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth + && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight + && !mPendingTransaction) { + return; + } + try { + if (DEBUG_POSITION) { + Log.d(TAG, String.format( + "%d updateSurfacePosition RenderWorker, frameNr = %d, " + + "position = [%d, %d, %d, %d] surfaceSize = %dx%d", + System.identityHashCode(SurfaceView.this), frameNumber, + left, top, right, bottom, mRtSurfaceWidth, mRtSurfaceHeight)); + } + mRTLastReportedPosition.set(left, top, right, bottom); + mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight); + onSetSurfacePositionAndScaleRT(mPositionChangedTransaction, mSurfaceControl, + mRTLastReportedPosition.left /*positionLeft*/, + mRTLastReportedPosition.top /*positionTop*/, + mRTLastReportedPosition.width() + / (float) mRtSurfaceWidth /*postScaleX*/, + mRTLastReportedPosition.height() + / (float) mRtSurfaceHeight /*postScaleY*/); + if (mViewVisibility) { + mPositionChangedTransaction.show(mSurfaceControl); + } + final ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + applyChildSurfaceTransaction_renderWorker(mPositionChangedTransaction, + viewRoot.mSurface, frameNumber); + } + applyOrMergeTransaction(mPositionChangedTransaction, frameNumber); + mPendingTransaction = false; + } catch (Exception ex) { + Log.e(TAG, "Exception from repositionChild", ex); } - applyChildSurfaceTransaction_renderWorker(mPositionChangedTransaction, - getViewRootImpl().mSurface, frameNumber); - applyOrMergeTransaction(mPositionChangedTransaction, frameNumber); - mPendingTransaction = false; - } catch (Exception ex) { - Log.e(TAG, "Exception from repositionChild", ex); } } @@ -1528,36 +1513,35 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionLost(long frameNumber) { - if (DEBUG) { + if (DEBUG_POSITION) { Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", System.identityHashCode(this), frameNumber)); } mRTLastReportedPosition.setEmpty(); mRTLastReportedSurfaceSize.set(-1, -1); - if (mPendingTransaction) { - Log.w(TAG, System.identityHashCode(SurfaceView.this) - + "Pending transaction cleared."); - mPositionChangedTransaction.clear(); - mPendingTransaction = false; - } - if (mSurfaceControl == null) { - return; - } /** * positionLost can be called while UI thread is un-paused so we * need to hold the lock here. */ synchronized (mSurfaceControlLock) { - mRtTransaction.hide(mSurfaceControl); - if (mRtReleaseSurfaces) { - mRtReleaseSurfaces = false; - releaseSurfaces(mRtTransaction); + if (mPendingTransaction) { + Log.w(TAG, System.identityHashCode(SurfaceView.this) + + "Pending transaction cleared."); + mPositionChangedTransaction.clear(); + mPendingTransaction = false; + } + if (mSurfaceControl == null) { + return; } + mRtTransaction.hide(mSurfaceControl); applyOrMergeTransaction(mRtTransaction, frameNumber); - mRtHandlingPositionUpdates = false; } } + + public Transaction getTransaction() { + return mPositionChangedTransaction; + } } private SurfaceViewPositionUpdateListener mPositionListener = null; @@ -1604,12 +1588,21 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * @hide */ public void setResizeBackgroundColor(int bgColor) { + setResizeBackgroundColor(mTmpTransaction, bgColor); + mTmpTransaction.apply(); + } + + /** + * Version of {@link #setResizeBackgroundColor(int)} that allows you to provide + * {@link SurfaceControl.Transaction}. + * @hide + */ + public void setResizeBackgroundColor(@NonNull SurfaceControl.Transaction t, int bgColor) { if (mBackgroundControl == null) { return; } - mBackgroundColor = bgColor; - updateBackgroundColor(mTmpTransaction).apply(); + updateBackgroundColor(t); } @UnsupportedAppUsage @@ -1642,6 +1635,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void setFixedSize(int width, int height) { if (mRequestedWidth != width || mRequestedHeight != height) { + if (DEBUG_POSITION) { + Log.d(TAG, String.format("%d setFixedSize %dx%d -> %dx%d", + System.identityHashCode(this), mRequestedWidth, mRequestedHeight, width, + height)); + } mRequestedWidth = width; mRequestedHeight = height; requestLayout(); @@ -1651,6 +1649,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void setSizeFromLayout() { if (mRequestedWidth != -1 || mRequestedHeight != -1) { + if (DEBUG_POSITION) { + Log.d(TAG, String.format("%d setSizeFromLayout was %dx%d", + System.identityHashCode(this), mRequestedWidth, mRequestedHeight)); + } mRequestedWidth = mRequestedHeight = -1; requestLayout(); } @@ -1878,18 +1880,45 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * @param p The SurfacePackage to embed. */ public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) { + setChildSurfacePackage(p, false /* applyTransactionOnDraw */); + } + + /** + * Similar to setChildSurfacePackage, but using the BLAST queue so the transaction can be + * synchronized with the ViewRootImpl frame. + * @hide + */ + public void setChildSurfacePackageOnDraw( + @NonNull SurfaceControlViewHost.SurfacePackage p) { + setChildSurfacePackage(p, true /* applyTransactionOnDraw */); + } + + /** + * @param applyTransactionOnDraw Whether to apply transaction at onDraw or immediately. + */ + private void setChildSurfacePackage( + @NonNull SurfaceControlViewHost.SurfacePackage p, boolean applyTransactionOnDraw) { final SurfaceControl lastSc = mSurfacePackage != null ? mSurfacePackage.getSurfaceControl() : null; if (mSurfaceControl != null && lastSc != null) { - mTmpTransaction.reparent(lastSc, null).apply(); + mTmpTransaction.reparent(lastSc, null); mSurfacePackage.release(); + applyTransaction(applyTransactionOnDraw); } else if (mSurfaceControl != null) { reparentSurfacePackage(mTmpTransaction, p); - mTmpTransaction.apply(); + applyTransaction(applyTransactionOnDraw); } mSurfacePackage = p; } + private void applyTransaction(boolean applyTransactionOnDraw) { + if (applyTransactionOnDraw) { + getViewRootImpl().applyTransactionOnDraw(mTmpTransaction); + } else { + mTmpTransaction.apply(); + } + } + private void reparentSurfacePackage(SurfaceControl.Transaction t, SurfaceControlViewHost.SurfacePackage p) { final SurfaceControl sc = p.getSurfaceControl(); diff --git a/packages/SystemUI/res/interpolator/wireless_charging_animation_interpolator_1.xml b/core/java/android/view/TaskTransitionSpec.aidl similarity index 69% rename from packages/SystemUI/res/interpolator/wireless_charging_animation_interpolator_1.xml rename to core/java/android/view/TaskTransitionSpec.aidl index 3fe59aeea8dc5c369614e5ceb0d7268646c34d52..08af15c3f9339101d344f9e62a24ad75c36e7fa6 100644 --- a/packages/SystemUI/res/interpolator/wireless_charging_animation_interpolator_1.xml +++ b/core/java/android/view/TaskTransitionSpec.aidl @@ -1,7 +1,5 @@ - - - + +package android.view; + +/** @hide */ +parcelable TaskTransitionSpec; diff --git a/core/java/android/view/TaskTransitionSpec.java b/core/java/android/view/TaskTransitionSpec.java new file mode 100644 index 0000000000000000000000000000000000000000..5f498a19f196c62272ed1e75b733de33262287fe --- /dev/null +++ b/core/java/android/view/TaskTransitionSpec.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArraySet; + +import java.util.Set; + +/** + * Holds information about how to execute task transition animations. + * + * This class is intended to be used with IWindowManager.setTaskTransitionSpec methods when + * we want more customization over the way default task transitions are executed. + * + * @hide + */ +public class TaskTransitionSpec implements Parcelable { + /** + * The background color to use during task animations (override the default background color) + */ + public final int backgroundColor; + + /** + * TEMPORARY FIELD (b/202383002) + * TODO: Remove once we use surfaceflinger rounded corners on tasks rather than taskbar overlays + * or when shell transitions are fully enabled + * + * A set of {@InsetsState.InternalInsetsType}s we want to use as the source to set the bounds + * of the task during the animation. Used to make sure that task animate above the taskbar. + * Will also be used to crop to the frame size of the inset source to the inset size to prevent + * the taskbar rounded corners overlay from being invisible during task transition animation. + */ + public final Set animationBoundInsets; + + public TaskTransitionSpec( + int backgroundColor, Set animationBoundInsets) { + this.backgroundColor = backgroundColor; + this.animationBoundInsets = animationBoundInsets; + } + + public TaskTransitionSpec(Parcel in) { + this.backgroundColor = in.readInt(); + + int animationBoundInsetsSize = in.readInt(); + this.animationBoundInsets = new ArraySet<>(animationBoundInsetsSize); + for (int i = 0; i < animationBoundInsetsSize; i++) { + this.animationBoundInsets.add(in.readInt()); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(backgroundColor); + + dest.writeInt(animationBoundInsets.size()); + for (int insetType : animationBoundInsets) { + dest.writeInt(insetType); + } + } + + public static final @android.annotation.NonNull Parcelable.Creator + CREATOR = new Parcelable.Creator() { + public TaskTransitionSpec createFromParcel(Parcel in) { + return new TaskTransitionSpec(in); + } + + public TaskTransitionSpec[] newArray(int size) { + return new TaskTransitionSpec[size]; + } + }; +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 2320434f14f68529fe3a729ba7f37bd77a412f36..f2ddf52351de06aebbad71df8a3d3db0dd83d90a 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3515,6 +3515,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG4_ALLOW_CLICK_WHEN_DISABLED * 1 PFLAG4_DETACHED * 1 PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE + * 1 PFLAG4_DRAG_A11Y_STARTED * |-------|-------|-------|-------| */ @@ -3586,6 +3587,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE = 0x000004000; + /** + * Indicates that the view has started a drag with {@link AccessibilityAction#ACTION_DRAG_START} + */ + private static final int PFLAG4_DRAG_A11Y_STARTED = 0x000008000; + /* End of masks for mPrivateFlags4 */ /** @hide */ @@ -5035,6 +5041,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int DRAG_FLAG_OPAQUE = 1 << 9; + /** + * Flag indicating that the drag was initiated with + * {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_START}. When + * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called, this + * is used by the system to perform a drag without animations. + */ + public static final int DRAG_FLAG_ACCESSIBILITY_ACTION = 1 << 10; + /** * Vertical scroll factor cached by {@link #getVerticalScrollFactor}. */ @@ -10376,8 +10390,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mTouchDelegate != null) { info.setTouchDelegateInfo(mTouchDelegate.getTouchDelegateInfo()); } + + if (startedSystemDragForAccessibility()) { + info.addAction(AccessibilityAction.ACTION_DRAG_CANCEL); + } + + if (canAcceptAccessibilityDrop()) { + info.addAction(AccessibilityAction.ACTION_DRAG_DROP); + } } + /** * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the * additional data. @@ -14214,9 +14237,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; } } + + if (action == R.id.accessibilityActionDragDrop) { + if (!canAcceptAccessibilityDrop()) { + return false; + } + try { + if (mAttachInfo != null && mAttachInfo.mSession != null) { + final int[] location = new int[2]; + getLocationInWindow(location); + final int centerX = location[0] + getWidth() / 2; + final int centerY = location[1] + getHeight() / 2; + return mAttachInfo.mSession.dropForAccessibility(mAttachInfo.mWindow, + centerX, centerY); + } + } catch (RemoteException e) { + Log.e(VIEW_LOG_TAG, "Unable to drop for accessibility", e); + } + return false; + } else if (action == R.id.accessibilityActionDragCancel) { + if (!startedSystemDragForAccessibility()) { + return false; + } + if (mAttachInfo != null && mAttachInfo.mDragToken != null) { + cancelDragAndDrop(); + return true; + } + return false; + } return false; } + private boolean canAcceptAccessibilityDrop() { + if (!canAcceptDrag()) { + return false; + } + ListenerInfo li = mListenerInfo; + return (li != null) && (li.mOnDragListener != null || li.mOnReceiveContentListener != null); + } + private boolean traverseAtGranularity(int granularity, boolean forward, boolean extendSelection) { CharSequence text = getIterableTextForAccessibility(); @@ -20211,7 +20270,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @CallSuper protected void onAttachedToWindow() { - if ((mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) { + if (mParent != null && (mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) { mParent.requestTransparentRegion(this); } @@ -24985,7 +25044,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, View parent = this; - while (parent.mParent != null && parent.mParent instanceof View) { + while (parent.mParent instanceof View) { parent = (View) parent.mParent; } @@ -26633,6 +26692,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, *
  • {@link #DRAG_FLAG_GLOBAL_URI_READ}
  • *
  • {@link #DRAG_FLAG_GLOBAL_URI_WRITE}
  • *
  • {@link #DRAG_FLAG_OPAQUE}
  • + *
  • {@link #DRAG_FLAG_ACCESSIBILITY_ACTION}
  • * * @return {@code true} if the method completes successfully, or * {@code false} if it fails anywhere. Returning {@code false} means the system was unable to @@ -26656,6 +26716,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback, data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0); } + Rect bounds = new Rect(); + getBoundsOnScreen(bounds, true); + + Point lastTouchPoint = new Point(); + mAttachInfo.mViewRootImpl.getLastTouchPoint(lastTouchPoint); + final ViewRootImpl root = mAttachInfo.mViewRootImpl; + + // Skip surface logic since shadows and animation are not required during the a11y drag + final boolean a11yEnabled = AccessibilityManager.getInstance(mContext).isEnabled(); + if (a11yEnabled && (flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) { + try { + IBinder token = mAttachInfo.mSession.performDrag( + mAttachInfo.mWindow, flags, null, + mAttachInfo.mViewRootImpl.getLastTouchSource(), + 0f, 0f, 0f, 0f, data); + if (ViewDebug.DEBUG_DRAG) { + Log.d(VIEW_LOG_TAG, "startDragAndDrop via a11y action returned " + token); + } + if (token != null) { + root.setLocalDragState(myLocalState); + mAttachInfo.mDragToken = token; + mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this); + setAccessibilityDragStarted(true); + } + return token != null; + } catch (Exception e) { + Log.e(VIEW_LOG_TAG, "Unable to initiate a11y drag", e); + return false; + } + } + Point shadowSize = new Point(); Point shadowTouchPoint = new Point(); shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint); @@ -26680,7 +26771,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, + " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y); } - final ViewRootImpl root = mAttachInfo.mViewRootImpl; final SurfaceSession session = new SurfaceSession(); final SurfaceControl surfaceControl = new SurfaceControl.Builder(session) .setName("drag surface") @@ -26703,12 +26793,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, surface.unlockCanvasAndPost(canvas); } - // repurpose 'shadowSize' for the last touch point - root.getLastTouchPoint(shadowSize); - - token = mAttachInfo.mSession.performDrag( - mAttachInfo.mWindow, flags, surfaceControl, root.getLastTouchSource(), - shadowSize.x, shadowSize.y, shadowTouchPoint.x, shadowTouchPoint.y, data); + token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl, + root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y, + shadowTouchPoint.x, shadowTouchPoint.y, data); if (ViewDebug.DEBUG_DRAG) { Log.d(VIEW_LOG_TAG, "performDrag returned " + token); } @@ -26720,6 +26807,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mDragToken = token; // Cache the local state object for delivery with DragEvents root.setLocalDragState(myLocalState); + if (a11yEnabled) { + // Set for AccessibilityEvents + mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this); + } } return token != null; } catch (Exception e) { @@ -26733,6 +26824,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + void setAccessibilityDragStarted(boolean started) { + int pflags4 = mPrivateFlags4; + if (started) { + pflags4 |= PFLAG4_DRAG_A11Y_STARTED; + } else { + pflags4 &= ~PFLAG4_DRAG_A11Y_STARTED; + } + + if (pflags4 != mPrivateFlags4) { + mPrivateFlags4 = pflags4; + sendWindowContentChangedAccessibilityEvent(CONTENT_CHANGE_TYPE_UNDEFINED); + } + } + + private boolean startedSystemDragForAccessibility() { + return (mPrivateFlags4 & PFLAG4_DRAG_A11Y_STARTED) != 0; + } + /** * Cancels an ongoing drag and drop operation. *

    @@ -26944,6 +27053,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } switch (event.mAction) { + case DragEvent.ACTION_DRAG_STARTED: { + if (result && li != null && li.mOnDragListener != null) { + sendWindowContentChangedAccessibilityEvent( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + } break; case DragEvent.ACTION_DRAG_ENTERED: { mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED; refreshDrawableState(); @@ -26952,7 +27067,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED; refreshDrawableState(); } break; + case DragEvent.ACTION_DROP: { + if (result && li != null && (li.mOnDragListener != null + || li.mOnReceiveContentListener != null)) { + sendWindowContentChangedAccessibilityEvent( + AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_DROPPED); + } + } break; case DragEvent.ACTION_DRAG_ENDED: { + sendWindowContentChangedAccessibilityEvent( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); mPrivateFlags2 &= ~View.DRAG_MASK; refreshDrawableState(); } break; @@ -26965,6 +27089,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return (mPrivateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0; } + void sendWindowContentChangedAccessibilityEvent(int changeType) { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(changeType); + sendAccessibilityEventUnchecked(event); + } + } + /** * This needs to be a better API (NOT ON VIEW) before it is exposed. If * it is ever exposed at all. diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 0a3d0da6da1e046b2fe1e0651b9e090175abd6d8..4b18d3a02282cc71cf557eb3833674a475a3907d 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -433,9 +433,8 @@ public class ViewConfiguration { mAmbiguousGestureMultiplier = Math.max(1.0f, multiplierValue.getFloat()); // Size of the screen in bytes, in ARGB_8888 format - final WindowManager windowManager = context.getSystemService(WindowManager.class); - final Rect maxWindowBounds = windowManager.getMaximumWindowMetrics().getBounds(); - mMaximumDrawingCacheSize = 4 * maxWindowBounds.width() * maxWindowBounds.height(); + final Rect maxBounds = config.windowConfiguration.getMaxBounds(); + mMaximumDrawingCacheSize = 4 * maxBounds.width() * maxBounds.height(); mOverscrollDistance = (int) (sizeAndDensity * OVERSCROLL_DISTANCE + 0.5f); mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f); diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 775c15e77d5dd5a5f4404fa75d0a8d6b61c4503a..49f5229d3c09c571a01bd2ba897b6ccb44be53a4 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -412,6 +412,9 @@ public interface ViewParent { *

  • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE} *
  • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT} *
  • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED} + *
  • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_STARTED} + *
  • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_CANCELLED} + *
  • {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_DROPPED} * */ public void notifySubtreeAccessibilityStateChanged( diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 675297d024eeb3dfa314661c15e0d5c34ab74842..a2ff69e159151d0ba001deae7c01894900ded1a9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -214,6 +214,7 @@ import java.util.List; import java.util.Objects; import java.util.Queue; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * The top of a view hierarchy, implementing the needed protocol between View @@ -291,12 +292,31 @@ public final class ViewRootImpl implements ViewParent, */ private static final int SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS = 2500; + /** + * If set to {@code true}, the new logic to layout system bars as normal window and to use + * layout result to get insets will be applied. Otherwise, the old hard-coded window logic will + * be applied. + */ + private static final String USE_FLEXIBLE_INSETS = "persist.debug.flexible_insets"; + + /** + * A flag to indicate to use the new generalized insets window logic, or the old hard-coded + * insets window layout logic. + * {@hide} + */ + public static final boolean INSETS_LAYOUT_GENERALIZATION = + SystemProperties.getBoolean(USE_FLEXIBLE_INSETS, false); + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) static final ThreadLocal sRunQueues = new ThreadLocal(); static final ArrayList sFirstDrawHandlers = new ArrayList<>(); static boolean sFirstDrawComplete = false; + private ArrayList mTransformHintListeners = + new ArrayList<>(); + private @SurfaceControl.BufferTransform + int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; /** * Callback for notifying about global configuration changes. */ @@ -633,6 +653,7 @@ public final class ViewRootImpl implements ViewParent, /* Drag/drop */ ClipDescription mDragDescription; View mCurrentDragView; + View mStartedDragViewForA11y; volatile Object mLocalDragState; final PointF mDragPoint = new PointF(); final PointF mLastTouchPoint = new PointF(); @@ -737,6 +758,8 @@ public final class ViewRootImpl implements ViewParent, */ private boolean mNextDrawUseBlastSync = false; + private Consumer mBLASTDrawConsumer; + /** * Wait for the blast sync transaction complete callback before drawing and queuing up more * frames. This will prevent out of order buffers submissions when WM has requested to @@ -744,6 +767,12 @@ public final class ViewRootImpl implements ViewParent, */ private boolean mWaitForBlastSyncComplete = false; + /** + * Keeps track of the last frame number that was attempted to draw. Should only be accessed on + * the RenderThread. + */ + private long mRtLastAttemptedDrawFrameNum = 0; + /** * Keeps track of whether a traverse was triggered while the UI thread was paused. This can * occur when the client is waiting on another process to submit the transaction that @@ -859,6 +888,13 @@ public final class ViewRootImpl implements ViewParent, } } + /** Remove a static config callback. */ + public static void removeConfigCallback(ConfigChangedCallback callback) { + synchronized (sConfigCallbacks) { + sConfigCallbacks.remove(callback); + } + } + /** Add activity config callback to be notified about override config changes. */ public void setActivityConfigCallback(ActivityConfigCallback callback) { mActivityConfigCallback = callback; @@ -1112,7 +1148,7 @@ public final class ViewRootImpl implements ViewParent, controlInsetsForCompatibility(mWindowAttributes); res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, - mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets, + mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets, mTempControls); if (mTranslator != null) { mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets); @@ -2469,7 +2505,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mContentInsets.set(mLastWindowInsets.getSystemWindowInsets().toRect()); mAttachInfo.mStableInsets.set(mLastWindowInsets.getStableInsets().toRect()); mAttachInfo.mVisibleInsets.set(mInsetsController.calculateVisibleInsets( - mWindowAttributes.softInputMode)); + mWindowAttributes.softInputMode).toRect()); } return mLastWindowInsets; } @@ -2519,6 +2555,14 @@ public final class ViewRootImpl implements ViewParent, || lp.type == TYPE_VOLUME_OVERLAY; } + private Rect getWindowBoundsInsetSystemBars() { + final Rect bounds = new Rect( + mContext.getResources().getConfiguration().windowConfiguration.getBounds()); + bounds.inset(mInsetsController.getState().calculateInsets( + bounds, Type.systemBars(), false /* ignoreVisibility */)); + return bounds; + } + int dipToPx(int dip) { final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); return (int) (displayMetrics.density * dip + 0.5f); @@ -2605,8 +2649,9 @@ public final class ViewRootImpl implements ViewParent, || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { // For wrap content, we have to remeasure later on anyways. Use size consistent with // below so we get best use of the measure cache. - desiredWindowWidth = dipToPx(config.screenWidthDp); - desiredWindowHeight = dipToPx(config.screenHeightDp); + final Rect bounds = getWindowBoundsInsetSystemBars(); + desiredWindowWidth = bounds.width(); + desiredWindowHeight = bounds.height(); } else { // After addToDisplay, the frame contains the frameHint from window manager, which // for most windows is going to be the same size as the result of relayoutWindow. @@ -2683,9 +2728,9 @@ public final class ViewRootImpl implements ViewParent, desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { - Configuration config = res.getConfiguration(); - desiredWindowWidth = dipToPx(config.screenWidthDp); - desiredWindowHeight = dipToPx(config.screenHeightDp); + final Rect bounds = getWindowBoundsInsetSystemBars(); + desiredWindowWidth = bounds.width(); + desiredWindowHeight = bounds.height(); } } } @@ -3315,6 +3360,9 @@ public final class ViewRootImpl implements ViewParent, } boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; + if (mBLASTDrawConsumer != null) { + mNextDrawUseBlastSync = true; + } if (!cancelDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { @@ -4009,68 +4057,112 @@ public final class ViewRootImpl implements ViewParent, } /** - * The callback will run on the render thread. + * Only call this on the UI Thread. + */ + void clearBlastSync() { + mNextDrawUseBlastSync = false; + mWaitForBlastSyncComplete = false; + if (DEBUG_BLAST) { + Log.d(mTag, "Scheduling a traversal=" + mRequestedTraverseWhilePaused + + " due to a previous skipped traversal."); + } + if (mRequestedTraverseWhilePaused) { + mRequestedTraverseWhilePaused = false; + scheduleTraversals(); + } + } + + /** + * @hide */ - private HardwareRenderer.FrameCompleteCallback createFrameCompleteCallback(Handler handler, - boolean reportNextDraw, ArrayList commitCallbacks) { - return frameNr -> { + public boolean isHardwareEnabled() { + return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled(); + } + + private boolean addFrameCompleteCallbackIfNeeded(boolean reportNextDraw) { + if (!isHardwareEnabled()) { + return false; + } + + if (!mNextDrawUseBlastSync && !reportNextDraw) { + return false; + } + + if (DEBUG_BLAST) { + Log.d(mTag, "Creating frameCompleteCallback"); + } + + mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(() -> { + long frameNr = mBlastBufferQueue.getLastAcquiredFrameNum(); if (DEBUG_BLAST) { - Log.d(mTag, "Received frameCompleteCallback frameNum=" + frameNr); + Log.d(mTag, "Received frameCompleteCallback " + + " lastAcquiredFrameNum=" + frameNr + + " lastAttemptedDrawFrameNum=" + mRtLastAttemptedDrawFrameNum); + } + + boolean frameWasNotDrawn = frameNr != mRtLastAttemptedDrawFrameNum; + // If frame wasn't drawn, clear out the next transaction so it doesn't affect the next + // draw attempt. The next transaction and transaction complete callback were only set + // for the current draw attempt. + if (frameWasNotDrawn) { + mBlastBufferQueue.setNextTransaction(null); + mBlastBufferQueue.setTransactionCompleteCallback(mRtLastAttemptedDrawFrameNum, + null); } - handler.postAtFrontOfQueue(() -> { + mHandler.postAtFrontOfQueue(() -> { if (mNextDrawUseBlastSync) { // We don't need to synchronize mRtBLASTSyncTransaction here since we're // guaranteed that this is called after onFrameDraw and mNextDrawUseBlastSync // is only true when the UI thread is paused. Therefore, no one should be // modifying this object until the next vsync. mSurfaceChangedTransaction.merge(mRtBLASTSyncTransaction); + if (mBLASTDrawConsumer != null) { + mBLASTDrawConsumer.accept(mSurfaceChangedTransaction); + } + mBLASTDrawConsumer = null; } if (reportNextDraw) { - // TODO: Use the frame number pendingDrawFinished(); } - if (commitCallbacks != null) { - for (int i = 0; i < commitCallbacks.size(); i++) { - commitCallbacks.get(i).run(); - } + + if (frameWasNotDrawn) { + clearBlastSync(); } }); - }; - } - - /** - * @hide - */ - public boolean isHardwareEnabled() { - return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled(); + }); + return true; } - private boolean addFrameCompleteCallbackIfNeeded() { + private void addFrameCommitCallbackIfNeeded() { if (!isHardwareEnabled()) { - return false; + return; } ArrayList commitCallbacks = mAttachInfo.mTreeObserver .captureFrameCommitCallbacks(); - final boolean needFrameCompleteCallback = - mNextDrawUseBlastSync || mReportNextDraw - || (commitCallbacks != null && commitCallbacks.size() > 0); - if (needFrameCompleteCallback) { - if (DEBUG_BLAST) { - Log.d(mTag, "Creating frameCompleteCallback" - + " mNextDrawUseBlastSync=" + mNextDrawUseBlastSync - + " mReportNextDraw=" + mReportNextDraw - + " commitCallbacks size=" - + (commitCallbacks == null ? 0 : commitCallbacks.size())); - } - mAttachInfo.mThreadedRenderer.setFrameCompleteCallback( - createFrameCompleteCallback(mAttachInfo.mHandler, mReportNextDraw, - commitCallbacks)); - return true; + final boolean needFrameCommitCallback = + (commitCallbacks != null && commitCallbacks.size() > 0); + if (!needFrameCommitCallback) { + return; } - return false; + + if (DEBUG_DRAW) { + Log.d(mTag, "Creating frameCommitCallback" + + " commitCallbacks size=" + commitCallbacks.size()); + } + mAttachInfo.mThreadedRenderer.setFrameCommitCallback(didProduceBuffer -> { + if (DEBUG_DRAW) { + Log.d(mTag, "Received frameCommitCallback didProduceBuffer=" + didProduceBuffer); + } + + mHandler.postAtFrontOfQueue(() -> { + for (int i = 0; i < commitCallbacks.size(); i++) { + commitCallbacks.get(i).run(); + } + }); + }); } private void addFrameCallbackIfNeeded() { @@ -4100,6 +4192,8 @@ public final class ViewRootImpl implements ViewParent, + " Creating transactionCompleteCallback=" + nextDrawUseBlastSync); } + mRtLastAttemptedDrawFrameNum = frame; + if (needsCallbackForBlur) { mBlurRegionAggregator .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates); @@ -4122,24 +4216,8 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_BLAST) { Log.d(mTag, "Received transactionCompleteCallback frameNum=" + frame); } - mHandler.postAtFrontOfQueue(() -> { - mNextDrawUseBlastSync = false; - mWaitForBlastSyncComplete = false; - if (DEBUG_BLAST) { - Log.d(mTag, "Scheduling a traversal=" + mRequestedTraverseWhilePaused - + " due to a previous skipped traversal."); - } - if (mRequestedTraverseWhilePaused) { - mRequestedTraverseWhilePaused = false; - scheduleTraversals(); - } - }); + mHandler.postAtFrontOfQueue(this::clearBlastSync); }); - } else if (reportNextDraw) { - // If we need to report next draw, wait for adapter to flush its shadow queue - // by processing previously queued buffers so that we can submit the - // transaction a timely manner. - mBlastBufferQueue.flushShadowQueue(); } }; registerRtFrameCallback(frameDrawingCallback); @@ -4159,8 +4237,9 @@ public final class ViewRootImpl implements ViewParent, mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); - boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded(); addFrameCallbackIfNeeded(); + addFrameCommitCallbackIfNeeded(); + boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded(mReportNextDraw); try { boolean canUseAsync = draw(fullRedrawNeeded); @@ -4269,6 +4348,14 @@ public final class ViewRootImpl implements ViewParent, try { if (!isContentCaptureEnabled()) return; + // Initial dispatch of window bounds to content capture + if (mAttachInfo.mContentCaptureManager != null) { + MainContentCaptureSession session = + mAttachInfo.mContentCaptureManager.getMainContentCaptureSession(); + session.notifyWindowBoundsChanged(session.getId(), + getConfiguration().windowConfiguration.getBounds()); + } + // Content capture is a go! rootView.dispatchInitialProvideContentCaptureStructure(); } finally { @@ -7564,6 +7651,11 @@ public final class ViewRootImpl implements ViewParent, if (what == DragEvent.ACTION_DRAG_STARTED) { mCurrentDragView = null; // Start the current-recipient tracking mDragDescription = event.mClipDescription; + if (mStartedDragViewForA11y != null) { + // Send a drag started a11y event + mStartedDragViewForA11y.sendWindowContentChangedAccessibilityEvent( + AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_STARTED); + } } else { if (what == DragEvent.ACTION_DRAG_ENDED) { mDragDescription = null; @@ -7638,6 +7730,16 @@ public final class ViewRootImpl implements ViewParent, // When the drag operation ends, reset drag-related state if (what == DragEvent.ACTION_DRAG_ENDED) { + if (mStartedDragViewForA11y != null) { + // If the drag failed, send a cancelled event from the source. Otherwise, + // the View that accepted the drop sends CONTENT_CHANGE_TYPE_DRAG_DROPPED + if (!event.getResult()) { + mStartedDragViewForA11y.sendWindowContentChangedAccessibilityEvent( + AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_CANCELLED); + } + mStartedDragViewForA11y.setAccessibilityDragStarted(false); + } + mStartedDragViewForA11y = null; mCurrentDragView = null; setLocalDragState(null); mAttachInfo.mDragToken = null; @@ -7717,6 +7819,13 @@ public final class ViewRootImpl implements ViewParent, mCurrentDragView = newDragTarget; } + /** Sets the view that started drag and drop for the purpose of sending AccessibilityEvents */ + void setDragStartedViewForAccessibility(View view) { + if (mStartedDragViewForA11y == null) { + mStartedDragViewForA11y = view; + } + } + private AudioManager getAudioManager() { if (mView == null) { throw new IllegalStateException("getAudioManager called when there is no mView"); @@ -7795,6 +7904,14 @@ public final class ViewRootImpl implements ViewParent, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls, mSurfaceSize); + + if (mAttachInfo.mContentCaptureManager != null) { + MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager + .getMainContentCaptureSession(); + mainSession.notifyWindowBoundsChanged(mainSession.getId(), + getConfiguration().windowConfiguration.getBounds()); + } + mPendingBackDropFrame.set(mTmpFrames.backdropFrame); if (mSurfaceControl.isValid()) { if (!useBLAST()) { @@ -7815,6 +7932,11 @@ public final class ViewRootImpl implements ViewParent, } mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl); } + int transformHint = mSurfaceControl.getTransformHint(); + if (mPreviousTransformHint != transformHint) { + mPreviousTransformHint = transformHint; + dispatchTransformHintChanged(transformHint); + } } else { destroySurface(); } @@ -8289,7 +8411,7 @@ public final class ViewRootImpl implements ViewParent, if (mTranslator != null) { mTranslator.translateInsetsStateInScreenToAppWindow(insetsState); } - if (insetsState != null && insetsState.getSource(ITYPE_IME).isVisible()) { + if (insetsState != null && insetsState.getSourceOrDefaultVisibility(ITYPE_IME)) { ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsChanged", getInsetsController().getHost().getInputMethodManager(), null /* icProto */); } @@ -8314,7 +8436,7 @@ public final class ViewRootImpl implements ViewParent, mTranslator.translateInsetsStateInScreenToAppWindow(insetsState); mTranslator.translateSourceControlsInScreenToAppWindow(activeControls); } - if (insetsState != null && insetsState.getSource(ITYPE_IME).isVisible()) { + if (insetsState != null && insetsState.getSourceOrDefaultVisibility(ITYPE_IME)) { ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsControlChanged", getInsetsController().getHost().getInputMethodManager(), null /* icProto */); } @@ -9924,7 +10046,10 @@ public final class ViewRootImpl implements ViewParent, if (!mUseMTRenderer) { return; } - mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size()); + // Only wait if it will report next draw. + if (mReportNextDraw) { + mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size()); + } for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { mWindowCallbacks.get(i).onRequestDraw(mReportNextDraw); } @@ -10430,13 +10555,78 @@ public final class ViewRootImpl implements ViewParent, @Override public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) { - registerRtFrameCallback(frame -> { - mergeWithNextTransaction(t, frame); - }); + if (mRemoved || !isHardwareEnabled()) { + t.apply(); + } else { + registerRtFrameCallback(frame -> mergeWithNextTransaction(t, frame)); + } return true; } - int getSurfaceTransformHint() { + @Override + public @SurfaceControl.BufferTransform int getBufferTransformHint() { return mSurfaceControl.getTransformHint(); } + + @Override + public void addOnBufferTransformHintChangedListener( + OnBufferTransformHintChangedListener listener) { + Objects.requireNonNull(listener); + if (mTransformHintListeners.contains(listener)) { + throw new IllegalArgumentException( + "attempt to call addOnBufferTransformHintChangedListener() " + + "with a previously registered listener"); + } + mTransformHintListeners.add(listener); + } + + @Override + public void removeOnBufferTransformHintChangedListener( + OnBufferTransformHintChangedListener listener) { + Objects.requireNonNull(listener); + mTransformHintListeners.remove(listener); + } + + private void dispatchTransformHintChanged(@SurfaceControl.BufferTransform int hint) { + if (mTransformHintListeners.isEmpty()) { + return; + } + ArrayList listeners = + (ArrayList) mTransformHintListeners.clone(); + for (int i = 0; i < listeners.size(); i++) { + OnBufferTransformHintChangedListener listener = listeners.get(i); + listener.onBufferTransformHintChanged(hint); + } + } + + /** + * Redirect the next draw of this ViewRoot (from the UI thread perspective) + * to the passed in consumer. This can be used to create P2P synchronization + * between ViewRoot's however it comes with many caveats. + * + * 1. You MUST consume the transaction, by either applying it immediately or + * merging it in to another transaction. The threading model doesn't + * allow you to hold in the passed transaction. + * 2. If you merge it in to another transaction, this ViewRootImpl will be + * paused until you finally apply that transaction and it receives + * the callback from SF. If you lose track of the transaction you will + * ANR the app. + * 3. Only one person can consume the transaction at a time, if you already + * have a pending consumer for this frame, the function will return false + * 4. Someone else may have requested to consume the next frame, in which case + * this function will return false and you will not receive a callback. + * 5. This function does not trigger drawing so even if it returns true you + * may not receive a callback unless there is some other UI thread work + * to trigger drawing. If it returns true, and a draw occurs, the callback + * will be called (Though again watch out for the null transaction case!) + * 6. This function must be called on the UI thread. The consumer will likewise + * be called on the UI thread. + */ + public boolean consumeNextDraw(Consumer consume) { + if (mBLASTDrawConsumer != null) { + return false; + } + mBLASTDrawConsumer = consume; + return true; + } } diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java index ce882da1a6da3d526d77e894fd3ea041b0ae9ad3..efffa2b05a1e8bdf79c57a5aeaea4937d766ce5a 100644 --- a/core/java/android/view/ViewRootInsetsControllerHost.java +++ b/core/java/android/view/ViewRootInsetsControllerHost.java @@ -149,10 +149,10 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host { } @Override - public void onInsetsModified(InsetsState insetsState) { + public void updateRequestedVisibilities(InsetsVisibilities vis) { try { if (mViewRoot.mAdded) { - mViewRoot.mWindowSession.insetsModified(mViewRoot.mWindow, insetsState); + mViewRoot.mWindowSession.updateRequestedVisibilities(mViewRoot.mWindow, vis); } } catch (RemoteException e) { Log.e(TAG, "Failed to call insetsModified", e); diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java index 57dfc62b4f8fbbdac75ab5f719ecd3ab100edfb3..1edbbba09eef9acd5cda18ab79a1dc1b24b718de 100644 --- a/core/java/android/view/WindowInfo.java +++ b/core/java/android/view/WindowInfo.java @@ -16,6 +16,7 @@ package android.view; +import android.app.ActivityTaskManager; import android.graphics.Region; import android.os.IBinder; import android.os.Parcel; @@ -51,6 +52,7 @@ public class WindowInfo implements Parcelable { public boolean inPictureInPicture; public boolean hasFlagWatchOutsideTouch; public int displayId = Display.INVALID_DISPLAY; + public int taskId = ActivityTaskManager.INVALID_TASK_ID; private WindowInfo() { /* do nothing - hide constructor */ @@ -67,6 +69,7 @@ public class WindowInfo implements Parcelable { public static WindowInfo obtain(WindowInfo other) { WindowInfo window = obtain(); window.displayId = other.displayId; + window.taskId = other.taskId; window.type = other.type; window.layer = other.layer; window.token = other.token; @@ -103,6 +106,7 @@ public class WindowInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(displayId); + parcel.writeInt(taskId); parcel.writeInt(type); parcel.writeInt(layer); parcel.writeStrongBinder(token); @@ -129,6 +133,7 @@ public class WindowInfo implements Parcelable { builder.append("WindowInfo["); builder.append("title=").append(title); builder.append(", displayId=").append(displayId); + builder.append(", taskId=").append(taskId); builder.append(", type=").append(type); builder.append(", layer=").append(layer); builder.append(", token=").append(token); @@ -146,6 +151,7 @@ public class WindowInfo implements Parcelable { private void initFromParcel(Parcel parcel) { displayId = parcel.readInt(); + taskId = parcel.readInt(); type = parcel.readInt(); layer = parcel.readInt(); token = parcel.readStrongBinder(); @@ -169,6 +175,7 @@ public class WindowInfo implements Parcelable { private void clear() { displayId = Display.INVALID_DISPLAY; + taskId = ActivityTaskManager.INVALID_TASK_ID; type = 0; layer = 0; token = null; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 5b0bc4ad1aec3709972a3e1e5e3f7e0a290dc3cf..9605f775b9411fa14254f6d50a454edf426aa125 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -97,6 +97,7 @@ import android.content.ClipData; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; @@ -316,6 +317,25 @@ public interface WindowManager extends ViewManager { */ int TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE = 27; + /** + * A window in a new task fragment is being opened. + * @hide + */ + int TRANSIT_OLD_TASK_FRAGMENT_OPEN = 28; + + /** + * A window in the top-most activity of task fragment is being closed to reveal the activity + * below. + * @hide + */ + int TRANSIT_OLD_TASK_FRAGMENT_CLOSE = 29; + + /** + * A window of task fragment is changing bounds. + * @hide + */ + int TRANSIT_OLD_TASK_FRAGMENT_CHANGE = 30; + /** * @hide */ @@ -341,7 +361,10 @@ public interface WindowManager extends ViewManager { TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN, TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE, TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE, - TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE + TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE, + TRANSIT_OLD_TASK_FRAGMENT_OPEN, + TRANSIT_OLD_TASK_FRAGMENT_CLOSE, + TRANSIT_OLD_TASK_FRAGMENT_CHANGE }) @Retention(RetentionPolicy.SOURCE) @interface TransitionOldType {} @@ -378,8 +401,11 @@ public interface WindowManager extends ViewManager { int TRANSIT_CHANGE = 6; /** * The keyguard was visible and has been dismissed. + * @deprecated use {@link #TRANSIT_TO_BACK} + {@link #TRANSIT_FLAG_KEYGUARD_GOING_AWAY} for + * keyguard going away with Shell transition. * @hide */ + @Deprecated int TRANSIT_KEYGUARD_GOING_AWAY = 7; /** * A window is appearing above a locked keyguard. @@ -391,6 +417,16 @@ public interface WindowManager extends ViewManager { * @hide */ int TRANSIT_KEYGUARD_UNOCCLUDE = 9; + /** + * A window is starting to enter PiP. + * @hide + */ + int TRANSIT_PIP = 10; + /** + * The screen is turning on. + * @hide + */ + int TRANSIT_WAKE = 11; /** * The first slot for custom transition types. Callers (like Shell) can make use of custom * transition types for dealing with special cases. These types are effectively ignored by @@ -400,7 +436,7 @@ public interface WindowManager extends ViewManager { * implementation. * @hide */ - int TRANSIT_FIRST_CUSTOM = 10; + int TRANSIT_FIRST_CUSTOM = 12; /** * @hide @@ -416,6 +452,8 @@ public interface WindowManager extends ViewManager { TRANSIT_KEYGUARD_GOING_AWAY, TRANSIT_KEYGUARD_OCCLUDE, TRANSIT_KEYGUARD_UNOCCLUDE, + TRANSIT_PIP, + TRANSIT_WAKE, TRANSIT_FIRST_CUSTOM }) @Retention(RetentionPolicy.SOURCE) @@ -464,6 +502,19 @@ public interface WindowManager extends ViewManager { */ int TRANSIT_FLAG_KEYGUARD_LOCKED = 0x40; + /** + * Transition flag: Indicates that this transition is for recents animation. + * TODO(b/188669821): Remove once special-case logic moves to shell. + * @hide + */ + int TRANSIT_FLAG_IS_RECENTS = 0x80; + + /** + * Transition flag: Indicates that keyguard should go away with this transition. + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY = 0x100; + /** * @hide */ @@ -474,7 +525,9 @@ public interface WindowManager extends ViewManager { TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION, TRANSIT_FLAG_APP_CRASHED, TRANSIT_FLAG_OPEN_BEHIND, - TRANSIT_FLAG_KEYGUARD_LOCKED + TRANSIT_FLAG_KEYGUARD_LOCKED, + TRANSIT_FLAG_IS_RECENTS, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY }) @Retention(RetentionPolicy.SOURCE) @interface TransitionFlags {} @@ -920,6 +973,8 @@ public interface WindowManager extends ViewManager { case TRANSIT_KEYGUARD_GOING_AWAY: return "KEYGUARD_GOING_AWAY"; case TRANSIT_KEYGUARD_OCCLUDE: return "KEYGUARD_OCCLUDE"; case TRANSIT_KEYGUARD_UNOCCLUDE: return "KEYGUARD_UNOCCLUDE"; + case TRANSIT_PIP: return "PIP"; + case TRANSIT_WAKE: return "WAKE"; case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM"; default: if (type > TRANSIT_FIRST_CUSTOM) { @@ -2368,6 +2423,20 @@ public interface WindowManager extends ViewManager { */ public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000; + /** + * Flag to indicate that this window will be excluded while computing the magnifiable region + * on the un-scaled screen coordinate, which could avoid the cutout on the magnification + * border. It should be used for unmagnifiable overlays. + * + *

    + * Note unlike {@link #PRIVATE_FLAG_NOT_MAGNIFIABLE}, this flag doesn't affect the ability + * of magnification. If you want to the window to be unmagnifiable and doesn't lead to the + * cutout, you need to combine both of them. + *

    + * @hide + */ + public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 0x00200000; + /** * Flag to prevent the window from being magnified by the accessibility magnifier. * @@ -2479,6 +2548,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, + PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, PRIVATE_FLAG_NOT_MAGNIFIABLE, PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION, PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, @@ -2559,6 +2629,10 @@ public interface WindowManager extends ViewManager { mask = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, name = "IS_ROUNDED_CORNERS_OVERLAY"), + @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, + equals = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, + name = "EXCLUDE_FROM_SCREEN_MAGNIFICATION"), @ViewDebug.FlagToString( mask = PRIVATE_FLAG_NOT_MAGNIFIABLE, equals = PRIVATE_FLAG_NOT_MAGNIFIABLE, @@ -3481,6 +3555,30 @@ public interface WindowManager extends ViewManager { */ public @InsetsState.InternalInsetsType int[] providesInsetsTypes; + /** + * If specified, the insets provided by this window will be our window frame minus the + * insets specified by providedInternalInsets. + * + * @hide + */ + public Insets providedInternalInsets = Insets.NONE; + + /** + * If specified, the insets provided by this window for the IME will be our window frame + * minus the insets specified by providedInternalImeInsets. + * + * @hide + */ + public Insets providedInternalImeInsets = Insets.NONE; + + /** + * {@link LayoutParams} to be applied to the window when layout with a assigned rotation. + * This will make layout during rotation change smoothly. + * + * @hide + */ + public LayoutParams[] paramsForRotation; + /** * Specifies types of insets that this window should avoid overlapping during layout. * @@ -3580,6 +3678,18 @@ public interface WindowManager extends ViewManager { return mFitInsetsIgnoringVisibility; } + private void checkNonRecursiveParams() { + if (paramsForRotation == null) { + return; + } + for (int i = paramsForRotation.length - 1; i >= 0; i--) { + if (paramsForRotation[i].paramsForRotation != null) { + throw new IllegalArgumentException( + "Params cannot contain params recursively."); + } + } + } + public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = TYPE_APPLICATION; @@ -3834,6 +3944,15 @@ public interface WindowManager extends ViewManager { } else { out.writeInt(0); } + providedInternalInsets.writeToParcel(out, 0 /* parcelableFlags */); + providedInternalImeInsets.writeToParcel(out, 0 /* parcelableFlags */); + if (paramsForRotation != null) { + checkNonRecursiveParams(); + out.writeInt(paramsForRotation.length); + out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */); + } else { + out.writeInt(0); + } } public static final @android.annotation.NonNull Parcelable.Creator CREATOR @@ -3905,6 +4024,13 @@ public interface WindowManager extends ViewManager { providesInsetsTypes = new int[insetsTypesLength]; in.readIntArray(providesInsetsTypes); } + providedInternalInsets = Insets.CREATOR.createFromParcel(in); + providedInternalImeInsets = Insets.CREATOR.createFromParcel(in); + int paramsForRotationLength = in.readInt(); + if (paramsForRotationLength > 0) { + paramsForRotation = new LayoutParams[paramsForRotationLength]; + in.readTypedArray(paramsForRotation, LayoutParams.CREATOR); + } } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -4201,6 +4327,22 @@ public interface WindowManager extends ViewManager { changes |= LAYOUT_CHANGED; } + if (!providedInternalInsets.equals(o.providedInternalInsets)) { + providedInternalInsets = o.providedInternalInsets; + changes |= LAYOUT_CHANGED; + } + + if (!providedInternalImeInsets.equals(o.providedInternalImeInsets)) { + providedInternalImeInsets = o.providedInternalImeInsets; + changes |= LAYOUT_CHANGED; + } + + if (!Arrays.equals(paramsForRotation, o.paramsForRotation)) { + paramsForRotation = o.paramsForRotation; + checkNonRecursiveParams(); + changes |= LAYOUT_CHANGED; + } + return changes; } @@ -4396,6 +4538,22 @@ public interface WindowManager extends ViewManager { sb.append(InsetsState.typeToString(providesInsetsTypes[i])); } } + if (!providedInternalInsets.equals(Insets.NONE)) { + sb.append(" providedInternalInsets="); + sb.append(providedInternalInsets); + } + if (!providedInternalImeInsets.equals(Insets.NONE)) { + sb.append(" providedInternalImeInsets="); + sb.append(providedInternalImeInsets); + } + if (paramsForRotation != null && paramsForRotation.length != 0) { + sb.append(System.lineSeparator()); + sb.append(prefix).append(" paramsForRotation="); + for (int i = 0; i < paramsForRotation.length; ++i) { + if (i > 0) sb.append(' '); + sb.append(paramsForRotation[i].toString()); + } + } sb.append('}'); return sb.toString(); @@ -4610,6 +4768,16 @@ public interface WindowManager extends ViewManager { return Integer.toString(inputFeature); } } + + /** + * True if the window should consume all pointer events itself, regardless of whether they + * are inside of the window. If the window is modal, its touchable region will expand to the + * size of its task. + * @hide + */ + public boolean isModal() { + return (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0; + } } /** diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 18013e815d134e2daf8ed652b581f7710c5e0310..c92a3a086a8bf2a9453dd69188aef31ce4591d7d 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -18,6 +18,7 @@ package android.view; import android.animation.ValueAnimator; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentCallbacks2; @@ -709,6 +710,16 @@ public final class WindowManagerGlobal { } } } + + /** @hide */ + @Nullable + public SurfaceControl mirrorWallpaperSurface(int displayId) { + try { + return getWindowManagerService().mirrorWallpaperSurface(displayId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } final class WindowLeaked extends AndroidRuntimeException { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index f800991944ac23c1b822f06232bea85966a45bb3..a2d3e3447354aa5c38cccb2669f03bbc29d703bc 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -19,9 +19,12 @@ package android.view; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; +import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import static android.window.WindowProviderService.isWindowProviderService; import android.annotation.CallbackExecutor; import android.annotation.NonNull; @@ -36,7 +39,9 @@ import android.graphics.Region; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.os.StrictMode; import android.window.WindowContext; +import android.window.WindowProvider; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; @@ -145,6 +150,7 @@ public final class WindowManagerImpl implements WindowManager { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; + assertWindowContextTypeMatches(wparams.type); // Only use the default token if we don't have a parent window and a token. if (mDefaultToken != null && mParentWindow == null && wparams.token == null) { wparams.token = mDefaultToken; @@ -152,6 +158,34 @@ public final class WindowManagerImpl implements WindowManager { wparams.mWindowContextToken = mWindowContextToken; } + private void assertWindowContextTypeMatches(@LayoutParams.WindowType int windowType) { + if (!(mContext instanceof WindowProvider)) { + return; + } + // Don't need to check sub-window type because sub window should be allowed to be attached + // to the parent window. + if (windowType >= FIRST_SUB_WINDOW && windowType <= LAST_SUB_WINDOW) { + return; + } + final WindowProvider windowProvider = (WindowProvider) mContext; + if (windowProvider.getWindowType() == windowType) { + return; + } + IllegalArgumentException exception = new IllegalArgumentException("Window type mismatch." + + " Window Context's window type is " + windowProvider.getWindowType() + + ", while LayoutParams' type is set to " + windowType + "." + + " Please create another Window Context via" + + " createWindowContext(getDisplay(), " + windowType + ", null)" + + " to add window with type:" + windowType); + if (!isWindowProviderService(windowProvider.getWindowContextOptions())) { + throw exception; + } + // Throw IncorrectCorrectViolation if the Window Context is allowed to provide multiple + // window types. Usually it's because the Window Context is a WindowProviderService. + StrictMode.onIncorrectContextUsed("WindowContext's window type must" + + " match type in WindowManager.LayoutParams", exception); + } + @Override public void removeView(View view) { mGlobal.removeView(view, false); diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index bbef3e6aeefa3e7694dd494202db802749cb7fe3..94f633314b4ef178b0d4dfc3a4fa984259132223 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -209,4 +209,44 @@ public interface WindowManagerPolicyConstants { return Integer.toString(why); } } + + /** + * How much to multiply the policy's type layer, to reserve room + * for multiple windows of the same type and Z-ordering adjustment + * with TYPE_LAYER_OFFSET. + */ + int TYPE_LAYER_MULTIPLIER = 10000; + + /** + * Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above + * or below others in the same layer. + */ + int TYPE_LAYER_OFFSET = 1000; + + /** + * How much to increment the layer for each window, to reserve room + * for effect surfaces between them. + */ + int WINDOW_LAYER_MULTIPLIER = 5; + + /** + * Animation thumbnail is as far as possible below the window above + * the thumbnail (or in other words as far as possible above the window + * below it). + */ + int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1; + + // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and + // make app pair split only have single root then we can just attach the + // divider to the single root task in shell. + int SPLIT_DIVIDER_LAYER = TYPE_LAYER_MULTIPLIER * 3; + int WATERMARK_LAYER = TYPE_LAYER_MULTIPLIER * 100; + int STRICT_MODE_LAYER = TYPE_LAYER_MULTIPLIER * 101; + int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200; + + /** + * Layers for screen rotation animation. We put these layers above + * WINDOW_FREEZE_LAYER so that screen freeze will cover all windows. + */ + int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER; } diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index ae54f51f35d1f78d3c36c49929c887f0cdf2c6f6..ffb7efa672432b7b4556df35c6905cfc41ff5c4a 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -135,7 +135,7 @@ public class WindowlessWindowManager implements IWindowSession { */ @Override public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs, - int viewVisibility, int displayId, InsetsState requestedVisibility, + int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession) @@ -181,10 +181,10 @@ public class WindowlessWindowManager implements IWindowSession { */ @Override public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs, - int viewVisibility, int displayId, int userId, InsetsState requestedVisibility, + int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { - return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibility, + return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities, outInputChannel, outInsetsState, outActiveControls); } @@ -454,7 +454,7 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public void insetsModified(android.view.IWindow window, android.view.InsetsState state) { + public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities) { } @Override @@ -498,4 +498,9 @@ public class WindowlessWindowManager implements IWindowSession { public void generateDisplayHash(IWindow window, Rect boundsInWindow, String hashAlgorithm, RemoteCallback callback) { } + + @Override + public boolean dropForAccessibility(IWindow window, int x, int y) { + return false; + } } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index f6d6fde6435fd37e441354325351a94083a3c681..3b65ffd3573008977018069360514eaa698e6d8e 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -612,6 +612,36 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 0x00000040; + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * A drag has started while accessibility is enabled. This is either via an + * AccessibilityAction, or via touch events. This is sent from the source that initiated the + * drag. + * + * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_START + */ + public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 0x00000080; + + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * A drag in with accessibility enabled has ended. This means the content has been + * successfully dropped. This is sent from the target that accepted the dragged content. + * + * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_DROP + */ + public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 0x00000100; + + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * A drag in with accessibility enabled has ended. This means the content has been + * unsuccessfully dropped, the user has canceled the action via an AccessibilityAction, or + * no drop has been detected within a timeout and the drag was automatically cancelled. This is + * sent from the source that initiated the drag. + * + * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_CANCEL + */ + public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 0x0000200; + /** * Change type for {@link #TYPE_WINDOWS_CHANGED} event: * The window was added. @@ -710,7 +740,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par CONTENT_CHANGE_TYPE_STATE_DESCRIPTION, CONTENT_CHANGE_TYPE_PANE_TITLE, CONTENT_CHANGE_TYPE_PANE_APPEARED, - CONTENT_CHANGE_TYPE_PANE_DISAPPEARED + CONTENT_CHANGE_TYPE_PANE_DISAPPEARED, + CONTENT_CHANGE_TYPE_DRAG_STARTED, + CONTENT_CHANGE_TYPE_DRAG_DROPPED, + CONTENT_CHANGE_TYPE_DRAG_CANCELLED }) public @interface ContentChangeTypes {} @@ -959,6 +992,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par case CONTENT_CHANGE_TYPE_PANE_APPEARED: return "CONTENT_CHANGE_TYPE_PANE_APPEARED"; case CONTENT_CHANGE_TYPE_PANE_DISAPPEARED: return "CONTENT_CHANGE_TYPE_PANE_DISAPPEARED"; + case CONTENT_CHANGE_TYPE_DRAG_STARTED: return "CONTENT_CHANGE_TYPE_DRAG_STARTED"; + case CONTENT_CHANGE_TYPE_DRAG_DROPPED: return "CONTENT_CHANGE_TYPE_DRAG_DROPPED"; + case CONTENT_CHANGE_TYPE_DRAG_CANCELLED: return "CONTENT_CHANGE_TYPE_DRAG_CANCELLED"; default: return Integer.toHexString(type); } } @@ -1017,6 +1053,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Sets the event type. * + * Note: An event must represent a single event type. * @param eventType The event type. * * @throws IllegalStateException If called from an AccessibilityService. diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index dd81dd93380bf0f52596bb5d243ab7f541843df1..aac09b8a3b578cf34dceaed49b07643590c6ed29 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -16,6 +16,9 @@ package android.view.accessibility; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK; + import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; import android.annotation.Nullable; @@ -86,6 +89,7 @@ public final class AccessibilityInteractionClient public static final int NO_ID = -1; public static final String CALL_STACK = "call_stack"; + public static final String IGNORE_CALL_STACK = "ignore_call_stack"; private static final String LOG_TAG = "AccessibilityInteractionClient"; @@ -121,6 +125,12 @@ public final class AccessibilityInteractionClient private volatile int mInteractionId = -1; private volatile int mCallingUid = Process.INVALID_UID; + // call stack for IAccessibilityInteractionConnectionCallback APIs. These callback APIs are + // shared by multiple requests APIs in IAccessibilityServiceConnection. To correctly log the + // request API which triggers the callback, we log trace entries for callback after the + // request API thread waiting for the callback returns. To log the correct callback stack in + // the request API thread, we save the callback stack in this member variables. + private List mCallStackOfCallback; private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult; @@ -307,18 +317,30 @@ public final class AccessibilityInteractionClient if (DEBUG) { Log.i(LOG_TAG, "Window cache hit"); } + if (shouldTraceClient()) { + logTraceClient(connection, "getWindow cache", + "connectionId=" + connectionId + ";accessibilityWindowId=" + + accessibilityWindowId + ";bypassCache=false"); + } return window; } if (DEBUG) { Log.i(LOG_TAG, "Window cache miss"); } } + final long identityToken = Binder.clearCallingIdentity(); try { window = connection.getWindow(accessibilityWindowId); } finally { Binder.restoreCallingIdentity(identityToken); } + if (shouldTraceClient()) { + logTraceClient(connection, "getWindow", "connectionId=" + connectionId + + ";accessibilityWindowId=" + accessibilityWindowId + ";bypassCache=" + + bypassCache); + } + if (window != null) { if (!bypassCache) { sAccessibilityCache.addWindow(window); @@ -368,6 +390,10 @@ public final class AccessibilityInteractionClient if (DEBUG) { Log.i(LOG_TAG, "Windows cache hit"); } + if (shouldTraceClient()) { + logTraceClient( + connection, "getWindows cache", "connectionId=" + connectionId); + } return windows; } if (DEBUG) { @@ -379,6 +405,9 @@ public final class AccessibilityInteractionClient } finally { Binder.restoreCallingIdentity(identityToken); } + if (shouldTraceClient()) { + logTraceClient(connection, "getWindows", "connectionId=" + connectionId); + } if (windows != null) { sAccessibilityCache.setWindowsOnAllDisplays(windows); return windows; @@ -472,6 +501,15 @@ public final class AccessibilityInteractionClient Log.i(LOG_TAG, "Node cache hit for " + idToString(accessibilityWindowId, accessibilityNodeId)); } + if (shouldTraceClient()) { + logTraceClient(connection, + "findAccessibilityNodeInfoByAccessibilityId cache", + "connectionId=" + connectionId + ";accessibilityWindowId=" + + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";bypassCache=" + bypassCache + + ";prefetchFlags=" + prefetchFlags + ";arguments=" + + arguments); + } return cachedInfo; } if (DEBUG) { @@ -488,6 +526,14 @@ public final class AccessibilityInteractionClient prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK; } final int interactionId = mInteractionIdCounter.getAndIncrement(); + if (shouldTraceClient()) { + logTraceClient(connection, "findAccessibilityNodeInfoByAccessibilityId", + "InteractionId:" + interactionId + "connectionId=" + connectionId + + ";accessibilityWindowId=" + accessibilityWindowId + + ";accessibilityNodeId=" + accessibilityNodeId + ";bypassCache=" + + bypassCache + ";prefetchFlags=" + prefetchFlags + ";arguments=" + + arguments); + } final String[] packageNames; final long identityToken = Binder.clearCallingIdentity(); try { @@ -500,16 +546,10 @@ public final class AccessibilityInteractionClient if (packageNames != null) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(interactionId); - if (mAccessibilityManager != null - && mAccessibilityManager.isAccessibilityTracingEnabled()) { - logTrace(connection, "findAccessibilityNodeInfoByAccessibilityId", - "InteractionId:" + interactionId + ";Result: " + info - + ";connectionId=" + connectionId - + ";accessibilityWindowId=" - + accessibilityWindowId + ";accessibilityNodeId=" - + accessibilityNodeId + ";bypassCache=" + bypassCache - + ";prefetchFlags=" + prefetchFlags - + ";arguments=" + arguments); + if (shouldTraceCallback()) { + logTraceCallback(connection, "findAccessibilityNodeInfoByAccessibilityId", + "InteractionId:" + interactionId + ";connectionId=" + + connectionId + ";Result: " + info); } if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0 && info != null) { @@ -571,6 +611,14 @@ public final class AccessibilityInteractionClient final String[] packageNames; final long identityToken = Binder.clearCallingIdentity(); try { + if (shouldTraceClient()) { + logTraceClient(connection, "findAccessibilityNodeInfosByViewId", + "InteractionId=" + interactionId + ";connectionId=" + connectionId + + ";accessibilityWindowId=" + accessibilityWindowId + + ";accessibilityNodeId=" + accessibilityNodeId + ";viewId=" + + viewId); + } + packageNames = connection.findAccessibilityNodeInfosByViewId( accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, Thread.currentThread().getId()); @@ -581,13 +629,10 @@ public final class AccessibilityInteractionClient if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - if (mAccessibilityManager != null - && mAccessibilityManager.isAccessibilityTracingEnabled()) { - logTrace(connection, "findAccessibilityNodeInfosByViewId", "InteractionId=" - + interactionId + ":Result: " + infos + ";connectionId=" - + connectionId + ";accessibilityWindowId=" + accessibilityWindowId - + ";accessibilityNodeId=" + accessibilityNodeId + ";viewId=" - + viewId); + if (shouldTraceCallback()) { + logTraceCallback(connection, "findAccessibilityNodeInfosByViewId", + "InteractionId=" + interactionId + ";connectionId=" + connectionId + + ":Result: " + infos); } if (infos != null) { finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, @@ -630,6 +675,12 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); + if (shouldTraceClient()) { + logTraceClient(connection, "findAccessibilityNodeInfosByText", + "InteractionId:" + interactionId + "connectionId=" + connectionId + + ";accessibilityWindowId=" + accessibilityWindowId + + ";accessibilityNodeId=" + accessibilityNodeId + ";text=" + text); + } final String[] packageNames; final long identityToken = Binder.clearCallingIdentity(); try { @@ -643,12 +694,10 @@ public final class AccessibilityInteractionClient if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - if (mAccessibilityManager != null - && mAccessibilityManager.isAccessibilityTracingEnabled()) { - logTrace(connection, "findAccessibilityNodeInfosByText", "InteractionId=" - + interactionId + ":Result: " + infos + ";connectionId=" - + connectionId + ";accessibilityWindowId=" + accessibilityWindowId - + ";accessibilityNodeId=" + accessibilityNodeId + ";text=" + text); + if (shouldTraceCallback()) { + logTraceCallback(connection, "findAccessibilityNodeInfosByText", + "InteractionId=" + interactionId + ";connectionId=" + connectionId + + ";Result: " + infos); } if (infos != null) { finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, @@ -690,6 +739,13 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); + if (shouldTraceClient()) { + logTraceClient(connection, "findFocus", + "InteractionId:" + interactionId + "connectionId=" + connectionId + + ";accessibilityWindowId=" + accessibilityWindowId + + ";accessibilityNodeId=" + accessibilityNodeId + ";focusType=" + + focusType); + } final String[] packageNames; final long identityToken = Binder.clearCallingIdentity(); try { @@ -703,13 +759,9 @@ public final class AccessibilityInteractionClient if (packageNames != null) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - if (mAccessibilityManager != null - && mAccessibilityManager.isAccessibilityTracingEnabled()) { - logTrace(connection, "findFocus", "InteractionId=" + interactionId - + ":Result: " + info + ";connectionId=" + connectionId - + ";accessibilityWindowId=" + accessibilityWindowId - + ";accessibilityNodeId=" + accessibilityNodeId + ";focusType=" - + focusType); + if (shouldTraceCallback()) { + logTraceCallback(connection, "findFocus", "InteractionId=" + interactionId + + ";connectionId=" + connectionId + ";Result:" + info); } finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames); return info; @@ -747,6 +799,13 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); + if (shouldTraceClient()) { + logTraceClient(connection, "focusSearch", + "InteractionId:" + interactionId + "connectionId=" + connectionId + + ";accessibilityWindowId=" + accessibilityWindowId + + ";accessibilityNodeId=" + accessibilityNodeId + ";direction=" + + direction); + } final String[] packageNames; final long identityToken = Binder.clearCallingIdentity(); try { @@ -761,13 +820,9 @@ public final class AccessibilityInteractionClient AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames); - if (mAccessibilityManager != null - && mAccessibilityManager.isAccessibilityTracingEnabled()) { - logTrace(connection, "focusSearch", "InteractionId=" + interactionId - + ":Result: " + info + ";connectionId=" + connectionId - + ";accessibilityWindowId=" + accessibilityWindowId - + ";accessibilityNodeId=" + accessibilityNodeId + ";direction=" - + direction); + if (shouldTraceCallback()) { + logTraceCallback(connection, "focusSearch", "InteractionId=" + interactionId + + ";connectionId=" + connectionId + ";Result:" + info); } return info; } @@ -803,6 +858,13 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); + if (shouldTraceClient()) { + logTraceClient(connection, "performAccessibilityAction", + "InteractionId:" + interactionId + "connectionId=" + connectionId + + ";accessibilityWindowId=" + accessibilityWindowId + + ";accessibilityNodeId=" + accessibilityNodeId + ";action=" + action + + ";arguments=" + arguments); + } final boolean success; final long identityToken = Binder.clearCallingIdentity(); try { @@ -816,13 +878,10 @@ public final class AccessibilityInteractionClient if (success) { final boolean result = getPerformAccessibilityActionResultAndClear(interactionId); - if (mAccessibilityManager != null - && mAccessibilityManager.isAccessibilityTracingEnabled()) { - logTrace(connection, "performAccessibilityAction", "InteractionId=" - + interactionId + ":Result: " + result + ";connectionId=" - + connectionId + ";accessibilityWindowId=" + accessibilityWindowId - + ";accessibilityNodeId=" + accessibilityNodeId + ";action=" - + action + ";arguments=" + arguments); + if (shouldTraceCallback()) { + logTraceCallback(connection, "performAccessibilityAction", + "InteractionId=" + interactionId + ";connectionId=" + connectionId + + ";Result: " + result); } return result; } @@ -886,6 +945,8 @@ public final class AccessibilityInteractionClient mFindAccessibilityNodeInfoResult = info; mInteractionId = interactionId; mCallingUid = Binder.getCallingUid(); + mCallStackOfCallback = new ArrayList( + Arrays.asList(Thread.currentThread().getStackTrace())); } mInstanceLock.notifyAll(); } @@ -936,6 +997,8 @@ public final class AccessibilityInteractionClient } mInteractionId = interactionId; mCallingUid = Binder.getCallingUid(); + mCallStackOfCallback = new ArrayList( + Arrays.asList(Thread.currentThread().getStackTrace())); } mInstanceLock.notifyAll(); } @@ -975,13 +1038,15 @@ public final class AccessibilityInteractionClient finalizeAndCacheAccessibilityNodeInfos( infos, connectionIdWaitingForPrefetchResultCopy, false, packageNamesForNextPrefetchResultCopy); - if (mAccessibilityManager != null - && mAccessibilityManager.isAccessibilityTracingEnabled()) { + if (shouldTraceCallback()) { logTrace(getConnection(connectionIdWaitingForPrefetchResultCopy), "setPrefetchAccessibilityNodeInfoResult", - "InteractionId:" + interactionId + ";Result: " + infos - + ";connectionId=" + connectionIdWaitingForPrefetchResultCopy, - Binder.getCallingUid()); + "InteractionId:" + interactionId + ";connectionId=" + + connectionIdWaitingForPrefetchResultCopy + ";Result: " + infos, + Binder.getCallingUid(), + Arrays.asList(Thread.currentThread().getStackTrace()), + new HashSet(Arrays.asList("getStackTrace")), + FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK); } } else if (DEBUG) { Log.w(LOG_TAG, "Prefetching for interaction with id " + interactionId + " dropped " @@ -1013,6 +1078,8 @@ public final class AccessibilityInteractionClient mPerformAccessibilityActionResult = succeeded; mInteractionId = interactionId; mCallingUid = Binder.getCallingUid(); + mCallStackOfCallback = new ArrayList( + Arrays.asList(Thread.currentThread().getStackTrace())); } mInstanceLock.notifyAll(); } @@ -1222,24 +1289,45 @@ public final class AccessibilityInteractionClient return true; } + private boolean shouldTraceClient() { + return (mAccessibilityManager != null) + && mAccessibilityManager.isA11yInteractionClientTraceEnabled(); + } + + private boolean shouldTraceCallback() { + return (mAccessibilityManager != null) + && mAccessibilityManager.isA11yInteractionConnectionCBTraceEnabled(); + } + private void logTrace( IAccessibilityServiceConnection connection, String method, String params, - int callingUid) { + int callingUid, List callStack, HashSet ignoreSet, + long logTypes) { try { Bundle b = new Bundle(); - ArrayList callStack = new ArrayList( - Arrays.asList(Thread.currentThread().getStackTrace())); - b.putSerializable(CALL_STACK, callStack); + b.putSerializable(CALL_STACK, new ArrayList(callStack)); + if (ignoreSet != null) { + b.putSerializable(IGNORE_CALL_STACK, ignoreSet); + } connection.logTrace(SystemClock.elapsedRealtimeNanos(), - LOG_TAG + ".callback for " + method, params, Process.myPid(), - Thread.currentThread().getId(), callingUid, b); + LOG_TAG + "." + method, + logTypes, params, Process.myPid(), Thread.currentThread().getId(), + callingUid, b); } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to log trace. " + e); } } - private void logTrace( + private void logTraceCallback( + IAccessibilityServiceConnection connection, String method, String params) { + logTrace(connection, method + " callback", params, mCallingUid, mCallStackOfCallback, + new HashSet(Arrays.asList("getStackTrace")), + FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK); + } + + private void logTraceClient( IAccessibilityServiceConnection connection, String method, String params) { - logTrace(connection, method, params, mCallingUid); + logTrace(connection, method, params, Binder.getCallingUid(), + Collections.emptyList(), null, FLAGS_ACCESSIBILITY_INTERACTION_CLIENT); } } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index f9cdbd322c260c1a2a629ebba399a7b90e2854c1..17fad7e57de77c9c585aacc2450e1005a9cc5305 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -111,7 +111,13 @@ public final class AccessibilityManager { public static final int STATE_FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000010; /** @hide */ - public static final int STATE_FLAG_ACCESSIBILITY_TRACING_ENABLED = 0x00000020; + public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED = 0x00000100; + /** @hide */ + public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED = 0x00000200; + /** @hide */ + public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED = 0x00000400; + /** @hide */ + public static final int STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED = 0x00000800; /** @hide */ public static final int DALTONIZER_DISABLED = -1; @@ -235,8 +241,8 @@ public final class AccessibilityManager { @UnsupportedAppUsage(trackingBug = 123768939L) boolean mIsHighTextContrastEnabled; - // Whether accessibility tracing is enabled or not - boolean mIsAccessibilityTracingEnabled = false; + // accessibility tracing state + int mAccessibilityTracingState = 0; AccessibilityPolicy mAccessibilityPolicy; @@ -1029,13 +1035,50 @@ public final class AccessibilityManager { } /** - * Gets accessibility tracing enabled state. + * Gets accessibility interaction connection tracing enabled state. + * + * @hide + */ + public boolean isA11yInteractionConnectionTraceEnabled() { + synchronized (mLock) { + return ((mAccessibilityTracingState + & STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED) != 0); + } + } + + /** + * Gets accessibility interaction connection callback tracing enabled state. + * + * @hide + */ + public boolean isA11yInteractionConnectionCBTraceEnabled() { + synchronized (mLock) { + return ((mAccessibilityTracingState + & STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED) != 0); + } + } + + /** + * Gets accessibility interaction client tracing enabled state. + * + * @hide + */ + public boolean isA11yInteractionClientTraceEnabled() { + synchronized (mLock) { + return ((mAccessibilityTracingState + & STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED) != 0); + } + } + + /** + * Gets accessibility service tracing enabled state. * * @hide */ - public boolean isAccessibilityTracingEnabled() { + public boolean isA11yServiceTraceEnabled() { synchronized (mLock) { - return mIsAccessibilityTracingEnabled; + return ((mAccessibilityTracingState + & STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED) != 0); } } @@ -1233,8 +1276,6 @@ public final class AccessibilityManager { (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; final boolean highTextContrastEnabled = (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0; - final boolean accessibilityTracingEnabled = - (stateFlags & STATE_FLAG_ACCESSIBILITY_TRACING_ENABLED) != 0; final boolean wasEnabled = isEnabled(); final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; @@ -1257,7 +1298,7 @@ public final class AccessibilityManager { notifyHighTextContrastStateChanged(); } - updateAccessibilityTracingState(accessibilityTracingEnabled); + updateAccessibilityTracingState(stateFlags); } /** @@ -1715,11 +1756,11 @@ public final class AccessibilityManager { } /** - * Update mIsAccessibilityTracingEnabled. + * Update mAccessibilityTracingState. */ - private void updateAccessibilityTracingState(boolean enabled) { + private void updateAccessibilityTracingState(int stateFlag) { synchronized (mLock) { - mIsAccessibilityTracingEnabled = enabled; + mAccessibilityTracingState = stateFlag; } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 085eb81182f1ebba0446295650a547bda5c5afb9..587a27074544c4ae3be5fe643bbf57fbb270944c 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ClipData; import android.graphics.Rect; import android.graphics.Region; import android.os.Build; @@ -4353,6 +4354,14 @@ public class AccessibilityNodeInfo implements Parcelable { case R.id.accessibilityActionImeEnter: return "ACTION_IME_ENTER"; default: + // TODO(197520937): Use finalized constants in switch + if (action == R.id.accessibilityActionDragStart) { + return "ACTION_DRAG"; + } else if (action == R.id.accessibilityActionDragCancel) { + return "ACTION_CANCEL_DRAG"; + } else if (action == R.id.accessibilityActionDragDrop) { + return "ACTION_DROP"; + } return "ACTION_UNKNOWN"; } } @@ -4995,6 +5004,46 @@ public class AccessibilityNodeInfo implements Parcelable { @NonNull public static final AccessibilityAction ACTION_IME_ENTER = new AccessibilityAction(R.id.accessibilityActionImeEnter); + /** + * Action to start a drag. + *

    + * This action initiates a drag & drop within the system. The source's dragged content is + * prepared before the drag begins. In View, this action should prepare the arguments to + * {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)} and then + * call {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)}. The + * equivalent should be performed for other UI toolkits. + *

    + * + * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_STARTED + */ + @NonNull public static final AccessibilityAction ACTION_DRAG_START = + new AccessibilityAction(R.id.accessibilityActionDragStart); + + /** + * Action to trigger a drop of the content being dragged. + *

    + * This action is added to potential drop targets if the source started a drag with + * {@link #ACTION_DRAG_START}. In View, these targets are Views that accepted + * {@link android.view.DragEvent#ACTION_DRAG_STARTED} and have an + * {@link View.OnDragListener}. + *

    + * + * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_DROPPED + */ + @NonNull public static final AccessibilityAction ACTION_DRAG_DROP = + new AccessibilityAction(R.id.accessibilityActionDragDrop); + + /** + * Action to cancel a drag. + *

    + * This action is added to the source that started a drag with {@link #ACTION_DRAG_START}. + *

    + * + * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_CANCELLED + */ + @NonNull public static final AccessibilityAction ACTION_DRAG_CANCEL = + new AccessibilityAction(R.id.accessibilityActionDragCancel); + private final int mActionId; private final CharSequence mLabel; diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index edcb59a79c70c2fb4b3334bcfcb7c680b3970b73..76e226163ca1c9a1028b80badcdd8e0bcd9ec730 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -19,6 +19,7 @@ package android.view.accessibility; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.ActivityTaskManager; import android.graphics.Rect; import android.graphics.Region; import android.os.Parcel; @@ -114,6 +115,7 @@ public final class AccessibilityWindowInfo implements Parcelable { private int mBooleanProperties; private int mId = UNDEFINED_WINDOW_ID; private int mParentId = UNDEFINED_WINDOW_ID; + private int mTaskId = ActivityTaskManager.INVALID_TASK_ID; private Region mRegionInScreen = new Region(); private LongArray mChildIds; private CharSequence mTitle; @@ -306,6 +308,28 @@ public final class AccessibilityWindowInfo implements Parcelable { mId = id; } + /** + * Gets the task ID. + * + * @return The task ID. + * + * @hide + */ + public int getTaskId() { + return mTaskId; + } + + /** + * Sets the task ID. + * + * @param taskId The task ID. + * + * @hide + */ + public void setTaskId(int taskId) { + mTaskId = taskId; + } + /** * Sets the unique id of the IAccessibilityServiceConnection over which * this instance can send requests to the system. @@ -578,6 +602,7 @@ public final class AccessibilityWindowInfo implements Parcelable { parcel.writeInt(mBooleanProperties); parcel.writeInt(mId); parcel.writeInt(mParentId); + parcel.writeInt(mTaskId); mRegionInScreen.writeToParcel(parcel, flags); parcel.writeCharSequence(mTitle); parcel.writeLong(mAnchorId); @@ -608,6 +633,7 @@ public final class AccessibilityWindowInfo implements Parcelable { mBooleanProperties = other.mBooleanProperties; mId = other.mId; mParentId = other.mParentId; + mTaskId = other.mTaskId; mRegionInScreen.set(other.mRegionInScreen); mTitle = other.mTitle; mAnchorId = other.mAnchorId; @@ -631,6 +657,7 @@ public final class AccessibilityWindowInfo implements Parcelable { mBooleanProperties = parcel.readInt(); mId = parcel.readInt(); mParentId = parcel.readInt(); + mTaskId = parcel.readInt(); mRegionInScreen = Region.CREATOR.createFromParcel(parcel); mTitle = parcel.readCharSequence(); mAnchorId = parcel.readLong(); @@ -676,6 +703,7 @@ public final class AccessibilityWindowInfo implements Parcelable { builder.append("title=").append(mTitle); builder.append(", displayId=").append(mDisplayId); builder.append(", id=").append(mId); + builder.append(", taskId=").append(mTaskId); builder.append(", type=").append(typeToString(mType)); builder.append(", layer=").append(mLayer); builder.append(", region=").append(mRegionInScreen); @@ -719,6 +747,7 @@ public final class AccessibilityWindowInfo implements Parcelable { mBooleanProperties = 0; mId = UNDEFINED_WINDOW_ID; mParentId = UNDEFINED_WINDOW_ID; + mTaskId = ActivityTaskManager.INVALID_TASK_ID; mRegionInScreen.setEmpty(); mChildIds = null; mConnectionId = UNDEFINED_WINDOW_ID; diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java index 9998fbc02d126568addf299ea6e72367dddcc367..0da54e57ed7990f2dd18f375de77abc85af5e3fb 100644 --- a/core/java/android/view/contentcapture/ContentCaptureContext.java +++ b/core/java/android/view/contentcapture/ContentCaptureContext.java @@ -27,6 +27,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.LocusId; import android.os.Bundle; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.Display; @@ -105,6 +106,7 @@ public final class ContentCaptureContext implements Parcelable { private final int mFlags; private final int mDisplayId; private final ActivityId mActivityId; + private final IBinder mWindowToken; // Fields below are set by the service upon "delivery" and are not marshalled in the parcel private int mParentSessionId = NO_SESSION_ID; @@ -112,7 +114,7 @@ public final class ContentCaptureContext implements Parcelable { /** @hide */ public ContentCaptureContext(@Nullable ContentCaptureContext clientContext, @NonNull ActivityId activityId, @NonNull ComponentName componentName, int displayId, - int flags) { + IBinder windowToken, int flags) { if (clientContext != null) { mHasClientContext = true; mExtras = clientContext.mExtras; @@ -126,6 +128,7 @@ public final class ContentCaptureContext implements Parcelable { mFlags = flags; mDisplayId = displayId; mActivityId = activityId; + mWindowToken = windowToken; } private ContentCaptureContext(@NonNull Builder builder) { @@ -137,6 +140,7 @@ public final class ContentCaptureContext implements Parcelable { mFlags = 0; mDisplayId = Display.INVALID_DISPLAY; mActivityId = null; + mWindowToken = null; } /** @hide */ @@ -148,6 +152,7 @@ public final class ContentCaptureContext implements Parcelable { mFlags = original.mFlags | extraFlags; mDisplayId = original.mDisplayId; mActivityId = original.mActivityId; + mWindowToken = original.mWindowToken; } /** @@ -229,6 +234,20 @@ public final class ContentCaptureContext implements Parcelable { return mDisplayId; } + /** + * Gets the window token of the activity associated with this context. + * + *

    The token can be used to attach relevant overlay views to the activity's window. This can + * be done through {@link android.view.WindowManager.LayoutParams#token}. + * + * @hide + */ + @SystemApi + @Nullable + public IBinder getWindowToken() { + return mWindowToken; + } + /** * Gets the flags associated with this context. * @@ -328,6 +347,7 @@ public final class ContentCaptureContext implements Parcelable { } pw.print(", activityId="); pw.print(mActivityId); pw.print(", displayId="); pw.print(mDisplayId); + pw.print(", windowToken="); pw.print(mWindowToken); if (mParentSessionId != NO_SESSION_ID) { pw.print(", parentId="); pw.print(mParentSessionId); } @@ -352,6 +372,7 @@ public final class ContentCaptureContext implements Parcelable { builder.append("act=").append(ComponentName.flattenToShortString(mComponentName)) .append(", activityId=").append(mActivityId) .append(", displayId=").append(mDisplayId) + .append(", windowToken=").append(mWindowToken) .append(", flags=").append(mFlags); } else { builder.append("id=").append(mId); @@ -381,6 +402,7 @@ public final class ContentCaptureContext implements Parcelable { parcel.writeParcelable(mComponentName, flags); if (fromServer()) { parcel.writeInt(mDisplayId); + parcel.writeStrongBinder(mWindowToken); parcel.writeInt(mFlags); mActivityId.writeToParcel(parcel, flags); } @@ -411,11 +433,12 @@ public final class ContentCaptureContext implements Parcelable { return clientContext; } else { final int displayId = parcel.readInt(); + final IBinder windowToken = parcel.readStrongBinder(); final int flags = parcel.readInt(); final ActivityId activityId = new ActivityId(parcel); return new ContentCaptureContext(clientContext, activityId, componentName, - displayId, flags); + displayId, windowToken, flags); } } diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index ce6d034c585eca49681f908143cc3e51608d7406..4b2d3a97bed2d01ab209cdd01482b4541b034c3b 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -24,6 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.graphics.Insets; +import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.text.Selection; @@ -122,6 +123,12 @@ public final class ContentCaptureEvent implements Parcelable { */ public static final int TYPE_VIEW_INSETS_CHANGED = 9; + /** + * Called before {@link #TYPE_VIEW_TREE_APPEARING}, or after the size of the window containing + * the views changed. + */ + public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10; + /** @hide */ @IntDef(prefix = { "TYPE_" }, value = { TYPE_VIEW_APPEARED, @@ -132,7 +139,8 @@ public final class ContentCaptureEvent implements Parcelable { TYPE_CONTEXT_UPDATED, TYPE_SESSION_PAUSED, TYPE_SESSION_RESUMED, - TYPE_VIEW_INSETS_CHANGED + TYPE_VIEW_INSETS_CHANGED, + TYPE_WINDOW_BOUNDS_CHANGED, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType{} @@ -150,6 +158,7 @@ public final class ContentCaptureEvent implements Parcelable { private int mParentSessionId = NO_SESSION_ID; private @Nullable ContentCaptureContext mClientContext; private @Nullable Insets mInsets; + private @Nullable Rect mBounds; private int mComposingStart = MAX_INVALID_VALUE; private int mComposingEnd = MAX_INVALID_VALUE; @@ -346,6 +355,13 @@ public final class ContentCaptureEvent implements Parcelable { return this; } + /** @hide */ + @NonNull + public ContentCaptureEvent setBounds(@NonNull Rect bounds) { + mBounds = bounds; + return this; + } + /** * Gets the type of the event. * @@ -418,6 +434,16 @@ public final class ContentCaptureEvent implements Parcelable { return mInsets; } + /** + * Gets the {@link Rect} bounds of the window associated with the event. Valid bounds will only + * be returned if the type of the event is {@link #TYPE_WINDOW_BOUNDS_CHANGED}, otherwise they + * will be null. + */ + @Nullable + public Rect getBounds() { + return mBounds; + } + /** * Merges event of the same type, either {@link #TYPE_VIEW_TEXT_CHANGED} * or {@link #TYPE_VIEW_DISAPPEARED}. @@ -489,6 +515,9 @@ public final class ContentCaptureEvent implements Parcelable { if (mInsets != null) { pw.print(", insets="); pw.println(mInsets); } + if (mBounds != null) { + pw.print(", bounds="); pw.println(mBounds); + } if (mComposingStart > MAX_INVALID_VALUE) { pw.print(", composing("); pw.print(mComposingStart); pw.print(", "); pw.print(mComposingEnd); pw.print(")"); @@ -533,6 +562,9 @@ public final class ContentCaptureEvent implements Parcelable { if (mInsets != null) { string.append(", insets=").append(mInsets); } + if (mBounds != null) { + string.append(", bounds=").append(mBounds); + } if (mComposingStart > MAX_INVALID_VALUE) { string.append(", composing=[") .append(mComposingStart).append(",").append(mComposingEnd).append("]"); @@ -568,6 +600,9 @@ public final class ContentCaptureEvent implements Parcelable { if (mType == TYPE_VIEW_INSETS_CHANGED) { parcel.writeParcelable(mInsets, flags); } + if (mType == TYPE_WINDOW_BOUNDS_CHANGED) { + parcel.writeParcelable(mBounds, flags); + } if (mType == TYPE_VIEW_TEXT_CHANGED) { parcel.writeInt(mComposingStart); parcel.writeInt(mComposingEnd); @@ -608,6 +643,9 @@ public final class ContentCaptureEvent implements Parcelable { if (type == TYPE_VIEW_INSETS_CHANGED) { event.setInsets(parcel.readParcelable(null)); } + if (type == TYPE_WINDOW_BOUNDS_CHANGED) { + event.setBounds(parcel.readParcelable(null)); + } if (type == TYPE_VIEW_TEXT_CHANGED) { event.setComposingIndex(parcel.readInt(), parcel.readInt()); event.restoreComposingSpan(); @@ -649,6 +687,8 @@ public final class ContentCaptureEvent implements Parcelable { return "CONTEXT_UPDATED"; case TYPE_VIEW_INSETS_CHANGED: return "VIEW_INSETS_CHANGED"; + case TYPE_WINDOW_BOUNDS_CHANGED: + return "TYPE_WINDOW_BOUNDS_CHANGED"; default: return "UKNOWN_TYPE: " + type; } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 9241c3074ddd497b01fb85d314bca8644e496c24..cd3c8c9c48f2ae244af63e029bc67a2612633e22 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -483,6 +483,8 @@ public final class ContentCaptureManager { /** * Returns the component name of the system service that is consuming the captured events for * the current user. + * + * @throws RuntimeException if getting the component name is timed out. */ @Nullable public ComponentName getServiceComponentName() { diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 4cf553207b6ebf3790f1d7d2ab73d719f0db2838..98ef4e7ae6faea311001a12a6d0889a0257c3544 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -26,6 +26,7 @@ import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_INSETS_C import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_WINDOW_BOUNDS_CHANGED; import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; @@ -38,6 +39,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.ParceledListSlice; import android.graphics.Insets; +import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -776,6 +778,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { .setClientContext(context), FORCE_FLUSH)); } + /** public because is also used by ViewRootImpl */ + public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { + mHandler.post(() -> sendEvent( + new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) + .setBounds(bounds) + )); + } + @Override void dump(@NonNull String prefix, @NonNull PrintWriter pw) { super.dump(prefix, pw); diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index d2db0df6c597cdbec603cdcb65a97406c4d3a653..5b2068ff16cdc5b219c3b20bca2befadbc932b0d 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -96,8 +96,6 @@ public interface InputMethod { * * @param token special token for the system to identify * {@link InputMethodService} - * @param displayId The id of the display that current IME shown. - * Used for {{@link #updateInputMethodDisplay(int)}} * @param privilegedOperations IPC endpoint to do some privileged * operations that are allowed only to the * current IME. @@ -105,9 +103,8 @@ public interface InputMethod { * @hide */ @MainThread - default void initializeInternal(IBinder token, int displayId, + default void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations, int configChanges) { - updateInputMethodDisplay(displayId); attachToken(token); } @@ -142,16 +139,6 @@ public interface InputMethod { @MainThread public void attachToken(IBinder token); - /** - * Update context display according to given displayId. - * - * @param displayId The id of the display that need to update for context. - * @hide - */ - @MainThread - default void updateInputMethodDisplay(int displayId) { - } - /** * Bind a new application environment in to the input method, so that it * can later start and stop input processing. diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 42d77cd096893a73d1708239fe56bd60196c4696..e6f103e6d53b15dfe436617e1e3f1ca738764037 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2154,6 +2154,7 @@ public final class InputMethodManager { * @hide */ public boolean requestImeShow(IBinder windowToken) { + checkFocus(); synchronized (mH) { final View servedView = getServedViewLocked(); if (servedView == null || servedView.getWindowToken() != windowToken) { diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index d078c2cfbfd137e287c4f30b4b0e67604308f7e8..fb534c7885e42a13d56e4fc85d1b1cd2e1feb1c6 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -110,11 +110,10 @@ public class UiTranslationController { public void updateUiTranslationState(@UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, List views, UiTranslationSpec uiTranslationSpec) { - if (!mActivity.isResumed() && (state == STATE_UI_TRANSLATION_STARTED - || state == STATE_UI_TRANSLATION_RESUMED)) { + if (mActivity.isDestroyed()) { + Log.i(TAG, "Cannot update " + stateToString(state) + " for destroyed " + mActivity); return; } - Log.i(TAG, "updateUiTranslationState state: " + stateToString(state) + (DEBUG ? (", views: " + views + ", spec: " + uiTranslationSpec) : "")); synchronized (mLock) { @@ -342,10 +341,8 @@ public class UiTranslationController { */ private void onVirtualViewTranslationCompleted( SparseArray> translatedResult) { - if (!mActivity.isResumed()) { - if (DEBUG) { - Log.v(TAG, "onTranslationCompleted: Activity is not resumed."); - } + if (mActivity.isDestroyed()) { + Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed."); return; } synchronized (mLock) { @@ -372,6 +369,10 @@ public class UiTranslationController { Log.v(TAG, "onVirtualViewTranslationCompleted: received response for " + "AutofillId " + autofillId); } + view.onVirtualViewTranslationResponses(virtualChildResponse); + if (mCurrentState == STATE_UI_TRANSLATION_PAUSED) { + return; + } mActivity.runOnUiThread(() -> { if (view.getViewTranslationCallback() == null) { if (DEBUG) { @@ -380,7 +381,6 @@ public class UiTranslationController { } return; } - view.onVirtualViewTranslationResponses(virtualChildResponse); if (view.getViewTranslationCallback() != null) { view.getViewTranslationCallback().onShowTranslation(view); } @@ -393,10 +393,8 @@ public class UiTranslationController { * The method is used to handle the translation result for non-vertual views. */ private void onTranslationCompleted(SparseArray translatedResult) { - if (!mActivity.isResumed()) { - if (DEBUG) { - Log.v(TAG, "onTranslationCompleted: Activity is not resumed."); - } + if (mActivity.isDestroyed()) { + Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed."); return; } final int resultCount = translatedResult.size(); @@ -430,12 +428,17 @@ public class UiTranslationController { + " may be gone."); continue; } + int currentState; + currentState = mCurrentState; mActivity.runOnUiThread(() -> { ViewTranslationCallback callback = view.getViewTranslationCallback(); if (view.getViewTranslationResponse() != null && view.getViewTranslationResponse().equals(response)) { if (callback instanceof TextViewTranslationCallback) { - if (((TextViewTranslationCallback) callback).isShowingTranslation()) { + TextViewTranslationCallback textViewCallback = + (TextViewTranslationCallback) callback; + if (textViewCallback.isShowingTranslation() + || textViewCallback.isAnimationRunning()) { if (DEBUG) { Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId + ". Ignoring."); @@ -463,6 +466,9 @@ public class UiTranslationController { callback.enableContentPadding(); } view.onViewTranslationResponse(response); + if (currentState == STATE_UI_TRANSLATION_PAUSED) { + return; + } callback.onShowTranslation(view); }); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 721260e8cafe67d61361bc8c9564386395c1c8e8..1244d750e1642162d3a85131395120a45d7ca213 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -3682,14 +3682,14 @@ public abstract class AbsListView extends AdapterView implements Te if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } - invalidateTopGlow(); + invalidateEdgeEffects(); } else if (incrementalDeltaY < 0) { mEdgeGlowBottom.onPullDistance((float) overscroll / getHeight(), 1.f - (float) x / getWidth()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } - invalidateBottomGlow(); + invalidateEdgeEffects(); } } } @@ -3729,7 +3729,7 @@ public abstract class AbsListView extends AdapterView implements Te if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } - invalidateTopGlow(); + invalidateEdgeEffects(); } else if (rawDeltaY < 0) { mEdgeGlowBottom.onPullDistance( (float) -overScrollDistance / getHeight(), @@ -3737,7 +3737,7 @@ public abstract class AbsListView extends AdapterView implements Te if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } - invalidateBottomGlow(); + invalidateEdgeEffects(); } } } @@ -3783,17 +3783,21 @@ public abstract class AbsListView extends AdapterView implements Te // First allow releasing existing overscroll effect: float consumed = 0; if (mEdgeGlowTop.getDistance() != 0) { - consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(), - (float) x / getWidth()); - if (consumed != 0f) { - invalidateTopGlow(); + if (canScrollUp()) { + mEdgeGlowTop.onRelease(); + } else { + consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(), + (float) x / getWidth()); } + invalidateEdgeEffects(); } else if (mEdgeGlowBottom.getDistance() != 0) { - consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(), - 1f - (float) x / getWidth()); - if (consumed != 0f) { - invalidateBottomGlow(); + if (canScrollDown()) { + mEdgeGlowBottom.onRelease(); + } else { + consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(), + 1f - (float) x / getWidth()); } + invalidateEdgeEffects(); } int pixelsConsumed = Math.round(consumed * getHeight()); return deltaY - pixelsConsumed; @@ -3803,30 +3807,16 @@ public abstract class AbsListView extends AdapterView implements Te * @return true if either the top or bottom edge glow is currently active or * false if it has no value to release. */ - private boolean isGlowActive() { - return mEdgeGlowBottom.getDistance() != 0 || mEdgeGlowTop.getDistance() != 0; - } - - private void invalidateTopGlow() { - if (!shouldDisplayEdgeEffects()) { - return; - } - final boolean clipToPadding = getClipToPadding(); - final int top = clipToPadding ? mPaddingTop : 0; - final int left = clipToPadding ? mPaddingLeft : 0; - final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth(); - invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight()); + private boolean doesTouchStopStretch() { + return (mEdgeGlowBottom.getDistance() != 0 && !canScrollDown()) + || (mEdgeGlowTop.getDistance() != 0 && !canScrollUp()); } - private void invalidateBottomGlow() { + private void invalidateEdgeEffects() { if (!shouldDisplayEdgeEffects()) { return; } - final boolean clipToPadding = getClipToPadding(); - final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight(); - final int left = clipToPadding ? mPaddingLeft : 0; - final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth(); - invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom); + invalidate(); } @Override @@ -4469,7 +4459,7 @@ public abstract class AbsListView extends AdapterView implements Te final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY; canvas.translate(translateX, edgeY); if (mEdgeGlowTop.draw(canvas)) { - invalidateTopGlow(); + invalidateEdgeEffects(); } canvas.restoreToCount(restoreCount); } @@ -4483,7 +4473,7 @@ public abstract class AbsListView extends AdapterView implements Te canvas.translate(edgeX, edgeY); canvas.rotate(180, width, 0); if (mEdgeGlowBottom.draw(canvas)) { - invalidateBottomGlow(); + invalidateEdgeEffects(); } canvas.restoreToCount(restoreCount); } @@ -4573,7 +4563,7 @@ public abstract class AbsListView extends AdapterView implements Te mActivePointerId = ev.getPointerId(0); int motionPosition = findMotionRow(y); - if (isGlowActive()) { + if (doesTouchStopStretch()) { // Pressed during edge effect, so this is considered the same as a fling catch. touchMode = mTouchMode = TOUCH_MODE_FLING; } else if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) { @@ -6579,7 +6569,7 @@ public abstract class AbsListView extends AdapterView implements Te */ public void setBottomEdgeEffectColor(@ColorInt int color) { mEdgeGlowBottom.setColor(color); - invalidateBottomGlow(); + invalidateEdgeEffects(); } /** @@ -6593,7 +6583,7 @@ public abstract class AbsListView extends AdapterView implements Te */ public void setTopEdgeEffectColor(@ColorInt int color) { mEdgeGlowTop.setColor(color); - invalidateTopGlow(); + invalidateEdgeEffects(); } /** diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index fe5eb085dc5c0d489eb0f344e5a3481e6f1fe517..1784dcfc505fe56349d825ceb3496e68a570ed9f 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -34,6 +34,7 @@ import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.Application; +import android.app.LoadedApk; import android.app.PendingIntent; import android.app.RemoteInput; import android.appwidget.AppWidgetHostView; @@ -299,6 +300,13 @@ public class RemoteViews implements Parcelable, Filter { */ public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4; + /** + * A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is + * intentionally a different instance in order to trick Bundle reader so that it doesn't allow + * lazy initialization. + */ + private static final Parcel.ReadWriteHelper ALTERNATIVE_DEFAULT = new Parcel.ReadWriteHelper(); + /** * Used to restrict the views which can be inflated * @@ -337,7 +345,10 @@ public class RemoteViews implements Parcelable, Filter { * Maps bitmaps to unique indicies to avoid Bitmap duplication. */ @UnsupportedAppUsage - private BitmapCache mBitmapCache; + private BitmapCache mBitmapCache = new BitmapCache(); + + /** Cache of ApplicationInfos used by collection items. */ + private ApplicationInfoCache mApplicationInfoCache = new ApplicationInfoCache(); /** * Indicates whether or not this RemoteViews object is contained as a child of any other @@ -575,7 +586,7 @@ public class RemoteViews implements Parcelable, Filter { return 0; } - public void setBitmapCache(BitmapCache bitmapCache) { + public void setHierarchyRootData(HierarchyRootData root) { // Do nothing } @@ -604,14 +615,6 @@ public class RemoteViews implements Parcelable, Filter { return false; } - /** - * Overridden by subclasses which have (or inherit) an ApplicationInfo instance - * as member variable - */ - public boolean hasSameAppInfo(ApplicationInfo parentInfo) { - return true; - } - public void visitUris(@NonNull Consumer visitor) { // Nothing to visit by default } @@ -689,9 +692,8 @@ public class RemoteViews implements Parcelable, Filter { } } - // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache - mBitmapCache = new BitmapCache(); - setBitmapCache(mBitmapCache); + // Because pruning can remove the need for bitmaps, we reconstruct the caches. + reconstructCaches(); } /** @@ -937,23 +939,70 @@ public class RemoteViews implements Parcelable, Filter { ArrayList list; } - private static class SetRemoteCollectionItemListAdapterAction extends Action { + /** + * Cache of {@link ApplicationInfo}s that can be used to ensure that the same + * {@link ApplicationInfo} instance is used throughout the RemoteViews. + */ + private static class ApplicationInfoCache { + private final Map, ApplicationInfo> mPackageUserToApplicationInfo; + + ApplicationInfoCache() { + mPackageUserToApplicationInfo = new ArrayMap<>(); + } + + /** + * Adds the {@link ApplicationInfo} to the cache if it's not present. Returns either the + * provided {@code applicationInfo} or a previously added value with the same package name + * and uid. + */ + @Nullable + ApplicationInfo getOrPut(@Nullable ApplicationInfo applicationInfo) { + Pair key = getPackageUserKey(applicationInfo); + if (key == null) return null; + return mPackageUserToApplicationInfo.computeIfAbsent(key, ignored -> applicationInfo); + } + + /** Puts the {@link ApplicationInfo} in the cache, replacing any previously stored value. */ + void put(@Nullable ApplicationInfo applicationInfo) { + Pair key = getPackageUserKey(applicationInfo); + if (key == null) return; + mPackageUserToApplicationInfo.put(key, applicationInfo); + } + + /** + * Returns the currently stored {@link ApplicationInfo} from the cache matching + * {@code applicationInfo}, or null if there wasn't any. + */ + @Nullable ApplicationInfo get(@Nullable ApplicationInfo applicationInfo) { + Pair key = getPackageUserKey(applicationInfo); + if (key == null) return null; + return mPackageUserToApplicationInfo.get(key); + } + } + + private class SetRemoteCollectionItemListAdapterAction extends Action { private final RemoteCollectionItems mItems; SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) { viewId = id; mItems = items; + mItems.setHierarchyRootData(getHierarchyRootData()); } SetRemoteCollectionItemListAdapterAction(Parcel parcel) { viewId = parcel.readInt(); - mItems = parcel.readTypedObject(RemoteCollectionItems.CREATOR); + mItems = new RemoteCollectionItems(parcel, getHierarchyRootData()); + } + + @Override + public void setHierarchyRootData(HierarchyRootData rootData) { + mItems.setHierarchyRootData(rootData); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(viewId); - dest.writeTypedObject(mItems, flags); + mItems.writeToParcel(dest, flags, /* attached= */ true); } @Override @@ -1601,8 +1650,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void setBitmapCache(BitmapCache bitmapCache) { - bitmapId = bitmapCache.getBitmapId(bitmap); + public void setHierarchyRootData(HierarchyRootData rootData) { + bitmapId = rootData.mBitmapCache.getBitmapId(bitmap); } @Override @@ -1814,7 +1863,18 @@ public class RemoteViews implements Parcelable, Filter { this.value = in.readTypedObject(Bitmap.CREATOR); break; case BUNDLE: - this.value = in.readBundle(); + // Because we use Parcel.allowSquashing() when writing, and that affects + // how the contents of Bundles are written, we need to ensure the bundle is + // unparceled immediately, not lazily. Setting a custom ReadWriteHelper + // just happens to have that effect on Bundle.readFromParcel(). + // TODO(b/212731590): build this state tracking into Bundle + if (in.hasReadWriteHelper()) { + this.value = in.readBundle(); + } else { + in.setReadWriteHelper(ALTERNATIVE_DEFAULT); + this.value = in.readBundle(); + in.setReadWriteHelper(null); + } break; case INTENT: this.value = in.readTypedObject(Intent.CREATOR); @@ -2219,15 +2279,6 @@ public class RemoteViews implements Parcelable, Filter { } } - private void configureRemoteViewsAsChild(RemoteViews rv) { - rv.setBitmapCache(mBitmapCache); - rv.setNotRoot(); - } - - void setNotRoot() { - mIsRoot = false; - } - private static boolean hasStableId(View view) { Object tag = view.getTag(com.android.internal.R.id.remote_views_stable_id); return tag != null; @@ -2301,17 +2352,14 @@ public class RemoteViews implements Parcelable, Filter { mNestedViews = nestedViews; mIndex = index; mStableId = stableId; - if (nestedViews != null) { - configureRemoteViewsAsChild(nestedViews); - } + nestedViews.configureAsChild(getHierarchyRootData()); } - ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, - int depth, Map classCookies) { + ViewGroupActionAdd(Parcel parcel, ApplicationInfo info, int depth) { viewId = parcel.readInt(); mIndex = parcel.readInt(); mStableId = parcel.readInt(); - mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies); + mNestedViews = new RemoteViews(parcel, getHierarchyRootData(), info, depth); mNestedViews.addFlags(mApplyFlags); } @@ -2323,8 +2371,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public boolean hasSameAppInfo(ApplicationInfo parentInfo) { - return mNestedViews.hasSameAppInfo(parentInfo); + public void setHierarchyRootData(HierarchyRootData root) { + mNestedViews.configureAsChild(root); } private int findViewIndexToRecycle(ViewGroup target, RemoteViews newContent) { @@ -2492,11 +2540,6 @@ public class RemoteViews implements Parcelable, Filter { }; } - @Override - public void setBitmapCache(BitmapCache bitmapCache) { - mNestedViews.setBitmapCache(bitmapCache); - } - @Override public int mergeBehavior() { return MERGE_APPEND; @@ -3504,8 +3547,7 @@ public class RemoteViews implements Parcelable, Filter { protected RemoteViews(ApplicationInfo application, @LayoutRes int layoutId) { mApplication = application; mLayoutId = layoutId; - mBitmapCache = new BitmapCache(); - mClassCookies = null; + mApplicationInfoCache.put(application); } private boolean hasMultipleLayouts() { @@ -3561,12 +3603,10 @@ public class RemoteViews implements Parcelable, Filter { mLandscape = landscape; mPortrait = portrait; - mBitmapCache = new BitmapCache(); - configureRemoteViewsAsChild(landscape); - configureRemoteViewsAsChild(portrait); - mClassCookies = (portrait.mClassCookies != null) ? portrait.mClassCookies : landscape.mClassCookies; + + configureDescendantsAsChildren(); } /** @@ -3592,10 +3632,12 @@ public class RemoteViews implements Parcelable, Filter { throw new IllegalArgumentException("Too many RemoteViews in constructor"); } if (remoteViews.size() == 1) { - initializeFrom(remoteViews.values().iterator().next()); + // If the map only contains a single mapping, treat this as if that RemoteViews was + // passed as the top-level RemoteViews. + RemoteViews single = remoteViews.values().iterator().next(); + initializeFrom(single, /* hierarchyRoot= */ single); return; } - mBitmapCache = new BitmapCache(); mClassCookies = initializeSizedRemoteViews( remoteViews.entrySet().stream().map( entry -> { @@ -3610,6 +3652,8 @@ public class RemoteViews implements Parcelable, Filter { mLayoutId = smallestView.mLayoutId; mViewId = smallestView.mViewId; mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; + + configureDescendantsAsChildren(); } // Initialize mSizedRemoteViews and return the class cookies. @@ -3638,7 +3682,6 @@ public class RemoteViews implements Parcelable, Filter { } else { sizedRemoteViews.add(view); } - configureRemoteViewsAsChild(view); view.setIdealSize(size); if (classCookies == null) { classCookies = view.mClassCookies; @@ -3653,13 +3696,41 @@ public class RemoteViews implements Parcelable, Filter { * Creates a copy of another RemoteViews. */ public RemoteViews(RemoteViews src) { - initializeFrom(src); + initializeFrom(src, /* hierarchyRoot= */ null); } - private void initializeFrom(RemoteViews src) { - mBitmapCache = src.mBitmapCache; + /** + * No-arg constructor for use with {@link #initializeFrom(RemoteViews, RemoteViews)}. A + * constructor taking two RemoteViews parameters would clash with the landscape/portrait + * constructor. + */ + private RemoteViews() {} + + private static RemoteViews createInitializedFrom(@NonNull RemoteViews src, + @Nullable RemoteViews hierarchyRoot) { + RemoteViews child = new RemoteViews(); + child.initializeFrom(src, hierarchyRoot); + return child; + } + + private void initializeFrom(@NonNull RemoteViews src, @Nullable RemoteViews hierarchyRoot) { + if (hierarchyRoot == null) { + mBitmapCache = src.mBitmapCache; + mApplicationInfoCache = src.mApplicationInfoCache; + } else { + mBitmapCache = hierarchyRoot.mBitmapCache; + mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache; + } + if (hierarchyRoot == null || src.mIsRoot) { + // If there's no provided root, or if src was itself a root, then this RemoteViews is + // the root of the new hierarchy. + mIsRoot = true; + hierarchyRoot = this; + } else { + // Otherwise, we're a descendant in the hierarchy. + mIsRoot = false; + } mApplication = src.mApplication; - mIsRoot = src.mIsRoot; mLayoutId = src.mLayoutId; mLightBackgroundLayoutId = src.mLightBackgroundLayoutId; mApplyFlags = src.mApplyFlags; @@ -3668,21 +3739,21 @@ public class RemoteViews implements Parcelable, Filter { mProviderInstanceId = src.mProviderInstanceId; if (src.hasLandscapeAndPortraitLayouts()) { - mLandscape = new RemoteViews(src.mLandscape); - mPortrait = new RemoteViews(src.mPortrait); + mLandscape = createInitializedFrom(src.mLandscape, hierarchyRoot); + mPortrait = createInitializedFrom(src.mPortrait, hierarchyRoot); } if (src.hasSizedRemoteViews()) { mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size()); for (RemoteViews srcView : src.mSizedRemoteViews) { - mSizedRemoteViews.add(new RemoteViews(srcView)); + mSizedRemoteViews.add(createInitializedFrom(srcView, hierarchyRoot)); } } if (src.mActions != null) { Parcel p = Parcel.obtain(); p.putClassCookies(mClassCookies); - src.writeActionsToParcel(p); + src.writeActionsToParcel(p, /* flags= */ 0); p.setDataPosition(0); // Since src is already in memory, we do not care about stack overflow as it has // already been read once. @@ -3690,9 +3761,11 @@ public class RemoteViews implements Parcelable, Filter { p.recycle(); } - // Now that everything is initialized and duplicated, setting a new BitmapCache will - // re-initialize the cache. - setBitmapCache(new BitmapCache()); + // Now that everything is initialized and duplicated, create new caches for this + // RemoteViews and recursively set up all descendants. + if (mIsRoot) { + reconstructCaches(); + } } /** @@ -3701,11 +3774,11 @@ public class RemoteViews implements Parcelable, Filter { * @param parcel */ public RemoteViews(Parcel parcel) { - this(parcel, null, null, 0, null); + this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0); } - private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth, - Map classCookies) { + private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData, + @Nullable ApplicationInfo info, int depth) { if (depth > MAX_NESTED_VIEWS && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) { throw new IllegalArgumentException("Too many nested views."); @@ -3714,20 +3787,17 @@ public class RemoteViews implements Parcelable, Filter { int mode = parcel.readInt(); - // We only store a bitmap cache in the root of the RemoteViews. - if (bitmapCache == null) { + if (rootData == null) { + // We only store a bitmap cache in the root of the RemoteViews. mBitmapCache = new BitmapCache(parcel); // Store the class cookies such that they are available when we clone this RemoteView. mClassCookies = parcel.copyClassCookies(); } else { - setBitmapCache(bitmapCache); - mClassCookies = classCookies; - setNotRoot(); + configureAsChild(rootData); } if (mode == MODE_NORMAL) { - mApplication = parcel.readInt() == 0 ? info : - ApplicationInfo.CREATOR.createFromParcel(parcel); + mApplication = ApplicationInfo.CREATOR.createFromParcel(parcel); mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel); mLayoutId = parcel.readInt(); mViewId = parcel.readInt(); @@ -3742,8 +3812,7 @@ public class RemoteViews implements Parcelable, Filter { } List remoteViews = new ArrayList<>(numViews); for (int i = 0; i < numViews; i++) { - RemoteViews view = new RemoteViews(parcel, mBitmapCache, info, depth, - mClassCookies); + RemoteViews view = new RemoteViews(parcel, getHierarchyRootData(), info, depth); info = view.mApplication; remoteViews.add(view); } @@ -3755,9 +3824,9 @@ public class RemoteViews implements Parcelable, Filter { mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; } else { // MODE_HAS_LANDSCAPE_AND_PORTRAIT - mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies); - mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth, - mClassCookies); + mLandscape = new RemoteViews(parcel, getHierarchyRootData(), info, depth); + mPortrait = + new RemoteViews(parcel, getHierarchyRootData(), mLandscape.mApplication, depth); mApplication = mPortrait.mApplication; mLayoutId = mPortrait.mLayoutId; mViewId = mPortrait.mViewId; @@ -3765,6 +3834,11 @@ public class RemoteViews implements Parcelable, Filter { } mApplyFlags = parcel.readInt(); mProviderInstanceId = parcel.readLong(); + + // Ensure that all descendants have their caches set up recursively. + if (mIsRoot) { + configureDescendantsAsChildren(); + } } private void readActionsFromParcel(Parcel parcel, int depth) { @@ -3787,8 +3861,7 @@ public class RemoteViews implements Parcelable, Filter { case REFLECTION_ACTION_TAG: return new ReflectionAction(parcel); case VIEW_GROUP_ACTION_ADD_TAG: - return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth, - mClassCookies); + return new ViewGroupActionAdd(parcel, mApplication, depth); case VIEW_GROUP_ACTION_REMOVE_TAG: return new ViewGroupActionRemove(parcel); case VIEW_CONTENT_NAVIGATION_TAG: @@ -3878,27 +3951,55 @@ public class RemoteViews implements Parcelable, Filter { } /** - * Recursively sets BitmapCache in the hierarchy and update the bitmap ids. + * Sets the root of the hierarchy and then recursively traverses the tree to update the root + * and populate caches for all descendants. */ - private void setBitmapCache(BitmapCache bitmapCache) { - mBitmapCache = bitmapCache; + private void configureAsChild(@NonNull HierarchyRootData rootData) { + mIsRoot = false; + mBitmapCache = rootData.mBitmapCache; + mApplicationInfoCache = rootData.mApplicationInfoCache; + mClassCookies = rootData.mClassCookies; + configureDescendantsAsChildren(); + } + + /** + * Recursively traverses the tree to update the root and populate caches for all descendants. + */ + private void configureDescendantsAsChildren() { + // Before propagating down the tree, replace our application from the root application info + // cache, to ensure the same instance is present throughout the hierarchy to allow for + // squashing. + mApplication = mApplicationInfoCache.getOrPut(mApplication); + + HierarchyRootData rootData = getHierarchyRootData(); if (hasSizedRemoteViews()) { for (RemoteViews remoteView : mSizedRemoteViews) { - remoteView.setBitmapCache(bitmapCache); + remoteView.configureAsChild(rootData); } } else if (hasLandscapeAndPortraitLayouts()) { - mLandscape.setBitmapCache(bitmapCache); - mPortrait.setBitmapCache(bitmapCache); + mLandscape.configureAsChild(rootData); + mPortrait.configureAsChild(rootData); } else { if (mActions != null) { - final int count = mActions.size(); - for (int i = 0; i < count; ++i) { - mActions.get(i).setBitmapCache(bitmapCache); + for (Action action : mActions) { + action.setHierarchyRootData(rootData); } } } } + /** + * Recreates caches at the root level of the hierarchy, then recursively populates the caches + * down the hierarchy. + */ + private void reconstructCaches() { + if (!mIsRoot) return; + mBitmapCache = new BitmapCache(); + mApplicationInfoCache = new ApplicationInfoCache(); + mApplication = mApplicationInfoCache.getOrPut(mApplication); + configureDescendantsAsChildren(); + } + /** * Returns an estimate of the bitmap heap memory usage for this RemoteViews. */ @@ -5475,7 +5576,8 @@ public class RemoteViews implements Parcelable, Filter { // user. So build a context that loads resources from that user but // still returns the current users userId so settings like data / time formats // are loaded without requiring cross user persmissions. - final Context contextForResources = getContextForResources(context); + final Context contextForResources = + getContextForResourcesEnsuringCorrectCachedApkPaths(context); if (colorResources != null) { colorResources.apply(contextForResources); } @@ -5733,24 +5835,44 @@ public class RemoteViews implements Parcelable, Filter { return previousLayoutId == getLayoutId() && mViewId == overrideId; } - // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls - // should set it to false. - private void reapply(Context context, View v, InteractionHandler handler, SizeF size, - ColorResources colorResources, boolean topLevel) { - + /** + * Returns the RemoteViews that should be used in the reapply operation. + * + * If the current RemoteViews has multiple layout, this will select the correct one. + * + * @throws RuntimeException If the current RemoteViews should not be reapplied onto the provided + * View. + */ + private RemoteViews getRemoteViewsToReapply(Context context, View v, @Nullable SizeF size) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); // In the case that a view has this RemoteViews applied in one orientation or size, is // persisted across change, and has the RemoteViews re-applied in a different situation // (orientation or size), we throw an exception, since the layouts may be completely // unrelated. - if (hasMultipleLayouts()) { + // If the ViewID has been changed on the view, or is changed by the RemoteViews, we also + // may throw an exception, as the RemoteViews will probably not apply properly. + // However, we need to let potentially unrelated RemoteViews apply, as this lack of testing + // is already used in production code in some apps. + if (hasMultipleLayouts() + || rvToApply.mViewId != View.NO_ID + || v.getTag(R.id.remote_views_override_id) != null) { if (!rvToApply.canRecycleView(v)) { throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + " that does not share the same root layout id."); } } + return rvToApply; + } + + // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls + // should set it to false. + private void reapply(Context context, View v, InteractionHandler handler, SizeF size, + ColorResources colorResources, boolean topLevel) { + + RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); + rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources); // If the parent of the view is has is a root, resolve the recycling. @@ -5787,17 +5909,7 @@ public class RemoteViews implements Parcelable, Filter { public CancellationSignal reapplyAsync(Context context, View v, Executor executor, OnViewAppliedListener listener, InteractionHandler handler, SizeF size, ColorResources colorResources) { - RemoteViews rvToApply = getRemoteViewsToApply(context, size); - - // In the case that a view has this RemoteViews applied in one orientation, is persisted - // across orientation change, and has the RemoteViews re-applied in the new orientation, - // we throw an exception, since the layouts may be completely unrelated. - if (hasMultipleLayouts()) { - if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { - throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + - " that does not share the same root layout id."); - } - } + RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), context, listener, handler, colorResources, v, true /* topLevel */) @@ -5836,30 +5948,28 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public void updateAppInfo(@NonNull ApplicationInfo info) { - if (mApplication != null && mApplication.sourceDir.equals(info.sourceDir)) { + ApplicationInfo existing = mApplicationInfoCache.get(info); + if (existing != null && !existing.sourceDir.equals(info.sourceDir)) { // Overlay paths are generated against a particular version of an application. // The overlays paths of a newly upgraded application are incompatible with the // old version of the application. - mApplication = info; - } - if (hasSizedRemoteViews()) { - for (RemoteViews layout : mSizedRemoteViews) { - layout.updateAppInfo(info); - } - } - if (hasLandscapeAndPortraitLayouts()) { - mLandscape.updateAppInfo(info); - mPortrait.updateAppInfo(info); + return; } + + // If we can update to the new AppInfo, put it in the cache and propagate the change + // throughout the hierarchy. + mApplicationInfoCache.put(info); + configureDescendantsAsChildren(); } - private Context getContextForResources(Context context) { + private Context getContextForResourcesEnsuringCorrectCachedApkPaths(Context context) { if (mApplication != null) { if (context.getUserId() == UserHandle.getUserId(mApplication.uid) && context.getPackageName().equals(mApplication.packageName)) { return context; } try { + LoadedApk.checkAndUpdateApkPaths(mApplication); return context.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED); } catch (NameNotFoundException e) { @@ -6015,6 +6125,8 @@ public class RemoteViews implements Parcelable, Filter { } public void writeToParcel(Parcel dest, int flags) { + boolean prevSquashingAllowed = dest.allowSquashing(); + if (!hasMultipleLayouts()) { dest.writeInt(MODE_NORMAL); // We only write the bitmap cache if we are the root RemoteViews, as this cache @@ -6022,12 +6134,7 @@ public class RemoteViews implements Parcelable, Filter { if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); } - if (!mIsRoot && (flags & PARCELABLE_ELIDE_DUPLICATES) != 0) { - dest.writeInt(0); - } else { - dest.writeInt(1); - mApplication.writeToParcel(dest, flags); - } + mApplication.writeToParcel(dest, flags); if (mIsRoot || mIdealSize == null) { dest.writeInt(0); } else { @@ -6037,17 +6144,15 @@ public class RemoteViews implements Parcelable, Filter { dest.writeInt(mLayoutId); dest.writeInt(mViewId); dest.writeInt(mLightBackgroundLayoutId); - writeActionsToParcel(dest); + writeActionsToParcel(dest, flags); } else if (hasSizedRemoteViews()) { dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS); if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); } - int childFlags = flags; dest.writeInt(mSizedRemoteViews.size()); for (RemoteViews view : mSizedRemoteViews) { - view.writeToParcel(dest, childFlags); - childFlags |= PARCELABLE_ELIDE_DUPLICATES; + view.writeToParcel(dest, flags); } } else { dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); @@ -6058,13 +6163,15 @@ public class RemoteViews implements Parcelable, Filter { } mLandscape.writeToParcel(dest, flags); // Both RemoteViews already share the same package and user - mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES); + mPortrait.writeToParcel(dest, flags); } dest.writeInt(mApplyFlags); dest.writeLong(mProviderInstanceId); + + dest.restoreAllowSquashing(prevSquashingAllowed); } - private void writeActionsToParcel(Parcel parcel) { + private void writeActionsToParcel(Parcel parcel, int flags) { int count; if (mActions != null) { count = mActions.size(); @@ -6075,8 +6182,7 @@ public class RemoteViews implements Parcelable, Filter { for (int i = 0; i < count; i++) { Action a = mActions.get(i); parcel.writeInt(a.getActionTag()); - a.writeToParcel(parcel, a.hasSameAppInfo(mApplication) - ? PARCELABLE_ELIDE_DUPLICATES : 0); + a.writeToParcel(parcel, flags); } } @@ -6555,6 +6661,8 @@ public class RemoteViews implements Parcelable, Filter { private final boolean mHasStableIds; private final int mViewTypeCount; + private HierarchyRootData mHierarchyRootData; + RemoteCollectionItems( long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) { mIds = ids; @@ -6577,16 +6685,53 @@ public class RemoteViews implements Parcelable, Filter { "View type count is set to " + viewTypeCount + ", but the collection " + "contains " + layoutIdCount + " different layout ids"); } + + // Until the collection items are attached to a parent, we configure the first item + // to be the root of the others to share caches and save space during serialization. + if (views.length > 0) { + setHierarchyRootData(views[0].getHierarchyRootData()); + views[0].mIsRoot = true; + } } - RemoteCollectionItems(Parcel in) { + RemoteCollectionItems(@NonNull Parcel in, @Nullable HierarchyRootData hierarchyRootData) { + mHasStableIds = in.readBoolean(); + mViewTypeCount = in.readInt(); int length = in.readInt(); mIds = new long[length]; in.readLongArray(mIds); + + boolean attached = in.readBoolean(); mViews = new RemoteViews[length]; - in.readTypedArray(mViews, RemoteViews.CREATOR); - mHasStableIds = in.readBoolean(); - mViewTypeCount = in.readInt(); + int firstChildIndex; + if (attached) { + if (hierarchyRootData == null) { + throw new IllegalStateException("Cannot unparcel a RemoteCollectionItems that " + + "was parceled as attached without providing data for a root " + + "RemoteViews"); + } + mHierarchyRootData = hierarchyRootData; + firstChildIndex = 0; + } else { + mViews[0] = new RemoteViews(in); + mHierarchyRootData = mViews[0].getHierarchyRootData(); + firstChildIndex = 1; + } + + for (int i = firstChildIndex; i < length; i++) { + mViews[i] = new RemoteViews( + in, + mHierarchyRootData, + /* info= */ null, + /* depth= */ 0); + } + } + + void setHierarchyRootData(@NonNull HierarchyRootData rootData) { + mHierarchyRootData = rootData; + for (RemoteViews view : mViews) { + view.configureAsChild(rootData); + } } @Override @@ -6596,11 +6741,39 @@ public class RemoteViews implements Parcelable, Filter { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mIds.length); - dest.writeLongArray(mIds); - dest.writeTypedArray(mViews, flags); + writeToParcel(dest, flags, /* attached= */ false); + } + + private void writeToParcel(@NonNull Parcel dest, int flags, boolean attached) { + boolean prevAllowSquashing = dest.allowSquashing(); + dest.writeBoolean(mHasStableIds); dest.writeInt(mViewTypeCount); + dest.writeInt(mIds.length); + dest.writeLongArray(mIds); + + if (attached && mHierarchyRootData == null) { + throw new IllegalStateException("Cannot call writeToParcelAttached for a " + + "RemoteCollectionItems without first calling setHierarchyRootData()"); + } + + // Write whether we parceled as attached or not. This allows cleaner validation and + // proper error messaging when unparceling later. + dest.writeBoolean(attached); + boolean restoreRoot = false; + if (!attached && mViews.length > 0 && !mViews[0].mIsRoot) { + // If we're writing unattached, temporarily set the first item as the root so that + // the bitmap cache is written to the parcel. + restoreRoot = true; + mViews[0].mIsRoot = true; + } + + for (RemoteViews view : mViews) { + view.writeToParcel(dest, flags); + } + + if (restoreRoot) mViews[0].mIsRoot = false; + dest.restoreAllowSquashing(prevAllowSquashing); } /** @@ -6658,7 +6831,7 @@ public class RemoteViews implements Parcelable, Filter { @NonNull @Override public RemoteCollectionItems createFromParcel(@NonNull Parcel source) { - return new RemoteCollectionItems(source); + return new RemoteCollectionItems(source, /* hierarchyRoot= */ null); } @NonNull @@ -6833,4 +7006,29 @@ public class RemoteViews implements Parcelable, Filter { viewId |= childId; return viewId; } + + @Nullable + private static Pair getPackageUserKey(@Nullable ApplicationInfo info) { + if (info == null || info.packageName == null) return null; + return Pair.create(info.packageName, info.uid); + } + + private HierarchyRootData getHierarchyRootData() { + return new HierarchyRootData(mBitmapCache, mApplicationInfoCache, mClassCookies); + } + + private static final class HierarchyRootData { + final BitmapCache mBitmapCache; + final ApplicationInfoCache mApplicationInfoCache; + final Map mClassCookies; + + HierarchyRootData( + BitmapCache bitmapCache, + ApplicationInfoCache applicationInfoCache, + Map classCookies) { + mBitmapCache = bitmapCache; + mApplicationInfoCache = applicationInfoCache; + mClassCookies = classCookies; + } + } } diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 6b33428d7fe494a78a681441416acebdda7c214c..8e293f4b356dcfc6f883d2909e4d32bdef8b7e7d 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -408,7 +408,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } @Override - protected Context getRemoteContext() { + protected Context getRemoteContextEnsuringCorrectCachedApkPath() { return null; } diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java index 4a78f3ee6faca95ab52858939bd73ae16bc0a884..942be21b1ade9dd97ab4b8a8b9321e453e7345d5 100644 --- a/core/java/android/widget/TextViewTranslationCallback.java +++ b/core/java/android/widget/TextViewTranslationCallback.java @@ -46,6 +46,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { private TranslationTransformationMethod mTranslationTransformation; private boolean mIsShowingTranslation = false; + private boolean mAnimationRunning = false; private boolean mIsTextPaddingEnabled = false; private CharSequence mPaddedText; private int mAnimationDurationMillis = 250; // default value @@ -92,6 +93,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { (TextView) view, () -> { mIsShowingTranslation = true; + mAnimationRunning = false; // TODO(b/178353965): well-handle setTransformationMethod. ((TextView) view).setTransformationMethod(transformation); }); @@ -124,6 +126,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { (TextView) view, () -> { mIsShowingTranslation = false; + mAnimationRunning = false; ((TextView) view).setTransformationMethod(transformation); }); if (!TextUtils.isEmpty(mContentDescription)) { @@ -162,6 +165,13 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { return mIsShowingTranslation; } + /** + * Returns whether the view is running animation to show or hide the translation. + */ + public boolean isAnimationRunning() { + return mAnimationRunning; + } + @Override public void enableContentPadding() { mIsTextPaddingEnabled = true; @@ -230,6 +240,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { mAnimator.end(); // Note: mAnimator is now null; do not use again here. } + mAnimationRunning = true; int fadedOutColor = colorWithAlpha(view.getCurrentTextColor(), 0); mAnimator = ValueAnimator.ofArgb(view.getCurrentTextColor(), fadedOutColor); mAnimator.addUpdateListener( diff --git a/core/java/android/window/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..9a079751553f1bff2abfa9604cfd2038a517b8d6 --- /dev/null +++ b/core/java/android/window/ConfigurationHelper.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static android.view.Display.INVALID_DISPLAY; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ResourcesManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.Display; +import android.view.WindowManager; + +/** + * A helper class to maintain {@link android.content.res.Configuration} related methods used both + * in {@link android.app.Activity} and {@link WindowContext}. + * + * @hide + */ +public class ConfigurationHelper { + private ConfigurationHelper() {} + + /** Ask text layout engine to free its caches if there is a locale change. */ + public static void freeTextLayoutCachesIfNeeded(int configDiff) { + if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) { + Canvas.freeTextLayoutCaches(); + } + } + + /** + * A helper method to filter out {@link ActivityInfo#CONFIG_SCREEN_SIZE} if the + * {@link Configuration#diffPublicOnly(Configuration) diff} of two {@link Configuration} + * doesn't cross the boundary. + * + * @see SizeConfigurationBuckets#filterDiff(int, Configuration, Configuration, + * SizeConfigurationBuckets) + */ + public static int diffPublicWithSizeBuckets(@Nullable Configuration currentConfig, + @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets buckets) { + // If current configuration is null, it is definitely different from updated Configuration. + if (currentConfig == null) { + return 0xffffffff; + } + int publicDiff = currentConfig.diffPublicOnly(newConfig); + return SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig, newConfig, buckets); + } + + /** + * Returns {@code true} if the {@link android.content.res.Resources} associated with + * a {@code token} needs to be updated. + * + * @param token A {@link Context#getActivityToken() activity token} or + * {@link Context#getWindowContextToken() window context token} + * @param config The original {@link Configuration} + * @param newConfig The updated Configuration + * @param displayChanged a flag to indicate there's a display change + * @param configChanged a flag to indicate there's a Configuration change. + * + * @see ResourcesManager#updateResourcesForActivity(IBinder, Configuration, int) + */ + public static boolean shouldUpdateResources(IBinder token, @Nullable Configuration config, + @NonNull Configuration newConfig, @NonNull Configuration overrideConfig, + boolean displayChanged, @Nullable Boolean configChanged) { + // The configuration has not yet been initialized. We should update it. + if (config == null) { + return true; + } + // If the token associated context is moved to another display, we should update the + // ResourcesKey. + if (displayChanged) { + return true; + } + // If the new config is the same as the config this Activity is already running with and + // the override config also didn't change, then don't update the Resources + if (!ResourcesManager.getInstance().isSameResourcesOverrideConfig(token, overrideConfig)) { + return true; + } + // If there's a update on WindowConfiguration#mBounds or maxBounds, we should update the + // Resources to make WindowMetrics API report the updated result. + if (shouldUpdateWindowMetricsBounds(config, newConfig)) { + return true; + } + return configChanged == null ? config.diff(newConfig) != 0 : configChanged; + } + + /** + * Returns {@code true} if {@code displayId} is different from {@code newDisplayId}. + * Note that {@link Display#INVALID_DISPLAY} means no difference. + */ + public static boolean isDifferentDisplay(int displayId, int newDisplayId) { + return newDisplayId != INVALID_DISPLAY && displayId != newDisplayId; + } + + // TODO(b/173090263): Remove this method after the improvement of AssetManager and ResourcesImpl + // constructions. + /** + * Returns {@code true} if the metrics reported by {@link android.view.WindowMetrics} APIs + * should be updated. + * + * @see WindowManager#getCurrentWindowMetrics() + * @see WindowManager#getMaximumWindowMetrics() + */ + private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig, + @NonNull Configuration newConfig) { + final Rect currentBounds = currentConfig.windowConfiguration.getBounds(); + final Rect newBounds = newConfig.windowConfiguration.getBounds(); + + final Rect currentMaxBounds = currentConfig.windowConfiguration.getMaxBounds(); + final Rect newMaxBounds = newConfig.windowConfiguration.getMaxBounds(); + + return !currentBounds.equals(newBounds) || !currentMaxBounds.equals(newMaxBounds); + } +} diff --git a/core/java/android/window/DisplayAreaInfo.java b/core/java/android/window/DisplayAreaInfo.java index 358467ff599f4da3404869e02dbbc7f022b26a11..1a7aab6852b6b7cce8d2da880609122be22abeb4 100644 --- a/core/java/android/window/DisplayAreaInfo.java +++ b/core/java/android/window/DisplayAreaInfo.java @@ -16,6 +16,8 @@ package android.window; +import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; + import android.annotation.NonNull; import android.annotation.TestApi; import android.content.res.Configuration; @@ -43,8 +45,17 @@ public final class DisplayAreaInfo implements Parcelable { */ public final int displayId; + /** + * The feature id of this display area. + */ public final int featureId; + /** + * The feature id of the root display area this display area is associated with. + * @hide + */ + public int rootDisplayAreaId = FEATURE_UNDEFINED; + public DisplayAreaInfo(@NonNull WindowContainerToken token, int displayId, int featureId) { this.token = token; this.displayId = displayId; @@ -56,6 +67,7 @@ public final class DisplayAreaInfo implements Parcelable { configuration.readFromParcel(in); displayId = in.readInt(); featureId = in.readInt(); + rootDisplayAreaId = in.readInt(); } @Override @@ -64,6 +76,7 @@ public final class DisplayAreaInfo implements Parcelable { configuration.writeToParcel(dest, flags); dest.writeInt(displayId); dest.writeInt(featureId); + dest.writeInt(rootDisplayAreaId); } @NonNull diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java index 878439906de2830802ed0188a4fab71b9d1a34e7..6758a3b411a2ac9f9b319f924b42274cce7e22d3 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -33,6 +33,15 @@ import java.util.concurrent.Executor; @TestApi public class DisplayAreaOrganizer extends WindowOrganizer { + /** + * Key to specify the {@link com.android.server.wm.RootDisplayArea} to attach a window to. + * It will be used by the function passed in from + * {@link com.android.server.wm.DisplayAreaPolicyBuilder#setSelectRootForWindowFunc(BiFunction)} + * to find the Root DA to attach the window. + * @hide + */ + public static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id"; + /** * The value in display area indicating that no value has been set. */ @@ -256,6 +265,7 @@ public class DisplayAreaOrganizer extends WindowOrganizer { } }; + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) private IDisplayAreaOrganizerController getController() { try { return getWindowOrganizerController().getDisplayAreaOrganizerController(); @@ -263,5 +273,4 @@ public class DisplayAreaOrganizer extends WindowOrganizer { return null; } } - } diff --git a/core/java/android/window/IRemoteTransitionFinishedCallback.aidl b/core/java/android/window/IRemoteTransitionFinishedCallback.aidl index 02aa1a93a35fc7af91c6b097140aa3510f07b980..7864c245310e82043b508d8c821f468498e01fcf 100644 --- a/core/java/android/window/IRemoteTransitionFinishedCallback.aidl +++ b/core/java/android/window/IRemoteTransitionFinishedCallback.aidl @@ -16,14 +16,18 @@ package android.window; +import android.view.SurfaceControl; import android.window.WindowContainerTransaction; /** * Interface to be invoked by the controlling process when a remote transition has finished. * * @see IRemoteTransition + * @param wct An optional WindowContainerTransaction to apply before the transition finished. + * @param sct An optional Surface Transaction that is added to the end of the finish/cleanup + * transaction. This is applied by shell.Transitions (before submitting the wct). * {@hide} */ interface IRemoteTransitionFinishedCallback { - void onTransitionFinished(in WindowContainerTransaction wct); + void onTransitionFinished(in WindowContainerTransaction wct, in SurfaceControl.Transaction sct); } diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl new file mode 100644 index 0000000000000000000000000000000000000000..cdfa206423c2e3da1d1114b471f28538c12f7d2f --- /dev/null +++ b/core/java/android/window/ITaskFragmentOrganizer.aidl @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.IBinder; +import android.window.TaskFragmentInfo; + +/** @hide */ +oneway interface ITaskFragmentOrganizer { + void onTaskFragmentAppeared(in TaskFragmentInfo taskFragmentInfo); + void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo); + void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo); + + /** + * Called when the parent leaf Task of organized TaskFragments is changed. + * When the leaf Task is changed, the organizer may want to update the TaskFragments in one + * transaction. + * + * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new + * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override + * bounds. + */ + void onTaskFragmentParentInfoChanged(in IBinder fragmentToken, in Configuration parentConfig); + + /** + * Called when the {@link WindowContainerTransaction} created with + * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. + * + * @param errorCallbackToken Token set through {@link + * WindowContainerTransaction#setErrorCallbackToken(IBinder)} + * @param exceptionBundle Bundle containing the exception. Should be created with + * {@link TaskFragmentOrganizer#putExceptionInBundle}. + */ + void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle exceptionBundle); +} diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4399207fcc27a41488b601ad187e378aec04c553 --- /dev/null +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.view.RemoteAnimationDefinition; +import android.window.ITaskFragmentOrganizer; + +/** @hide */ +interface ITaskFragmentOrganizerController { + + /** + * Registers a TaskFragmentOrganizer to manage TaskFragments. + */ + void registerOrganizer(in ITaskFragmentOrganizer organizer); + + /** + * Unregisters a previously registered TaskFragmentOrganizer. + */ + void unregisterOrganizer(in ITaskFragmentOrganizer organizer); + + /** + * Registers remote animations per transition type for the organizer. It will override the + * animations if the transition only contains windows that belong to the organized + * TaskFragments. + */ + void registerRemoteAnimations(in ITaskFragmentOrganizer organizer, + in RemoteAnimationDefinition definition); + + /** + * Unregisters remote animations per transition type for the organizer. + */ + void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer); + + /** + * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * only occupies a portion of Task bounds. + */ + boolean isActivityEmbedded(in IBinder activityToken); +} diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 69bc1b5f7763438a58491afe0385fade96939ac2..fd86769293a6ecf70baeb8dc962ad6788b4a3ccd 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -20,6 +20,7 @@ import android.view.SurfaceControl; import android.app.ActivityManager; import android.graphics.Rect; import android.window.StartingWindowInfo; +import android.window.StartingWindowRemovalInfo; import android.window.WindowContainerToken; /** @@ -39,12 +40,9 @@ oneway interface ITaskOrganizer { /** * Called when the Task want to remove the starting window. - * @param leash A persistent leash for the top window in this task. - * @param frame Window frame of the top window. - * @param playRevealAnimation Play vanish animation. + * @param removalInfo The information used to remove the starting window. */ - void removeStartingWindow(int taskId, in SurfaceControl leash, in Rect frame, - in boolean playRevealAnimation); + void removeStartingWindow(in StartingWindowRemovalInfo removalInfo); /** * Called when the Task want to copy the splash screen. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/core/java/android/window/ITransitionMetricsReporter.aidl similarity index 60% rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt rename to core/java/android/window/ITransitionMetricsReporter.aidl index 0037059e2c517e316a49d53c02ccc79973c32dfe..00f71dc7bb905657f03bf36884796b6e6e38bcc8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt +++ b/core/java/android/window/ITransitionMetricsReporter.aidl @@ -14,16 +14,20 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.pip +package android.window; -import android.content.ComponentName -import com.android.server.wm.traces.common.windowmanager.WindowManagerState -import com.android.server.wm.traces.parser.toWindowName +import android.os.IBinder; /** - * Checks that an activity [activity] is in PIP mode + * Implemented by WM Core to know the metrics of transition that runs on a different process. + * @hide */ -fun WindowManagerState.isInPipMode(activity: ComponentName): Boolean { - val windowName = activity.toWindowName() - return isInPipMode(windowName) +oneway interface ITransitionMetricsReporter { + + /** + * Called when the transition animation starts. + * + * @param startTime The time when the animation started. + */ + void reportAnimationStart(IBinder transitionToken, long startTime); } diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl index 1223d72f643ed1da7fc9b4655775130b09ad4734..3c7cd0254e7894a67dfa2876f837163c1e8d76a7 100644 --- a/core/java/android/window/IWindowOrganizerController.aidl +++ b/core/java/android/window/IWindowOrganizerController.aidl @@ -19,8 +19,11 @@ package android.window; import android.view.SurfaceControl; import android.os.IBinder; +import android.view.RemoteAnimationAdapter; import android.window.IDisplayAreaOrganizerController; +import android.window.ITaskFragmentOrganizerController; import android.window.ITaskOrganizerController; +import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; import android.window.IWindowContainerTransactionCallback; import android.window.WindowContainerToken; @@ -59,6 +62,17 @@ interface IWindowOrganizerController { IBinder startTransition(int type, in @nullable IBinder transitionToken, in @nullable WindowContainerTransaction t); + /** + * Starts a legacy transition. + * @param type The transition type. + * @param adapter The animation to use. + * @param syncCallback A sync callback for the contents of `t` + * @param t Operations that are part of the transition. + * @return sync-id or -1 if this no-op'd because a transition is already running. + */ + int startLegacyTransition(int type, in RemoteAnimationAdapter adapter, + in IWindowContainerTransactionCallback syncCallback, in WindowContainerTransaction t); + /** * Finishes a transition. This must be called for all created transitions. * @param transitionToken Which transition to finish @@ -77,9 +91,15 @@ interface IWindowOrganizerController { /** @return An interface enabling the management of display area organizers. */ IDisplayAreaOrganizerController getDisplayAreaOrganizerController(); + /** @return An interface enabling the management of task fragment organizers. */ + ITaskFragmentOrganizerController getTaskFragmentOrganizerController(); + /** * Registers a transition player with Core. There is only one of these at a time and calling * this will replace the existing one if set. */ void registerTransitionPlayer(in ITransitionPlayer player); + + /** @return An interface enabling the transition players to report its metrics. */ + ITransitionMetricsReporter getTransitionMetricsReporter(); } diff --git a/core/java/android/window/PictureInPictureSurfaceTransaction.java b/core/java/android/window/PictureInPictureSurfaceTransaction.java index dbf7eb34e2730cd800a7710c2013e1627924b8c1..2bf2f31937895bfb0e3780d8c0b9f9dfbd37793a 100644 --- a/core/java/android/window/PictureInPictureSurfaceTransaction.java +++ b/core/java/android/window/PictureInPictureSurfaceTransaction.java @@ -19,6 +19,7 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Matrix; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; @@ -34,9 +35,10 @@ import java.util.Objects; * @hide */ public final class PictureInPictureSurfaceTransaction implements Parcelable { + private static final float NOT_SET = -1f; - public final float mPositionX; - public final float mPositionY; + public final float mAlpha; + public final PointF mPosition; public final float[] mFloat9; @@ -45,33 +47,37 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { public final float mCornerRadius; - private final Rect mWindowCrop = new Rect(); + private final Rect mWindowCrop; - public PictureInPictureSurfaceTransaction(Parcel in) { - mPositionX = in.readFloat(); - mPositionY = in.readFloat(); + private PictureInPictureSurfaceTransaction(Parcel in) { + mAlpha = in.readFloat(); + mPosition = in.readTypedObject(PointF.CREATOR); mFloat9 = new float[9]; in.readFloatArray(mFloat9); mRotation = in.readFloat(); mCornerRadius = in.readFloat(); - mWindowCrop.set(Objects.requireNonNull(in.readTypedObject(Rect.CREATOR))); + mWindowCrop = in.readTypedObject(Rect.CREATOR); } - public PictureInPictureSurfaceTransaction(float positionX, float positionY, - float[] float9, float rotation, float cornerRadius, + private PictureInPictureSurfaceTransaction(float alpha, @Nullable PointF position, + @Nullable float[] float9, float rotation, float cornerRadius, @Nullable Rect windowCrop) { - mPositionX = positionX; - mPositionY = positionY; - mFloat9 = Arrays.copyOf(float9, 9); - mRotation = rotation; - mCornerRadius = cornerRadius; - if (windowCrop != null) { - mWindowCrop.set(windowCrop); + mAlpha = alpha; + mPosition = position; + if (float9 == null) { + mFloat9 = new float[9]; + Matrix.IDENTITY_MATRIX.getValues(mFloat9); + mRotation = 0; + } else { + mFloat9 = Arrays.copyOf(float9, 9); + mRotation = rotation; } + mCornerRadius = cornerRadius; + mWindowCrop = (windowCrop == null) ? null : new Rect(windowCrop); } public PictureInPictureSurfaceTransaction(PictureInPictureSurfaceTransaction other) { - this(other.mPositionX, other.mPositionY, + this(other.mAlpha, other.mPosition, other.mFloat9, other.mRotation, other.mCornerRadius, other.mWindowCrop); } @@ -82,13 +88,18 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { return matrix; } + /** @return {@code true} if this transaction contains setting corner radius. */ + public boolean hasCornerRadiusSet() { + return mCornerRadius > 0; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PictureInPictureSurfaceTransaction)) return false; PictureInPictureSurfaceTransaction that = (PictureInPictureSurfaceTransaction) o; - return Objects.equals(mPositionX, that.mPositionX) - && Objects.equals(mPositionY, that.mPositionY) + return Objects.equals(mAlpha, that.mAlpha) + && Objects.equals(mPosition, that.mPosition) && Arrays.equals(mFloat9, that.mFloat9) && Objects.equals(mRotation, that.mRotation) && Objects.equals(mCornerRadius, that.mCornerRadius) @@ -97,7 +108,7 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { @Override public int hashCode() { - return Objects.hash(mPositionX, mPositionY, Arrays.hashCode(mFloat9), + return Objects.hash(mAlpha, mPosition, Arrays.hashCode(mFloat9), mRotation, mCornerRadius, mWindowCrop); } @@ -108,8 +119,8 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeFloat(mPositionX); - out.writeFloat(mPositionY); + out.writeFloat(mAlpha); + out.writeTypedObject(mPosition, 0 /* flags */); out.writeFloatArray(mFloat9); out.writeFloat(mRotation); out.writeFloat(mCornerRadius); @@ -120,8 +131,8 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { public String toString() { final Matrix matrix = getMatrix(); return "PictureInPictureSurfaceTransaction(" - + " posX=" + mPositionX - + " posY=" + mPositionY + + " alpha=" + mAlpha + + " position=" + mPosition + " matrix=" + matrix.toShortString() + " rotation=" + mRotation + " cornerRadius=" + mCornerRadius @@ -134,11 +145,20 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { @NonNull SurfaceControl surfaceControl, @NonNull SurfaceControl.Transaction tx) { final Matrix matrix = surfaceTransaction.getMatrix(); - tx.setMatrix(surfaceControl, matrix, new float[9]) - .setPosition(surfaceControl, - surfaceTransaction.mPositionX, surfaceTransaction.mPositionY) - .setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop) - .setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius); + tx.setMatrix(surfaceControl, matrix, new float[9]); + if (surfaceTransaction.mPosition != null) { + tx.setPosition(surfaceControl, + surfaceTransaction.mPosition.x, surfaceTransaction.mPosition.y); + } + if (surfaceTransaction.mWindowCrop != null) { + tx.setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop); + } + if (surfaceTransaction.hasCornerRadiusSet()) { + tx.setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius); + } + if (surfaceTransaction.mAlpha != NOT_SET) { + tx.setAlpha(surfaceControl, surfaceTransaction.mAlpha); + } } public static final @android.annotation.NonNull Creator @@ -151,4 +171,44 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable { return new PictureInPictureSurfaceTransaction[size]; } }; + + public static class Builder { + private float mAlpha = NOT_SET; + private PointF mPosition; + private float[] mFloat9; + private float mRotation; + private float mCornerRadius = NOT_SET; + private Rect mWindowCrop; + + public Builder setAlpha(float alpha) { + mAlpha = alpha; + return this; + } + + public Builder setPosition(float x, float y) { + mPosition = new PointF(x, y); + return this; + } + + public Builder setTransform(@NonNull float[] float9, float rotation) { + mFloat9 = Arrays.copyOf(float9, 9); + mRotation = rotation; + return this; + } + + public Builder setCornerRadius(float cornerRadius) { + mCornerRadius = cornerRadius; + return this; + } + + public Builder setWindowCrop(@NonNull Rect windowCrop) { + mWindowCrop = new Rect(windowCrop); + return this; + } + + public PictureInPictureSurfaceTransaction build() { + return new PictureInPictureSurfaceTransaction(mAlpha, mPosition, + mFloat9, mRotation, mCornerRadius, mWindowCrop); + } + } } diff --git a/core/java/android/window/RemoteTransition.aidl b/core/java/android/window/RemoteTransition.aidl new file mode 100644 index 0000000000000000000000000000000000000000..f3c3f54410ed064fe2b745f308db729b0435bfd3 --- /dev/null +++ b/core/java/android/window/RemoteTransition.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +parcelable RemoteTransition; diff --git a/core/java/android/window/RemoteTransition.java b/core/java/android/window/RemoteTransition.java new file mode 100644 index 0000000000000000000000000000000000000000..4bd15f27a91a411c4800aeb643ccee2a45812f02 --- /dev/null +++ b/core/java/android/window/RemoteTransition.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.IApplicationThread; +import android.os.IBinder; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** + * Represents a remote transition animation and information required to run it (eg. the app thread + * that needs to be boosted). + * @hide + */ +@DataClass(genToString = true, genSetters = true, genAidl = true) +public class RemoteTransition implements Parcelable { + + /** The actual remote-transition interface used to run the transition animation. */ + private @NonNull IRemoteTransition mRemoteTransition; + + /** The application thread that will be running the remote transition. */ + private @Nullable IApplicationThread mAppThread; + + /** Constructs with no app thread (animation runs in shell). */ + public RemoteTransition(@NonNull IRemoteTransition remoteTransition) { + this(remoteTransition, null /* appThread */); + } + + /** Get the IBinder associated with the underlying IRemoteTransition. */ + public @Nullable IBinder asBinder() { + return mRemoteTransition.asBinder(); + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/window/RemoteTransition.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new RemoteTransition. + * + * @param remoteTransition + * The actual remote-transition interface used to run the transition animation. + * @param appThread + * The application thread that will be running the remote transition. + */ + @DataClass.Generated.Member + public RemoteTransition( + @NonNull IRemoteTransition remoteTransition, + @Nullable IApplicationThread appThread) { + this.mRemoteTransition = remoteTransition; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRemoteTransition); + this.mAppThread = appThread; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * The actual remote-transition interface used to run the transition animation. + */ + @DataClass.Generated.Member + public @NonNull IRemoteTransition getRemoteTransition() { + return mRemoteTransition; + } + + /** + * The application thread that will be running the remote transition. + */ + @DataClass.Generated.Member + public @Nullable IApplicationThread getAppThread() { + return mAppThread; + } + + /** + * The actual remote-transition interface used to run the transition animation. + */ + @DataClass.Generated.Member + public @NonNull RemoteTransition setRemoteTransition(@NonNull IRemoteTransition value) { + mRemoteTransition = value; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRemoteTransition); + return this; + } + + /** + * The application thread that will be running the remote transition. + */ + @DataClass.Generated.Member + public @NonNull RemoteTransition setAppThread(@NonNull IApplicationThread value) { + mAppThread = value; + return this; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "RemoteTransition { " + + "remoteTransition = " + mRemoteTransition + ", " + + "appThread = " + mAppThread + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mAppThread != null) flg |= 0x2; + dest.writeByte(flg); + dest.writeStrongInterface(mRemoteTransition); + if (mAppThread != null) dest.writeStrongInterface(mAppThread); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected RemoteTransition(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + IRemoteTransition remoteTransition = IRemoteTransition.Stub.asInterface(in.readStrongBinder()); + IApplicationThread appThread = (flg & 0x2) == 0 ? null : IApplicationThread.Stub.asInterface(in.readStrongBinder()); + + this.mRemoteTransition = remoteTransition; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRemoteTransition); + this.mAppThread = appThread; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public RemoteTransition[] newArray(int size) { + return new RemoteTransition[size]; + } + + @Override + public RemoteTransition createFromParcel(@NonNull android.os.Parcel in) { + return new RemoteTransition(in); + } + }; + + @DataClass.Generated( + time = 1630690027011L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/window/RemoteTransition.java", + inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.app.IApplicationThread mAppThread\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/window/SizeConfigurationBuckets.java b/core/java/android/window/SizeConfigurationBuckets.java index 7422f2449a8d4e6a987cdac6ab6b1f9d6def710d..f474f0a76cc6d5b63c5aaa30fe6f41ff71489ef4 100644 --- a/core/java/android/window/SizeConfigurationBuckets.java +++ b/core/java/android/window/SizeConfigurationBuckets.java @@ -16,6 +16,7 @@ package android.window; +import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; @@ -25,6 +26,7 @@ import android.content.res.Configuration; import android.os.Parcelable; import android.util.SparseIntArray; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; import java.util.Arrays; @@ -54,10 +56,24 @@ public final class SizeConfigurationBuckets implements Parcelable { @Nullable private final int[] mSmallest; + /** Screen Layout Size (screenLayout & SCREENLAYOUT_SIZE_MASK) buckets */ + @Nullable + private final int[] mScreenLayoutSize; + + /** + * Screen Layout Long (screenLayout & SCREENLAYOUT_LONG_MASK) boolean. Only need to know if a + * value is set because only two possible buckets, SCREENLAYOUT_LONG_NO and + * SCREENLAYOUT_LONG_YES, so if either is set, then any change is a bucket change. + */ + private final boolean mScreenLayoutLongSet; + public SizeConfigurationBuckets(Configuration[] sizeConfigurations) { SparseIntArray horizontal = new SparseIntArray(); SparseIntArray vertical = new SparseIntArray(); SparseIntArray smallest = new SparseIntArray(); + SparseIntArray screenLayoutSize = new SparseIntArray(); + int curScreenLayoutSize; + boolean screenLayoutLongSet = false; for (int i = sizeConfigurations.length - 1; i >= 0; i--) { Configuration config = sizeConfigurations[i]; if (config.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { @@ -69,23 +85,42 @@ public final class SizeConfigurationBuckets implements Parcelable { if (config.smallestScreenWidthDp != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { smallest.put(config.smallestScreenWidthDp, 0); } + if ((curScreenLayoutSize = config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) + != Configuration.SCREENLAYOUT_SIZE_UNDEFINED) { + screenLayoutSize.put(curScreenLayoutSize, 0); + } + if (!screenLayoutLongSet && (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) + != Configuration.SCREENLAYOUT_LONG_UNDEFINED) { + screenLayoutLongSet = true; + } } mHorizontal = horizontal.copyKeys(); mVertical = vertical.copyKeys(); mSmallest = smallest.copyKeys(); + mScreenLayoutSize = screenLayoutSize.copyKeys(); + mScreenLayoutLongSet = screenLayoutLongSet; } /** * Get the changes between two configurations but don't count changes in sizes if they don't - * cross boundaries that are important to the app. + * cross boundaries that are important to the app. * * This is a static helper to deal with null `buckets`. When no buckets have been specified, * this actually filters out all 3 size-configs. This is legacy behavior. */ - public static int filterDiff(int diff, Configuration oldConfig, Configuration newConfig, - @Nullable SizeConfigurationBuckets buckets) { + public static int filterDiff(int diff, @NonNull Configuration oldConfig, + @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets buckets) { + final boolean nonSizeLayoutFieldsUnchanged = + areNonSizeLayoutFieldsUnchanged(oldConfig.screenLayout, newConfig.screenLayout); if (buckets == null) { - return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE); + // Only unflip CONFIG_SCREEN_LAYOUT if non-size-related attributes of screen layout do + // not change. + if (nonSizeLayoutFieldsUnchanged) { + return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE + | CONFIG_SCREEN_LAYOUT); + } else { + return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE); + } } if ((diff & CONFIG_SCREEN_SIZE) != 0) { final boolean crosses = buckets.crossesHorizontalSizeThreshold(oldConfig.screenWidthDp, @@ -103,6 +138,13 @@ public final class SizeConfigurationBuckets implements Parcelable { diff &= ~CONFIG_SMALLEST_SCREEN_SIZE; } } + if ((diff & CONFIG_SCREEN_LAYOUT) != 0 && nonSizeLayoutFieldsUnchanged) { + if (!buckets.crossesScreenLayoutSizeThreshold(oldConfig, newConfig) + && !buckets.crossesScreenLayoutLongThreshold(oldConfig.screenLayout, + newConfig.screenLayout)) { + diff &= ~CONFIG_SCREEN_LAYOUT; + } + } return diff; } @@ -118,6 +160,61 @@ public final class SizeConfigurationBuckets implements Parcelable { return crossesSizeThreshold(mSmallest, firstDp, secondDp); } + /** + * Returns whether a screen layout size threshold has been crossed. + */ + @VisibleForTesting + public boolean crossesScreenLayoutSizeThreshold(@NonNull Configuration firstConfig, + @NonNull Configuration secondConfig) { + // If both the old and new screen layout are equal (both can be undefined), then no + // threshold is crossed. + if ((firstConfig.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) + == (secondConfig.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)) { + return false; + } + // Any time the new layout size is smaller than the old layout size, the activity has + // crossed a size threshold because layout size represents the smallest possible size the + // activity can occupy. + if (!secondConfig.isLayoutSizeAtLeast(firstConfig.screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK)) { + return true; + } + // If the new layout size is at least as large as the old layout size, then check if the new + // layout size has crossed a threshold. + if (mScreenLayoutSize != null) { + for (int screenLayoutSize : mScreenLayoutSize) { + if (firstConfig.isLayoutSizeAtLeast(screenLayoutSize) + != secondConfig.isLayoutSizeAtLeast(screenLayoutSize)) { + return true; + } + } + } + return false; + } + + private boolean crossesScreenLayoutLongThreshold(int firstScreenLayout, + int secondScreenLayout) { + final int firstScreenLayoutLongValue = firstScreenLayout + & Configuration.SCREENLAYOUT_LONG_MASK; + final int secondScreenLayoutLongValue = secondScreenLayout + & Configuration.SCREENLAYOUT_LONG_MASK; + return mScreenLayoutLongSet && firstScreenLayoutLongValue != secondScreenLayoutLongValue; + } + + /** + * Returns whether non-size related screen layout attributes have changed. If true, then + * {@link ActivityInfo#CONFIG_SCREEN_LAYOUT} should not be filtered out in + * {@link SizeConfigurationBuckets#filterDiff()} because the non-size related attributes + * do not have a bucket range like the size-related attributes of screen layout. + */ + @VisibleForTesting + public static boolean areNonSizeLayoutFieldsUnchanged(int oldScreenLayout, + int newScreenLayout) { + final int nonSizeRelatedFields = Configuration.SCREENLAYOUT_LAYOUTDIR_MASK + | Configuration.SCREENLAYOUT_ROUND_MASK | Configuration.SCREENLAYOUT_COMPAT_NEEDED; + return (oldScreenLayout & nonSizeRelatedFields) == (newScreenLayout & nonSizeRelatedFields); + } + /** * The purpose of this method is to decide whether the activity needs to be relaunched upon * changing its size. In most cases the activities don't need to be relaunched, if the resize @@ -132,7 +229,8 @@ public final class SizeConfigurationBuckets implements Parcelable { * it resizes width from 620dp to 700dp, it won't be relaunched as it stays on the same side * of the threshold. */ - private static boolean crossesSizeThreshold(int[] thresholds, int firstDp, + @VisibleForTesting + public static boolean crossesSizeThreshold(int[] thresholds, int firstDp, int secondDp) { if (thresholds == null) { return false; @@ -150,12 +248,13 @@ public final class SizeConfigurationBuckets implements Parcelable { @Override public String toString() { return Arrays.toString(mHorizontal) + " " + Arrays.toString(mVertical) + " " - + Arrays.toString(mSmallest); + + Arrays.toString(mSmallest) + " " + Arrays.toString(mScreenLayoutSize) + " " + + mScreenLayoutLongSet; } - // Code below generated by codegen v1.0.22. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -177,15 +276,25 @@ public final class SizeConfigurationBuckets implements Parcelable { * Vertical (screenHeightDp) buckets * @param smallest * Smallest (smallestScreenWidthDp) buckets + * @param screenLayoutSize + * Screen Layout Size (screenLayout & SCREENLAYOUT_SIZE_MASK) buckets + * @param screenLayoutLongSet + * Screen Layout Long (screenLayout & SCREENLAYOUT_LONG_MASK) boolean. Only need to know if a + * value is set because only two possible buckets, SCREENLAYOUT_LONG_NO and + * SCREENLAYOUT_LONG_YES, so if either is set, then any change is a bucket change. */ @DataClass.Generated.Member public SizeConfigurationBuckets( @Nullable int[] horizontal, @Nullable int[] vertical, - @Nullable int[] smallest) { + @Nullable int[] smallest, + @Nullable int[] screenLayoutSize, + boolean screenLayoutLongSet) { this.mHorizontal = horizontal; this.mVertical = vertical; this.mSmallest = smallest; + this.mScreenLayoutSize = screenLayoutSize; + this.mScreenLayoutLongSet = screenLayoutLongSet; // onConstructed(); // You can define this method to get a callback } @@ -214,6 +323,24 @@ public final class SizeConfigurationBuckets implements Parcelable { return mSmallest; } + /** + * Screen Layout Size (screenLayout & SCREENLAYOUT_SIZE_MASK) buckets + */ + @DataClass.Generated.Member + public @Nullable int[] getScreenLayoutSize() { + return mScreenLayoutSize; + } + + /** + * Screen Layout Long (screenLayout & SCREENLAYOUT_LONG_MASK) boolean. Only need to know if a + * value is set because only two possible buckets, SCREENLAYOUT_LONG_NO and + * SCREENLAYOUT_LONG_YES, so if either is set, then any change is a bucket change. + */ + @DataClass.Generated.Member + public boolean isScreenLayoutLongSet() { + return mScreenLayoutLongSet; + } + @Override @DataClass.Generated.Member public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { @@ -221,13 +348,16 @@ public final class SizeConfigurationBuckets implements Parcelable { // void parcelFieldName(Parcel dest, int flags) { ... } byte flg = 0; + if (mScreenLayoutLongSet) flg |= 0x10; if (mHorizontal != null) flg |= 0x1; if (mVertical != null) flg |= 0x2; if (mSmallest != null) flg |= 0x4; + if (mScreenLayoutSize != null) flg |= 0x8; dest.writeByte(flg); if (mHorizontal != null) dest.writeIntArray(mHorizontal); if (mVertical != null) dest.writeIntArray(mVertical); if (mSmallest != null) dest.writeIntArray(mSmallest); + if (mScreenLayoutSize != null) dest.writeIntArray(mScreenLayoutSize); } @Override @@ -242,13 +372,17 @@ public final class SizeConfigurationBuckets implements Parcelable { // static FieldType unparcelFieldName(Parcel in) { ... } byte flg = in.readByte(); + boolean screenLayoutLongSet = (flg & 0x10) != 0; int[] horizontal = (flg & 0x1) == 0 ? null : in.createIntArray(); int[] vertical = (flg & 0x2) == 0 ? null : in.createIntArray(); int[] smallest = (flg & 0x4) == 0 ? null : in.createIntArray(); + int[] screenLayoutSize = (flg & 0x8) == 0 ? null : in.createIntArray(); this.mHorizontal = horizontal; this.mVertical = vertical; this.mSmallest = smallest; + this.mScreenLayoutSize = screenLayoutSize; + this.mScreenLayoutLongSet = screenLayoutLongSet; // onConstructed(); // You can define this method to get a callback } @@ -268,10 +402,10 @@ public final class SizeConfigurationBuckets implements Parcelable { }; @DataClass.Generated( - time = 1615845864280L, - codegenVersion = "1.0.22", + time = 1628273704583L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/SizeConfigurationBuckets.java", - inputSignatures = "private final @android.annotation.Nullable int[] mHorizontal\nprivate final @android.annotation.Nullable int[] mVertical\nprivate final @android.annotation.Nullable int[] mSmallest\npublic static int filterDiff(int,android.content.res.Configuration,android.content.res.Configuration,android.window.SizeConfigurationBuckets)\nprivate boolean crossesHorizontalSizeThreshold(int,int)\nprivate boolean crossesVerticalSizeThreshold(int,int)\nprivate boolean crossesSmallestSizeThreshold(int,int)\nprivate static boolean crossesSizeThreshold(int[],int,int)\npublic @java.lang.Override java.lang.String toString()\nclass SizeConfigurationBuckets extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true)") + inputSignatures = "private final @android.annotation.Nullable int[] mHorizontal\nprivate final @android.annotation.Nullable int[] mVertical\nprivate final @android.annotation.Nullable int[] mSmallest\nprivate final @android.annotation.Nullable int[] mScreenLayoutSize\nprivate final boolean mScreenLayoutLongSet\npublic static int filterDiff(int,android.content.res.Configuration,android.content.res.Configuration,android.window.SizeConfigurationBuckets)\nprivate boolean crossesHorizontalSizeThreshold(int,int)\nprivate boolean crossesVerticalSizeThreshold(int,int)\nprivate boolean crossesSmallestSizeThreshold(int,int)\npublic @com.android.internal.annotations.VisibleForTesting boolean crossesScreenLayoutSizeThreshold(android.content.res.Configuration,android.content.res.Configuration)\nprivate boolean crossesScreenLayoutLongThreshold(int,int)\npublic static @com.android.internal.annotations.VisibleForTesting boolean areNonSizeLayoutFieldsUnchanged(int,int)\npublic static @com.android.internal.annotations.VisibleForTesting boolean crossesSizeThreshold(int[],int,int)\npublic @java.lang.Override java.lang.String toString()\nclass SizeConfigurationBuckets extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index 3e0075857402d2de0eb709d3136deef987bd21d3..090dbff488e921df05b2a06571dd7540108c7ade 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -42,6 +42,11 @@ import java.util.ArrayList; * Activity.getSplashScreen() to get the SplashScreen.

    */ public interface SplashScreen { + /** + * The splash screen style is not defined. + * @hide + */ + int SPLASH_SCREEN_STYLE_UNDEFINED = -1; /** * Force splash screen to be empty. * @hide @@ -55,6 +60,7 @@ public interface SplashScreen { /** @hide */ @IntDef(prefix = { "SPLASH_SCREEN_STYLE_" }, value = { + SPLASH_SCREEN_STYLE_UNDEFINED, SPLASH_SCREEN_STYLE_EMPTY, SPLASH_SCREEN_STYLE_ICON }) @@ -92,6 +98,9 @@ public interface SplashScreen { * overrides and persists the theme used for the {@link SplashScreen} of this application. *

    * To reset to the default theme, set this the themeId to {@link Resources#ID_NULL}. + *

    + * Note: The theme name must be stable across versions, otherwise it won't be found + * after your application is updated. */ void setSplashScreenTheme(@StyleRes int themeId); @@ -241,7 +250,6 @@ public interface SplashScreen { public void handOverSplashScreenView(@NonNull IBinder token, @NonNull SplashScreenView splashScreenView) { - transferSurface(splashScreenView); dispatchOnExitAnimation(token, splashScreenView); } @@ -265,9 +273,5 @@ public interface SplashScreen { return impl != null && impl.mExitAnimationListener != null; } } - - private void transferSurface(@NonNull SplashScreenView splashScreenView) { - splashScreenView.transferSurface(); - } } } diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index acf20d701eeb4448661007f1f88ea1a2a9f359a9..f04155d112d494ca3d3c7b7033e850eb9b53c0a4 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -20,6 +20,10 @@ import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACK import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_AVD; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; @@ -54,11 +58,13 @@ import android.widget.FrameLayout; import android.widget.ImageView; import com.android.internal.R; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.DecorView; import com.android.internal.util.ContrastColorUtil; import java.time.Duration; import java.time.Instant; +import java.util.function.Consumer; /** *

    The view which allows an activity to customize its splash screen exit animation.

    @@ -144,6 +150,7 @@ public final class SplashScreenView extends FrameLayout { private Bitmap mParceledBrandingBitmap; private Instant mIconAnimationStart; private Duration mIconAnimationDuration; + private Consumer mUiThreadInitTask; public Builder(@NonNull Context context) { mContext = context; @@ -231,6 +238,15 @@ public final class SplashScreenView extends FrameLayout { return this; } + /** + * Set the Runnable that can receive the task which should be executed on UI thread. + * @param uiThreadInitTask + */ + public Builder setUiThreadInitConsumer(Consumer uiThreadInitTask) { + mUiThreadInitTask = uiThreadInitTask; + return this; + } + /** * Set the Drawable object and size for the branding view. */ @@ -262,7 +278,11 @@ public final class SplashScreenView extends FrameLayout { // center icon if (mIconDrawable instanceof SplashScreenView.IconAnimateListener || mSurfacePackage != null) { - view.mIconView = createSurfaceView(view); + if (mUiThreadInitTask != null) { + mUiThreadInitTask.accept(() -> view.mIconView = createSurfaceView(view)); + } else { + view.mIconView = createSurfaceView(view); + } view.initIconAnimation(mIconDrawable, mIconAnimationDuration != null ? mIconAnimationDuration.toMillis() : 0); view.mIconAnimationStart = mIconAnimationStart; @@ -316,7 +336,9 @@ public final class SplashScreenView extends FrameLayout { } private SurfaceView createSurfaceView(@NonNull SplashScreenView view) { - final SurfaceView surfaceView = new SurfaceView(view.getContext()); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#createSurfaceView"); + final Context viewContext = view.getContext(); + final SurfaceView surfaceView = new SurfaceView(viewContext); surfaceView.setPadding(0, 0, 0, 0); surfaceView.setBackground(mIconBackground); if (mSurfacePackage == null) { @@ -326,10 +348,10 @@ public final class SplashScreenView extends FrameLayout { + Thread.currentThread().getId()); } - SurfaceControlViewHost viewHost = new SurfaceControlViewHost(mContext, - mContext.getDisplay(), + SurfaceControlViewHost viewHost = new SurfaceControlViewHost(viewContext, + viewContext.getDisplay(), surfaceView.getHostToken()); - ImageView imageView = new ImageView(mContext); + ImageView imageView = new ImageView(viewContext); imageView.setBackground(mIconDrawable); viewHost.setView(imageView, mIconSize, mIconSize); SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage(); @@ -360,6 +382,7 @@ public final class SplashScreenView extends FrameLayout { view.addView(surfaceView); view.mSurfaceView = surfaceView; + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return surfaceView; } } @@ -446,7 +469,10 @@ public final class SplashScreenView extends FrameLayout { } - void transferSurface() { + /** + * @hide + */ + public void syncTransferSurfaceOnDraw() { if (mSurfacePackage == null) { return; } @@ -456,8 +482,8 @@ public final class SplashScreenView extends FrameLayout { String.format("SurfacePackage'surface reparented to %s", parent))); Log.d(TAG, "Transferring surface " + mSurfaceView.toString()); } - mSurfaceView.setChildSurfacePackage(mSurfacePackage); + mSurfaceView.setChildSurfacePackageOnDraw(mSurfacePackage); } void initIconAnimation(Drawable iconDrawable, long duration) { @@ -466,6 +492,23 @@ public final class SplashScreenView extends FrameLayout { } IconAnimateListener aniDrawable = (IconAnimateListener) iconDrawable; aniDrawable.prepareAnimate(duration, this::animationStartCallback); + aniDrawable.setAnimationJankMonitoring(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + InteractionJankMonitor.getInstance().cancel(CUJ_SPLASHSCREEN_AVD); + } + + @Override + public void onAnimationEnd(Animator animation) { + InteractionJankMonitor.getInstance().end(CUJ_SPLASHSCREEN_AVD); + } + + @Override + public void onAnimationStart(Animator animation) { + InteractionJankMonitor.getInstance().begin( + SplashScreenView.this, CUJ_SPLASHSCREEN_AVD); + } + }); } private void animationStartCallback() { @@ -515,10 +558,6 @@ public final class SplashScreenView extends FrameLayout { restoreSystemUIColors(); mWindow = null; } - if (mHostActivity != null) { - mHostActivity.setSplashScreenView(null); - mHostActivity = null; - } mHasRemoved = true; } @@ -531,23 +570,32 @@ public final class SplashScreenView extends FrameLayout { private void releaseAnimationSurfaceHost() { if (mSurfaceHost != null && !mIsCopied) { - final SurfaceControlViewHost finalSurfaceHost = mSurfaceHost; + if (DEBUG) { + Log.d(TAG, + "Shell removed splash screen." + + " Releasing SurfaceControlViewHost on thread #" + + Thread.currentThread().getId()); + } + releaseIconHost(mSurfaceHost); mSurfaceHost = null; - finalSurfaceHost.getView().post(() -> { - if (DEBUG) { - Log.d(TAG, - "Shell removed splash screen." - + " Releasing SurfaceControlViewHost on thread #" - + Thread.currentThread().getId()); - } - finalSurfaceHost.release(); - }); } else if (mSurfacePackage != null && mSurfaceHost == null) { mSurfacePackage = null; mClientCallback.sendResult(null); } } + /** + * Release the host which hold the SurfaceView of the icon. + * @hide + */ + public static void releaseIconHost(SurfaceControlViewHost host) { + final Drawable background = host.getView().getBackground(); + if (background instanceof SplashScreenView.IconAnimateListener) { + ((SplashScreenView.IconAnimateListener) background).stopAnimation(); + } + host.release(); + } + /** * Called when this view is attached to an activity. This also makes SystemUI colors * transparent so the content of splash screen view can draw fully. @@ -555,7 +603,6 @@ public final class SplashScreenView extends FrameLayout { * @hide */ public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) { - activity.setSplashScreenView(this); mHostActivity = activity; mWindow = window; final WindowManager.LayoutParams attr = window.getAttributes(); @@ -639,6 +686,17 @@ public final class SplashScreenView extends FrameLayout { * @return true if this drawable object can also be animated and it can be played now. */ boolean prepareAnimate(long duration, Runnable startListener); + + /** + * Stop animation. + */ + void stopAnimation(); + + /** + * Provides a chance to start interaction jank monitoring in avd animation. + * @param listener a listener to start jank monitoring + */ + default void setAnimationJankMonitoring(AnimatorListenerAdapter listener) {} } /** diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index 8c64474dc887cd38540ad52fd4ae70b6c7942c1b..5950e9fc64560f680d1745ec6d2e304cefcbfbbe 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -19,13 +19,13 @@ package android.window; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.pm.ActivityInfo; import android.os.Parcel; import android.os.Parcelable; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.WindowManager; /** @@ -33,7 +33,6 @@ import android.view.WindowManager; * start in the system. * @hide */ -@TestApi public final class StartingWindowInfo implements Parcelable { /** * Prefer nothing or not care the type of starting window. @@ -165,7 +164,13 @@ public final class StartingWindowInfo implements Parcelable { * TaskSnapshot. * @hide */ - public TaskSnapshot mTaskSnapshot; + public TaskSnapshot taskSnapshot; + + /** + * The requested insets visibility of the top main window. + * @hide + */ + public final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); public StartingWindowInfo() { @@ -190,7 +195,8 @@ public final class StartingWindowInfo implements Parcelable { dest.writeTypedObject(mainWindowLayoutParams, flags); dest.writeInt(splashScreenThemeResId); dest.writeBoolean(isKeyguardOccluded); - dest.writeTypedObject(mTaskSnapshot, flags); + dest.writeTypedObject(taskSnapshot, flags); + requestedVisibilities.writeToParcel(dest, flags); } void readFromParcel(@NonNull Parcel source) { @@ -203,7 +209,8 @@ public final class StartingWindowInfo implements Parcelable { mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR); splashScreenThemeResId = source.readInt(); isKeyguardOccluded = source.readBoolean(); - mTaskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR); + taskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR); + requestedVisibilities.readFromParcel(source); } @Override diff --git a/core/java/android/window/StartingWindowRemovalInfo.aidl b/core/java/android/window/StartingWindowRemovalInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8e4ac0453f83807fb4405145eb989276d07e0410 --- /dev/null +++ b/core/java/android/window/StartingWindowRemovalInfo.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** @hide */ +parcelable StartingWindowRemovalInfo; \ No newline at end of file diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..573db0d58625a8034f5b93bb264d3e9ec6a7732e --- /dev/null +++ b/core/java/android/window/StartingWindowRemovalInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControl; + +/** + * Information when removing a starting window of a particular task. + * @hide + */ +public final class StartingWindowRemovalInfo implements Parcelable { + + /** + * The identifier of a task. + * @hide + */ + public int taskId; + + /** + * The animation container layer of the top activity. + * @hide + */ + @Nullable + public SurfaceControl windowAnimationLeash; + + /** + * The main window frame for the window of the top activity. + * @hide + */ + @Nullable + public Rect mainFrame; + + /** + * Whether need to play reveal animation. + * @hide + */ + public boolean playRevealAnimation; + + /** + * Whether need to defer removing the starting window for IME. + * @hide + */ + public boolean deferRemoveForIme; + + public StartingWindowRemovalInfo() { + + } + + private StartingWindowRemovalInfo(@NonNull Parcel source) { + readFromParcel(source); + } + + @Override + public int describeContents() { + return 0; + } + + void readFromParcel(@NonNull Parcel source) { + taskId = source.readInt(); + windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR); + mainFrame = source.readTypedObject(Rect.CREATOR); + playRevealAnimation = source.readBoolean(); + deferRemoveForIme = source.readBoolean(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(taskId); + dest.writeTypedObject(windowAnimationLeash, flags); + dest.writeTypedObject(mainFrame, flags); + dest.writeBoolean(playRevealAnimation); + dest.writeBoolean(deferRemoveForIme); + } + + @Override + public String toString() { + return "StartingWindowRemovalInfo{taskId=" + taskId + + " frame=" + mainFrame + + " playRevealAnimation=" + playRevealAnimation + + " deferRemoveForIme=" + deferRemoveForIme + "}"; + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public StartingWindowRemovalInfo createFromParcel(@NonNull Parcel source) { + return new StartingWindowRemovalInfo(source); + } + public StartingWindowRemovalInfo[] newArray(int size) { + return new StartingWindowRemovalInfo[size]; + } + }; +} diff --git a/core/java/android/window/TaskFragmentCreationParams.aidl b/core/java/android/window/TaskFragmentCreationParams.aidl new file mode 100644 index 0000000000000000000000000000000000000000..fde50892640baa5a47b6d78b8d2f2e971ece7827 --- /dev/null +++ b/core/java/android/window/TaskFragmentCreationParams.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * Data object for options to create TaskFragment with. + * @hide + */ +parcelable TaskFragmentCreationParams; diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java new file mode 100644 index 0000000000000000000000000000000000000000..81ab7836435d8598c3d9632855f58532fcc12bb4 --- /dev/null +++ b/core/java/android/window/TaskFragmentCreationParams.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.app.WindowConfiguration.WindowingMode; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.graphics.Rect; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data object for options to create TaskFragment with. + * @hide + */ +@TestApi +public final class TaskFragmentCreationParams implements Parcelable { + + /** The organizer that will organize this TaskFragment. */ + @NonNull + private final TaskFragmentOrganizerToken mOrganizer; + + /** + * Unique token assigned from the client organizer to identify the {@link TaskFragmentInfo} when + * a new TaskFragment is created with this option. + */ + @NonNull + private final IBinder mFragmentToken; + + /** + * Activity token used to identify the leaf Task to create the TaskFragment in. It has to belong + * to the same app as the root Activity of the target Task. + */ + @NonNull + private final IBinder mOwnerToken; + + /** The initial bounds of the TaskFragment. Fills parent if empty. */ + @NonNull + private final Rect mInitialBounds = new Rect(); + + /** The initial windowing mode of the TaskFragment. Inherits from parent if not set. */ + @WindowingMode + private int mWindowingMode = WINDOWING_MODE_UNDEFINED; + + private TaskFragmentCreationParams( + @NonNull TaskFragmentOrganizerToken organizer, + @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) { + mOrganizer = organizer; + mFragmentToken = fragmentToken; + mOwnerToken = ownerToken; + } + + @NonNull + public TaskFragmentOrganizerToken getOrganizer() { + return mOrganizer; + } + + @NonNull + public IBinder getFragmentToken() { + return mFragmentToken; + } + + @NonNull + public IBinder getOwnerToken() { + return mOwnerToken; + } + + @NonNull + public Rect getInitialBounds() { + return mInitialBounds; + } + + @WindowingMode + public int getWindowingMode() { + return mWindowingMode; + } + + private TaskFragmentCreationParams(Parcel in) { + mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in); + mFragmentToken = in.readStrongBinder(); + mOwnerToken = in.readStrongBinder(); + mInitialBounds.readFromParcel(in); + mWindowingMode = in.readInt(); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + mOrganizer.writeToParcel(dest, flags); + dest.writeStrongBinder(mFragmentToken); + dest.writeStrongBinder(mOwnerToken); + mInitialBounds.writeToParcel(dest, flags); + dest.writeInt(mWindowingMode); + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public TaskFragmentCreationParams createFromParcel(Parcel in) { + return new TaskFragmentCreationParams(in); + } + + @Override + public TaskFragmentCreationParams[] newArray(int size) { + return new TaskFragmentCreationParams[size]; + } + }; + + @Override + public String toString() { + return "TaskFragmentCreationParams{" + + " organizer=" + mOrganizer + + " fragmentToken=" + mFragmentToken + + " ownerToken=" + mOwnerToken + + " initialBounds=" + mInitialBounds + + " windowingMode=" + mWindowingMode + + "}"; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** Builder to construct the options to create TaskFragment with. */ + public static final class Builder { + + @NonNull + private final TaskFragmentOrganizerToken mOrganizer; + + @NonNull + private final IBinder mFragmentToken; + + @NonNull + private final IBinder mOwnerToken; + + @NonNull + private final Rect mInitialBounds = new Rect(); + + @WindowingMode + private int mWindowingMode = WINDOWING_MODE_UNDEFINED; + + public Builder(@NonNull TaskFragmentOrganizerToken organizer, + @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) { + mOrganizer = organizer; + mFragmentToken = fragmentToken; + mOwnerToken = ownerToken; + } + + /** Sets the initial bounds for the TaskFragment. */ + @NonNull + public Builder setInitialBounds(@NonNull Rect bounds) { + mInitialBounds.set(bounds); + return this; + } + + /** Sets the initial windowing mode for the TaskFragment. */ + @NonNull + public Builder setWindowingMode(@WindowingMode int windowingMode) { + mWindowingMode = windowingMode; + return this; + } + + /** Constructs the options to create TaskFragment with. */ + @NonNull + public TaskFragmentCreationParams build() { + final TaskFragmentCreationParams result = new TaskFragmentCreationParams( + mOrganizer, mFragmentToken, mOwnerToken); + result.mInitialBounds.set(mInitialBounds); + result.mWindowingMode = mWindowingMode; + return result; + } + } +} diff --git a/core/java/android/window/TaskFragmentInfo.aidl b/core/java/android/window/TaskFragmentInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..461a7364803f0b04a807c74e4513d67dedc866a6 --- /dev/null +++ b/core/java/android/window/TaskFragmentInfo.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * Stores information about a particular TaskFragment. + * @hide + */ +parcelable TaskFragmentInfo; diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..a118f9a8188f8d474cce76388ca6d4f91d8c47f8 --- /dev/null +++ b/core/java/android/window/TaskFragmentInfo.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static android.app.WindowConfiguration.WindowingMode; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.content.res.Configuration; +import android.graphics.Point; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Stores information about a particular TaskFragment. + * @hide + */ +@TestApi +public final class TaskFragmentInfo implements Parcelable { + + /** + * Client assigned unique token in {@link TaskFragmentCreationParams#getFragmentToken()} to + * create this TaskFragment with. + */ + @NonNull + private final IBinder mFragmentToken; + + @NonNull + private final WindowContainerToken mToken; + + @NonNull + private final Configuration mConfiguration = new Configuration(); + + /** Whether the TaskFragment contains any child Window Container. */ + private final boolean mIsEmpty; + + /** The number of the running activities in the TaskFragment. */ + private final int mRunningActivityCount; + + /** Whether this TaskFragment is visible on the window hierarchy. */ + private final boolean mIsVisible; + + /** + * List of Activity tokens that are children of this TaskFragment. It only contains Activities + * that belong to the organizer process for security. + */ + @NonNull + private final List mActivities = new ArrayList<>(); + + /** Relative position of the fragment's top left corner in the parent container. */ + private final Point mPositionInParent; + + /** + * Whether the last running activity in the TaskFragment was finished due to clearing task while + * launching an activity in the host Task. + */ + private final boolean mIsTaskClearedForReuse; + + /** @hide */ + public TaskFragmentInfo( + @NonNull IBinder fragmentToken, @NonNull WindowContainerToken token, + @NonNull Configuration configuration, boolean isEmpty, int runningActivityCount, + boolean isVisible, @NonNull List activities, @NonNull Point positionInParent, + boolean isTaskClearedForReuse) { + mFragmentToken = requireNonNull(fragmentToken); + mToken = requireNonNull(token); + mConfiguration.setTo(configuration); + mIsEmpty = isEmpty; + mRunningActivityCount = runningActivityCount; + mIsVisible = isVisible; + mActivities.addAll(activities); + mPositionInParent = requireNonNull(positionInParent); + mIsTaskClearedForReuse = isTaskClearedForReuse; + } + + @NonNull + public IBinder getFragmentToken() { + return mFragmentToken; + } + + @NonNull + public WindowContainerToken getToken() { + return mToken; + } + + @NonNull + public Configuration getConfiguration() { + return mConfiguration; + } + + public boolean isEmpty() { + return mIsEmpty; + } + + public boolean hasRunningActivity() { + return mRunningActivityCount > 0; + } + + public int getRunningActivityCount() { + return mRunningActivityCount; + } + + public boolean isVisible() { + return mIsVisible; + } + + @NonNull + public List getActivities() { + return mActivities; + } + + /** Returns the relative position of the fragment's top left corner in the parent container. */ + @NonNull + public Point getPositionInParent() { + return mPositionInParent; + } + + public boolean isTaskClearedForReuse() { + return mIsTaskClearedForReuse; + } + + @WindowingMode + public int getWindowingMode() { + return mConfiguration.windowConfiguration.getWindowingMode(); + } + + /** + * Returns {@code true} if the parameters that are important for task fragment organizers are + * equal between this {@link TaskFragmentInfo} and {@param that}. + */ + public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentInfo that) { + if (that == null) { + return false; + } + + return mFragmentToken.equals(that.mFragmentToken) + && mToken.equals(that.mToken) + && mIsEmpty == that.mIsEmpty + && mRunningActivityCount == that.mRunningActivityCount + && mIsVisible == that.mIsVisible + && getWindowingMode() == that.getWindowingMode() + && mActivities.equals(that.mActivities) + && mPositionInParent.equals(that.mPositionInParent) + && mIsTaskClearedForReuse == that.mIsTaskClearedForReuse; + } + + private TaskFragmentInfo(Parcel in) { + mFragmentToken = in.readStrongBinder(); + mToken = in.readTypedObject(WindowContainerToken.CREATOR); + mConfiguration.readFromParcel(in); + mIsEmpty = in.readBoolean(); + mRunningActivityCount = in.readInt(); + mIsVisible = in.readBoolean(); + in.readBinderList(mActivities); + mPositionInParent = requireNonNull(in.readTypedObject(Point.CREATOR)); + mIsTaskClearedForReuse = in.readBoolean(); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mFragmentToken); + dest.writeTypedObject(mToken, flags); + mConfiguration.writeToParcel(dest, flags); + dest.writeBoolean(mIsEmpty); + dest.writeInt(mRunningActivityCount); + dest.writeBoolean(mIsVisible); + dest.writeBinderList(mActivities); + dest.writeTypedObject(mPositionInParent, flags); + dest.writeBoolean(mIsTaskClearedForReuse); + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public TaskFragmentInfo createFromParcel(Parcel in) { + return new TaskFragmentInfo(in); + } + + @Override + public TaskFragmentInfo[] newArray(int size) { + return new TaskFragmentInfo[size]; + } + }; + + @Override + public String toString() { + return "TaskFragmentInfo{" + + " fragmentToken=" + mFragmentToken + + " token=" + mToken + + " isEmpty=" + mIsEmpty + + " runningActivityCount=" + mRunningActivityCount + + " isVisible=" + mIsVisible + + " activities=" + mActivities + + " positionInParent=" + mPositionInParent + + " isTaskClearedForReuse=" + mIsTaskClearedForReuse + + "}"; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java new file mode 100644 index 0000000000000000000000000000000000000000..9c2fde04e4d2ddae8c1896320db164ff06f0092a --- /dev/null +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.RemoteAnimationDefinition; + +import java.util.concurrent.Executor; + +/** + * Interface for WindowManager to delegate control of {@code TaskFragment}. + * @hide + */ +@TestApi +public class TaskFragmentOrganizer extends WindowOrganizer { + + /** + * Key to the exception in {@link Bundle} in {@link ITaskFragmentOrganizer#onTaskFragmentError}. + */ + private static final String KEY_ERROR_CALLBACK_EXCEPTION = "fragment_exception"; + + /** + * Creates a {@link Bundle} with an exception that can be passed to + * {@link ITaskFragmentOrganizer#onTaskFragmentError}. + * @hide + */ + public static Bundle putExceptionInBundle(@NonNull Throwable exception) { + final Bundle exceptionBundle = new Bundle(); + exceptionBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception); + return exceptionBundle; + } + + /** + * Callbacks from WM Core are posted on this executor. + */ + private final Executor mExecutor; + + public TaskFragmentOrganizer(@NonNull Executor executor) { + mExecutor = executor; + } + + /** + * Gets the executor to run callbacks on. + */ + @NonNull + public Executor getExecutor() { + return mExecutor; + } + + /** + * Registers a TaskFragmentOrganizer to manage TaskFragments. + */ + @CallSuper + public void registerOrganizer() { + try { + getController().registerOrganizer(mInterface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters a previously registered TaskFragmentOrganizer. + */ + @CallSuper + public void unregisterOrganizer() { + try { + getController().unregisterOrganizer(mInterface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Registers remote animations per transition type for the organizer. It will override the + * animations if the transition only contains windows that belong to the organized + * TaskFragments. + * @hide + */ + @CallSuper + public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) { + try { + getController().registerRemoteAnimations(mInterface, definition); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters remote animations per transition type for the organizer. + * @hide + */ + @CallSuper + public void unregisterRemoteAnimations() { + try { + getController().unregisterRemoteAnimations(mInterface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Called when a TaskFragment is created and organized by this organizer. */ + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {} + + /** Called when the status of an organized TaskFragment is changed. */ + public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {} + + /** Called when an organized TaskFragment is removed. */ + public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {} + + /** + * Called when the parent leaf Task of organized TaskFragments is changed. + * When the leaf Task is changed, the organizer may want to update the TaskFragments in one + * transaction. + * + * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new + * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override + * bounds. + */ + public void onTaskFragmentParentInfoChanged( + @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {} + + /** + * Called when the {@link WindowContainerTransaction} created with + * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. + * + * @param errorCallbackToken token set in + * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} + * @param exception exception from the server side. + */ + public void onTaskFragmentError( + @NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {} + + @Override + public void applyTransaction(@NonNull WindowContainerTransaction t) { + t.setTaskFragmentOrganizer(mInterface); + super.applyTransaction(t); + } + + // Suppress the lint because it is not a registration method. + @SuppressWarnings("ExecutorRegistration") + @Override + public int applySyncTransaction(@NonNull WindowContainerTransaction t, + @NonNull WindowContainerTransactionCallback callback) { + t.setTaskFragmentOrganizer(mInterface); + return super.applySyncTransaction(t, callback); + } + + private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() { + @Override + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { + mExecutor.execute( + () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo)); + } + + @Override + public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) { + mExecutor.execute( + () -> TaskFragmentOrganizer.this.onTaskFragmentInfoChanged(taskFragmentInfo)); + } + + @Override + public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { + mExecutor.execute( + () -> TaskFragmentOrganizer.this.onTaskFragmentVanished(taskFragmentInfo)); + } + + @Override + public void onTaskFragmentParentInfoChanged( + @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) { + mExecutor.execute( + () -> TaskFragmentOrganizer.this.onTaskFragmentParentInfoChanged( + fragmentToken, parentConfig)); + } + + @Override + public void onTaskFragmentError( + @NonNull IBinder errorCallbackToken, @NonNull Bundle exceptionBundle) { + mExecutor.execute(() -> TaskFragmentOrganizer.this.onTaskFragmentError( + errorCallbackToken, + (Throwable) exceptionBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION))); + } + }; + + private final TaskFragmentOrganizerToken mToken = new TaskFragmentOrganizerToken(mInterface); + + @NonNull + public TaskFragmentOrganizerToken getOrganizerToken() { + return mToken; + } + + private ITaskFragmentOrganizerController getController() { + try { + return getWindowOrganizerController().getTaskFragmentOrganizerController(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * only occupies a portion of Task bounds. + * @hide + */ + public boolean isActivityEmbedded(@NonNull IBinder activityToken) { + try { + return getController().isActivityEmbedded(activityToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/window/TaskFragmentOrganizerToken.java b/core/java/android/window/TaskFragmentOrganizerToken.java new file mode 100644 index 0000000000000000000000000000000000000000..d5216a6444d96566987a4ff8d2d0dc62bb31aa8a --- /dev/null +++ b/core/java/android/window/TaskFragmentOrganizerToken.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Interface to communicate between window manager and {@link TaskFragmentOrganizer}. + *

    + * Window manager will dispatch TaskFragment information updates via this interface. + * It is necessary because {@link ITaskFragmentOrganizer} aidl interface can not be used as a + * {@link TestApi}. + * @hide + */ +@TestApi +public final class TaskFragmentOrganizerToken implements Parcelable { + private final ITaskFragmentOrganizer mRealToken; + + TaskFragmentOrganizerToken(ITaskFragmentOrganizer realToken) { + mRealToken = realToken; + } + + /** @hide */ + public IBinder asBinder() { + return mRealToken.asBinder(); + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongInterface(mRealToken); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public TaskFragmentOrganizerToken createFromParcel(Parcel in) { + final ITaskFragmentOrganizer realToken = + ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder()); + // The TaskFragmentOrganizerToken may be null for TaskOrganizer or + // DisplayAreaOrganizer. + if (realToken == null) { + return null; + } + return new TaskFragmentOrganizerToken(realToken); + } + + @Override + public TaskFragmentOrganizerToken[] newArray(int size) { + return new TaskFragmentOrganizerToken[size]; + } + }; + + @Override + public int hashCode() { + return mRealToken.asBinder().hashCode(); + } + + @Override + public String toString() { + return "TaskFragmentOrganizerToken{" + mRealToken + "}"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof TaskFragmentOrganizerToken)) { + return false; + } + return mRealToken.asBinder() == ((TaskFragmentOrganizerToken) obj).asBinder(); + } +} diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index d8723a821a2279be08229beda418269129a9e0ab..27c7d3158f95cfc09a1ab9af8eb673bb43e13e79 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -24,7 +24,6 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.ActivityManager; -import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.view.SurfaceControl; @@ -94,6 +93,7 @@ public class TaskOrganizer extends WindowOrganizer { * @param info The information about the Task that's available * @param appToken Token of the application being started. * context to for resources + * @hide */ @BinderThread public void addStartingWindow(@NonNull StartingWindowInfo info, @@ -101,14 +101,11 @@ public class TaskOrganizer extends WindowOrganizer { /** * Called when the Task want to remove the starting window. - * @param leash A persistent leash for the top window in this task. Release it once exit - * animation has finished. - * @param frame Window frame of the top window. - * @param playRevealAnimation Play vanish animation. + * @param removalInfo The information used to remove the starting window. + * @hide */ @BinderThread - public void removeStartingWindow(int taskId, @Nullable SurfaceControl leash, - @Nullable Rect frame, boolean playRevealAnimation) {} + public void removeStartingWindow(@NonNull StartingWindowRemovalInfo removalInfo) {} /** * Called when the Task want to copy the splash screen. @@ -226,6 +223,7 @@ public class TaskOrganizer extends WindowOrganizer { } } + /** * Restarts the top activity in the given task by killing its process if it is visible. * @hide @@ -256,10 +254,8 @@ public class TaskOrganizer extends WindowOrganizer { } @Override - public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, - boolean playRevealAnimation) { - mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskId, leash, frame, - playRevealAnimation)); + public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { + mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(removalInfo)); } @Override @@ -298,6 +294,7 @@ public class TaskOrganizer extends WindowOrganizer { } }; + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) private ITaskOrganizerController getController() { try { return getWindowOrganizerController().getTaskOrganizerController(); diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index f1e5fb95ea5452ffceb4abfb180a6f4c52ba9944..b7bb6082296e51a7094059cf2491a6660f54865d 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -50,6 +50,7 @@ public class TaskSnapshot implements Parcelable { /** The size of the snapshot before scaling */ private final Point mTaskSize; private final Rect mContentInsets; + private final Rect mLetterboxInsets; // Whether this snapshot is a down-sampled version of the high resolution snapshot, used // mainly for loading snapshots quickly from disk when user is flinging fast private final boolean mIsLowResolution; @@ -67,9 +68,10 @@ public class TaskSnapshot implements Parcelable { public TaskSnapshot(long id, @NonNull ComponentName topActivityComponent, HardwareBuffer snapshot, @NonNull ColorSpace colorSpace, int orientation, int rotation, Point taskSize, - Rect contentInsets, boolean isLowResolution, boolean isRealSnapshot, - int windowingMode, @WindowInsetsController.Appearance int appearance, - boolean isTranslucent, boolean hasImeSurface) { + Rect contentInsets, Rect letterboxInsets, boolean isLowResolution, + boolean isRealSnapshot, int windowingMode, + @WindowInsetsController.Appearance int appearance, boolean isTranslucent, + boolean hasImeSurface) { mId = id; mTopActivityComponent = topActivityComponent; mSnapshot = snapshot; @@ -79,6 +81,7 @@ public class TaskSnapshot implements Parcelable { mRotation = rotation; mTaskSize = new Point(taskSize); mContentInsets = new Rect(contentInsets); + mLetterboxInsets = new Rect(letterboxInsets); mIsLowResolution = isLowResolution; mIsRealSnapshot = isRealSnapshot; mWindowingMode = windowingMode; @@ -99,6 +102,7 @@ public class TaskSnapshot implements Parcelable { mRotation = source.readInt(); mTaskSize = source.readTypedObject(Point.CREATOR); mContentInsets = source.readTypedObject(Rect.CREATOR); + mLetterboxInsets = source.readTypedObject(Rect.CREATOR); mIsLowResolution = source.readBoolean(); mIsRealSnapshot = source.readBoolean(); mWindowingMode = source.readInt(); @@ -178,6 +182,14 @@ public class TaskSnapshot implements Parcelable { return mContentInsets; } + /** + * @return The letterbox insets on the snapshot. These can be clipped off in order to + * remove any letterbox areas in the snapshot. + */ + public Rect getLetterboxInsets() { + return mLetterboxInsets; + } + /** * @return Whether this snapshot is a down-sampled version of the full resolution. */ @@ -241,6 +253,7 @@ public class TaskSnapshot implements Parcelable { dest.writeInt(mRotation); dest.writeTypedObject(mTaskSize, 0); dest.writeTypedObject(mContentInsets, 0); + dest.writeTypedObject(mLetterboxInsets, 0); dest.writeBoolean(mIsLowResolution); dest.writeBoolean(mIsRealSnapshot); dest.writeInt(mWindowingMode); @@ -262,6 +275,7 @@ public class TaskSnapshot implements Parcelable { + " mRotation=" + mRotation + " mTaskSize=" + mTaskSize.toString() + " mContentInsets=" + mContentInsets.toShortString() + + " mLetterboxInsets=" + mLetterboxInsets.toShortString() + " mIsLowResolution=" + mIsLowResolution + " mIsRealSnapshot=" + mIsRealSnapshot + " mWindowingMode=" + mWindowingMode @@ -289,6 +303,7 @@ public class TaskSnapshot implements Parcelable { private int mRotation; private Point mTaskSize; private Rect mContentInsets; + private Rect mLetterboxInsets; private boolean mIsRealSnapshot; private int mWindowingMode; private @WindowInsetsController.Appearance @@ -340,6 +355,11 @@ public class TaskSnapshot implements Parcelable { return this; } + public Builder setLetterboxInsets(Rect letterboxInsets) { + mLetterboxInsets = letterboxInsets; + return this; + } + public Builder setIsRealSnapshot(boolean realSnapshot) { mIsRealSnapshot = realSnapshot; return this; @@ -387,6 +407,7 @@ public class TaskSnapshot implements Parcelable { mRotation, mTaskSize, mContentInsets, + mLetterboxInsets, // When building a TaskSnapshot with the Builder class, isLowResolution // is always false. Low-res snapshots are only created when loading from // disk. diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java index 141f47b130d1678e205355c17e116a6071a2976e..db15145b0a1e75ae4ecd05b1c0ac30a964bd81dc 100644 --- a/core/java/android/window/TransitionFilter.java +++ b/core/java/android/window/TransitionFilter.java @@ -17,12 +17,17 @@ package android.window; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.view.WindowManager.TransitionType; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; +import android.view.WindowManager; /** * A parcelable filter that can be used for rerouting transitions to a remote. This is a local @@ -33,11 +38,29 @@ import android.os.Parcelable; */ public final class TransitionFilter implements Parcelable { + /** The associated requirement doesn't care about the z-order. */ + public static final int CONTAINER_ORDER_ANY = 0; + /** The associated requirement only matches the top-most (z-order) container. */ + public static final int CONTAINER_ORDER_TOP = 1; + + /** @hide */ + @IntDef(prefix = { "CONTAINER_ORDER_" }, value = { + CONTAINER_ORDER_ANY, + CONTAINER_ORDER_TOP, + }) + public @interface ContainerOrder {} + /** * When non-null: this is a list of transition types that this filter applies to. This filter * will fail for transitions that aren't one of these types. */ - @Nullable public int[] mTypeSet = null; + @Nullable public @TransitionType int[] mTypeSet = null; + + /** All flags must be set on a transition. */ + public @WindowManager.TransitionFlags int mFlags = 0; + + /** All flags must NOT be set on a transition. */ + public @WindowManager.TransitionFlags int mNotFlags = 0; /** * A list of required changes. To pass, a transition must meet all requirements. @@ -49,6 +72,8 @@ public final class TransitionFilter implements Parcelable { private TransitionFilter(Parcel in) { mTypeSet = in.createIntArray(); + mFlags = in.readInt(); + mNotFlags = in.readInt(); mRequirements = in.createTypedArray(Requirement.CREATOR); } @@ -65,10 +90,19 @@ public final class TransitionFilter implements Parcelable { } if (!typePass) return false; } + if ((info.getFlags() & mFlags) != mFlags) { + return false; + } + if ((info.getFlags() & mNotFlags) != 0) { + return false; + } // Make sure info meets all of the requirements. if (mRequirements != null) { for (int i = 0; i < mRequirements.length; ++i) { - if (!mRequirements[i].matches(info)) return false; + final boolean matches = mRequirements[i].matches(info); + if (matches == mRequirements[i].mNot) { + return false; + } } } return true; @@ -78,6 +112,8 @@ public final class TransitionFilter implements Parcelable { /** @hide */ public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeIntArray(mTypeSet); + dest.writeInt(mFlags); + dest.writeInt(mNotFlags); dest.writeTypedArray(mRequirements, flags); } @@ -107,10 +143,12 @@ public final class TransitionFilter implements Parcelable { sb.append("{types=["); if (mTypeSet != null) { for (int i = 0; i < mTypeSet.length; ++i) { - sb.append((i == 0 ? "" : ",") + mTypeSet[i]); + sb.append((i == 0 ? "" : ",") + WindowManager.transitTypeToString(mTypeSet[i])); } } - sb.append("] checks=["); + sb.append("] flags=0x" + Integer.toHexString(mFlags)); + sb.append("] notFlags=0x" + Integer.toHexString(mNotFlags)); + sb.append(" checks=["); if (mRequirements != null) { for (int i = 0; i < mRequirements.length; ++i) { sb.append((i == 0 ? "" : ",") + mRequirements[i]); @@ -125,30 +163,56 @@ public final class TransitionFilter implements Parcelable { */ public static final class Requirement implements Parcelable { public int mActivityType = ACTIVITY_TYPE_UNDEFINED; + + /** This only matches if the change is independent of its parents. */ + public boolean mMustBeIndependent = true; + + /** If this matches, the parent filter will fail */ + public boolean mNot = false; + public int[] mModes = null; + /** Matches only if all the flags here are set on the change. */ + public @TransitionInfo.ChangeFlags int mFlags = 0; + + /** If this needs to be a task. */ + public boolean mMustBeTask = false; + + public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY; + public ComponentName mTopActivity; + public Requirement() { } private Requirement(Parcel in) { mActivityType = in.readInt(); + mMustBeIndependent = in.readBoolean(); + mNot = in.readBoolean(); mModes = in.createIntArray(); + mFlags = in.readInt(); + mMustBeTask = in.readBoolean(); + mOrder = in.readInt(); + mTopActivity = in.readTypedObject(ComponentName.CREATOR); } /** Go through changes and find if at-least one change matches this filter */ boolean matches(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if (!TransitionInfo.isIndependent(change, info)) { + if (mMustBeIndependent && !TransitionInfo.isIndependent(change, info)) { // Only look at independent animating windows. continue; } + if (mOrder == CONTAINER_ORDER_TOP && i > 0) { + continue; + } if (mActivityType != ACTIVITY_TYPE_UNDEFINED) { if (change.getTaskInfo() == null || change.getTaskInfo().getActivityType() != mActivityType) { continue; } } + if (!matchesTopActivity(change.getTaskInfo())) continue; if (mModes != null) { boolean pass = false; for (int m = 0; m < mModes.length; ++m) { @@ -159,24 +223,44 @@ public final class TransitionFilter implements Parcelable { } if (!pass) continue; } + if ((change.getFlags() & mFlags) != mFlags) { + continue; + } + if (mMustBeTask && change.getTaskInfo() == null) { + continue; + } return true; } return false; } + private boolean matchesTopActivity(ActivityManager.RunningTaskInfo info) { + if (mTopActivity == null) return true; + if (info == null) return false; + final ComponentName component = info.topActivity; + return mTopActivity.equals(component); + } + /** Check if the request matches this filter. It may generate false positives */ boolean matches(@NonNull TransitionRequestInfo request) { - // Can't check modes since the transition hasn't been built at this point. + // Can't check modes/order since the transition hasn't been built at this point. if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true; return request.getTriggerTask() != null - && request.getTriggerTask().getActivityType() == mActivityType; + && request.getTriggerTask().getActivityType() == mActivityType + && matchesTopActivity(request.getTriggerTask()); } @Override /** @hide */ public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mActivityType); + dest.writeBoolean(mMustBeIndependent); + dest.writeBoolean(mNot); dest.writeIntArray(mModes); + dest.writeInt(mFlags); + dest.writeBoolean(mMustBeTask); + dest.writeInt(mOrder); + dest.writeTypedObject(mTopActivity, flags); } @NonNull @@ -202,14 +286,31 @@ public final class TransitionFilter implements Parcelable { @Override public String toString() { StringBuilder out = new StringBuilder(); - out.append("{atype=" + WindowConfiguration.activityTypeToString(mActivityType)); + out.append('{'); + if (mNot) out.append("NOT "); + out.append("atype=" + WindowConfiguration.activityTypeToString(mActivityType)); + out.append(" independent=" + mMustBeIndependent); out.append(" modes=["); if (mModes != null) { for (int i = 0; i < mModes.length; ++i) { out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i])); } } - return out.append("]}").toString(); + out.append("]").toString(); + out.append(" flags=" + TransitionInfo.flagsToString(mFlags)); + out.append(" mustBeTask=" + mMustBeTask); + out.append(" order=" + containerOrderToString(mOrder)); + out.append(" topActivity=").append(mTopActivity); + out.append("}"); + return out.toString(); + } + } + + private static String containerOrderToString(int order) { + switch (order) { + case CONTAINER_ORDER_ANY: return "ANY"; + case CONTAINER_ORDER_TOP: return "TOP"; } + return "UNKNOWN(" + order + ")"; } } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 23b8ee4a019f5f8713231426d0f710a847649f97..7208930c0b204d4e1bbe826cea652f0cf035c2c9 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -16,13 +16,23 @@ package android.window; +import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; +import static android.app.ActivityOptions.ANIM_CUSTOM; +import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; +import static android.app.ActivityOptions.ANIM_SCALE_UP; +import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; +import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.view.WindowManager.TransitionFlags; +import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; import android.annotation.IntDef; @@ -31,11 +41,11 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.graphics.Point; import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.os.Parcel; import android.os.Parcelable; import android.view.Surface; import android.view.SurfaceControl; -import android.view.WindowManager; import java.util.ArrayList; import java.util.List; @@ -80,8 +90,22 @@ public final class TransitionInfo implements Parcelable { /** The container has voice session. */ public static final int FLAG_IS_VOICE_INTERACTION = 1 << 4; + /** The container is the display. */ + public static final int FLAG_IS_DISPLAY = 1 << 5; + + /** The container can show on top of lock screen. */ + public static final int FLAG_OCCLUDES_KEYGUARD = 1 << 6; + + /** + * Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is + * used to prevent seamless rotation. + * TODO(b/194540864): Once we can include all windows in transition, then replace this with + * something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations. + */ + public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 5; + public static final int FLAG_FIRST_CUSTOM = 1 << 8; /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { @@ -91,20 +115,24 @@ public final class TransitionInfo implements Parcelable { FLAG_TRANSLUCENT, FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT, FLAG_IS_VOICE_INTERACTION, + FLAG_IS_DISPLAY, + FLAG_OCCLUDES_KEYGUARD, + FLAG_DISPLAY_HAS_ALERT_WINDOWS, FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} - private final @WindowManager.TransitionOldType int mType; - private final @WindowManager.TransitionFlags int mFlags; + private final @TransitionType int mType; + private final @TransitionFlags int mFlags; private final ArrayList mChanges = new ArrayList<>(); private SurfaceControl mRootLeash; private final Point mRootOffset = new Point(); + private AnimationOptions mOptions; + /** @hide */ - public TransitionInfo(@WindowManager.TransitionOldType int type, - @WindowManager.TransitionFlags int flags) { + public TransitionInfo(@TransitionType int type, @TransitionFlags int flags) { mType = type; mFlags = flags; } @@ -112,10 +140,11 @@ public final class TransitionInfo implements Parcelable { private TransitionInfo(Parcel in) { mType = in.readInt(); mFlags = in.readInt(); - in.readList(mChanges, null /* classLoader */); + in.readTypedList(mChanges, Change.CREATOR); mRootLeash = new SurfaceControl(); mRootLeash.readFromParcel(in); mRootOffset.readFromParcel(in); + mOptions = in.readTypedObject(AnimationOptions.CREATOR); } @Override @@ -123,9 +152,10 @@ public final class TransitionInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mType); dest.writeInt(mFlags); - dest.writeList(mChanges); + dest.writeTypedList(mChanges); mRootLeash.writeToParcel(dest, flags); mRootOffset.writeToParcel(dest, flags); + dest.writeTypedObject(mOptions, flags); } @NonNull @@ -154,7 +184,11 @@ public final class TransitionInfo implements Parcelable { mRootOffset.set(offsetLeft, offsetTop); } - public int getType() { + public void setAnimationOptions(AnimationOptions options) { + mOptions = options; + } + + public @TransitionType int getType() { return mType; } @@ -182,6 +216,14 @@ public final class TransitionInfo implements Parcelable { return mRootOffset; } + public AnimationOptions getAnimationOptions() { + return mOptions; + } + + /** + * @return the list of {@link Change}s in this transition. The list is sorted top-to-bottom + * in Z (meaning index 0 is the top-most container). + */ @NonNull public List getChanges() { return mChanges; @@ -208,10 +250,17 @@ public final class TransitionInfo implements Parcelable { mChanges.add(change); } + /** + * Whether this transition includes keyguard going away. + */ + public boolean isKeyguardGoingAway() { + return (mFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("{t=" + transitTypeToString(mType) + " f=" + Integer.toHexString(mFlags) + sb.append("{t=" + transitTypeToString(mType) + " f=0x" + Integer.toHexString(mFlags) + " ro=" + mRootOffset + " c=["); for (int i = 0; i < mChanges.size(); ++i) { if (i > 0) { @@ -257,6 +306,15 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_IS_VOICE_INTERACTION) != 0) { sb.append((sb.length() == 0 ? "" : "|") + "IS_VOICE_INTERACTION"); } + if ((flags & FLAG_IS_DISPLAY) != 0) { + sb.append((sb.length() == 0 ? "" : "|") + "IS_DISPLAY"); + } + if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) { + sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD"); + } + if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { + sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS"); + } if ((flags & FLAG_FIRST_CUSTOM) != 0) { sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM"); } @@ -302,8 +360,10 @@ public final class TransitionInfo implements Parcelable { private final Rect mEndAbsBounds = new Rect(); private final Point mEndRelOffset = new Point(); private ActivityManager.RunningTaskInfo mTaskInfo = null; + private boolean mAllowEnterPip; private int mStartRotation = ROTATION_UNDEFINED; private int mEndRotation = ROTATION_UNDEFINED; + private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { mContainer = container; @@ -321,8 +381,10 @@ public final class TransitionInfo implements Parcelable { mEndAbsBounds.readFromParcel(in); mEndRelOffset.readFromParcel(in); mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); + mAllowEnterPip = in.readBoolean(); mStartRotation = in.readInt(); mEndRotation = in.readInt(); + mRotationAnimation = in.readInt(); } /** Sets the parent of this change's container. The parent must be a participant or null. */ @@ -363,12 +425,25 @@ public final class TransitionInfo implements Parcelable { mTaskInfo = taskInfo; } + /** Sets the allowEnterPip flag which represents AppOpsManager check on PiP permission */ + public void setAllowEnterPip(boolean allowEnterPip) { + mAllowEnterPip = allowEnterPip; + } + /** Sets the start and end rotation of this container. */ public void setRotation(@Surface.Rotation int start, @Surface.Rotation int end) { mStartRotation = start; mEndRotation = end; } + /** + * Sets the app-requested animation type for rotation. Will be one of the + * ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams}; + */ + public void setRotationAnimation(int anim) { + mRotationAnimation = anim; + } + /** @return the container that is changing. May be null if non-remotable (eg. activity) */ @Nullable public WindowContainerToken getContainer() { @@ -432,6 +507,10 @@ public final class TransitionInfo implements Parcelable { return mTaskInfo; } + public boolean getAllowEnterPip() { + return mAllowEnterPip; + } + public int getStartRotation() { return mStartRotation; } @@ -440,6 +519,11 @@ public final class TransitionInfo implements Parcelable { return mEndRotation; } + /** @return the rotation animation. */ + public int getRotationAnimation() { + return mRotationAnimation; + } + /** @hide */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -452,8 +536,10 @@ public final class TransitionInfo implements Parcelable { mEndAbsBounds.writeToParcel(dest, flags); mEndRelOffset.writeToParcel(dest, flags); dest.writeTypedObject(mTaskInfo, flags); + dest.writeBoolean(mAllowEnterPip); dest.writeInt(mStartRotation); dest.writeInt(mEndRotation); + dest.writeInt(mRotationAnimation); } @NonNull @@ -481,7 +567,149 @@ public final class TransitionInfo implements Parcelable { return "{" + mContainer + "(" + mParent + ") leash=" + mLeash + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb=" + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r=" - + mStartRotation + "->" + mEndRotation + "}"; + + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}"; + } + } + + /** Represents animation options during a transition */ + public static final class AnimationOptions implements Parcelable { + + private int mType; + private int mEnterResId; + private int mExitResId; + private boolean mOverrideTaskTransition; + private String mPackageName; + private final Rect mTransitionBounds = new Rect(); + private HardwareBuffer mThumbnail; + + private AnimationOptions(int type) { + mType = type; + } + + public AnimationOptions(Parcel in) { + mType = in.readInt(); + mEnterResId = in.readInt(); + mExitResId = in.readInt(); + mOverrideTaskTransition = in.readBoolean(); + mPackageName = in.readString(); + mTransitionBounds.readFromParcel(in); + mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR); + } + + public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId, + int exitResId, boolean overrideTaskTransition) { + AnimationOptions options = new AnimationOptions(ANIM_CUSTOM); + options.mPackageName = packageName; + options.mEnterResId = enterResId; + options.mExitResId = exitResId; + options.mOverrideTaskTransition = overrideTaskTransition; + return options; + } + + public static AnimationOptions makeClipRevealAnimOptions(int startX, int startY, int width, + int height) { + AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL); + options.mTransitionBounds.set(startX, startY, startX + width, startY + height); + return options; + } + + public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width, + int height) { + AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP); + options.mTransitionBounds.set(startX, startY, startX + width, startY + height); + return options; + } + + public static AnimationOptions makeThumnbnailAnimOptions(HardwareBuffer srcThumb, + int startX, int startY, boolean scaleUp) { + AnimationOptions options = new AnimationOptions( + scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN); + options.mTransitionBounds.set(startX, startY, startX, startY); + options.mThumbnail = srcThumb; + return options; + } + + public static AnimationOptions makeCrossProfileAnimOptions() { + AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS); + return options; + } + + public int getType() { + return mType; + } + + public int getEnterResId() { + return mEnterResId; + } + + public int getExitResId() { + return mExitResId; + } + + public boolean getOverrideTaskTransition() { + return mOverrideTaskTransition; + } + + public String getPackageName() { + return mPackageName; + } + + public Rect getTransitionBounds() { + return mTransitionBounds; + } + + public HardwareBuffer getThumbnail() { + return mThumbnail; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeInt(mEnterResId); + dest.writeInt(mExitResId); + dest.writeBoolean(mOverrideTaskTransition); + dest.writeString(mPackageName); + mTransitionBounds.writeToParcel(dest, flags); + dest.writeTypedObject(mThumbnail, flags); + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public AnimationOptions createFromParcel(Parcel in) { + return new AnimationOptions(in); + } + + @Override + public AnimationOptions[] newArray(int size) { + return new AnimationOptions[size]; + } + }; + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + @NonNull + private static String typeToString(int mode) { + switch(mode) { + case ANIM_CUSTOM: return "ANIM_CUSTOM"; + case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL"; + case ANIM_SCALE_UP: return "ANIM_SCALE_UP"; + case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP"; + case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN"; + case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS"; + default: return ""; + } + } + + @Override + public String toString() { + return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName + + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}"; } } } diff --git a/core/java/android/window/TransitionMetrics.java b/core/java/android/window/TransitionMetrics.java new file mode 100644 index 0000000000000000000000000000000000000000..9a93c1a1ffd68088dfa61b4e4e068622c0575d7b --- /dev/null +++ b/core/java/android/window/TransitionMetrics.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Singleton; + +/** + * A helper class for who plays transition animation can report its metrics easily. + * @hide + */ +public class TransitionMetrics { + + private final ITransitionMetricsReporter mTransitionMetricsReporter; + + private TransitionMetrics(ITransitionMetricsReporter reporter) { + mTransitionMetricsReporter = reporter; + } + + /** Reports the current timestamp as when the transition animation starts. */ + public void reportAnimationStart(IBinder transitionToken) { + try { + mTransitionMetricsReporter.reportAnimationStart(transitionToken, + SystemClock.elapsedRealtime()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** Gets the singleton instance of TransitionMetrics. */ + public static TransitionMetrics getInstance() { + return sTransitionMetrics.get(); + } + + private static final Singleton sTransitionMetrics = new Singleton<>() { + @Override + protected TransitionMetrics create() { + return new TransitionMetrics(WindowOrganizer.getTransitionMetricsReporter()); + } + }; +} diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java index cc493ab63e222936c6616855b65d27e7409e11a7..f7707317efd7c179efc416576775d11fa3ef77e4 100644 --- a/core/java/android/window/TransitionRequestInfo.java +++ b/core/java/android/window/TransitionRequestInfo.java @@ -40,11 +40,11 @@ public final class TransitionRequestInfo implements Parcelable { private @Nullable ActivityManager.RunningTaskInfo mTriggerTask; /** If non-null, a remote-transition associated with the source of this transition. */ - private @Nullable IRemoteTransition mRemoteTransition; + private @Nullable RemoteTransition mRemoteTransition; - // Code below generated by codegen v1.0.22. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -72,7 +72,7 @@ public final class TransitionRequestInfo implements Parcelable { public TransitionRequestInfo( @WindowManager.TransitionType int type, @Nullable ActivityManager.RunningTaskInfo triggerTask, - @Nullable IRemoteTransition remoteTransition) { + @Nullable RemoteTransition remoteTransition) { this.mType = type; com.android.internal.util.AnnotationValidations.validate( WindowManager.TransitionType.class, null, mType); @@ -103,7 +103,7 @@ public final class TransitionRequestInfo implements Parcelable { * If non-null, a remote-transition associated with the source of this transition. */ @DataClass.Generated.Member - public @Nullable IRemoteTransition getRemoteTransition() { + public @Nullable RemoteTransition getRemoteTransition() { return mRemoteTransition; } @@ -121,7 +121,7 @@ public final class TransitionRequestInfo implements Parcelable { * If non-null, a remote-transition associated with the source of this transition. */ @DataClass.Generated.Member - public @android.annotation.NonNull TransitionRequestInfo setRemoteTransition(@android.annotation.NonNull IRemoteTransition value) { + public @android.annotation.NonNull TransitionRequestInfo setRemoteTransition(@android.annotation.NonNull RemoteTransition value) { mRemoteTransition = value; return this; } @@ -151,7 +151,7 @@ public final class TransitionRequestInfo implements Parcelable { dest.writeByte(flg); dest.writeInt(mType); if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags); - if (mRemoteTransition != null) dest.writeStrongInterface(mRemoteTransition); + if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags); } @Override @@ -168,7 +168,7 @@ public final class TransitionRequestInfo implements Parcelable { byte flg = in.readByte(); int type = in.readInt(); ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); - IRemoteTransition remoteTransition = (flg & 0x4) == 0 ? null : IRemoteTransition.Stub.asInterface(in.readStrongBinder()); + RemoteTransition remoteTransition = (flg & 0x4) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR); this.mType = type; com.android.internal.util.AnnotationValidations.validate( @@ -194,10 +194,10 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1610060387917L, - codegenVersion = "1.0.22", + time = 1629321632222L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", - inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.IRemoteTransition mRemoteTransition\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") + inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index c0af57214e5e559a0c9e60d7e37bb8b5ac216c75..6864ccaa7c8a32015cee9e49d61667ba557a0ca6 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -19,7 +19,9 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.PendingIntent; import android.app.WindowConfiguration; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; @@ -34,6 +36,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Represents a collection of operations on some WindowContainers that should be applied all at @@ -48,11 +51,19 @@ public final class WindowContainerTransaction implements Parcelable { // Flat list because re-order operations are order-dependent private final ArrayList mHierarchyOps = new ArrayList<>(); + @Nullable + private IBinder mErrorCallbackToken; + + @Nullable + private ITaskFragmentOrganizer mTaskFragmentOrganizer; + public WindowContainerTransaction() {} private WindowContainerTransaction(Parcel in) { in.readMap(mChanges, null /* loader */); in.readList(mHierarchyOps, null /* loader */); + mErrorCallbackToken = in.readStrongBinder(); + mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder()); } private Change getOrCreateChange(IBinder token) { @@ -284,7 +295,7 @@ public final class WindowContainerTransaction implements Parcelable { } /** - * Reparent's all children tasks of {@param currentParent} in the specified + * Reparent's all children tasks or the top task of {@param currentParent} in the specified * {@param windowingMode} and {@param activityType} to {@param newParent} in their current * z-order. * @@ -295,20 +306,45 @@ public final class WindowContainerTransaction implements Parcelable { * @param activityTypes of the tasks to reparent. * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to * the bottom. + * @param reparentTopOnly When {@code true}, only reparent the top task which fit windowingModes + * and activityTypes. + * @hide */ @NonNull public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent, @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes, - @Nullable int[] activityTypes, boolean onTop) { + @Nullable int[] activityTypes, boolean onTop, boolean reparentTopOnly) { mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent( currentParent != null ? currentParent.asBinder() : null, newParent != null ? newParent.asBinder() : null, windowingModes, activityTypes, - onTop)); + onTop, + reparentTopOnly)); return this; } + /** + * Reparent's all children tasks of {@param currentParent} in the specified + * {@param windowingMode} and {@param activityType} to {@param newParent} in their current + * z-order. + * + * @param currentParent of the tasks to perform the operation no. + * {@code null} will perform the operation on the display. + * @param newParent for the tasks. {@code null} will perform the operation on the display. + * @param windowingModes of the tasks to reparent. + * @param activityTypes of the tasks to reparent. + * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to + * the bottom. + */ + @NonNull + public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent, + @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes, + @Nullable int[] activityTypes, boolean onTop) { + return reparentTasks(currentParent, newParent, windowingModes, activityTypes, onTop, + false /* reparentTopOnly */); + } + /** * Sets whether a container should be the launch root for the specified windowing mode and * activity type. This currently only applies to Task containers created by organizer. @@ -325,16 +361,19 @@ public final class WindowContainerTransaction implements Parcelable { /** * Sets to containers adjacent to each other. Containers below two visible adjacent roots will - * be made invisible. This currently only applies to Task containers created by organizer. + * be made invisible. This currently only applies to TaskFragment containers created by + * organizer. * @param root1 the first root. * @param root2 the second root. */ @NonNull public WindowContainerTransaction setAdjacentRoots( - @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) { + @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2, + boolean moveTogether) { mHierarchyOps.add(HierarchyOp.createForAdjacentRoots( root1.asBinder(), - root2.asBinder())); + root2.asBinder(), + moveTogether)); return this; } @@ -377,6 +416,177 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + /** + * Sends a pending intent in sync. + * @param sender The PendingIntent sender. + * @param intent The fillIn intent to patch over the sender's base intent. + * @param options bundle containing ActivityOptions for the task's top activity. + * @hide + */ + @NonNull + public WindowContainerTransaction sendPendingIntent(PendingIntent sender, Intent intent, + @Nullable Bundle options) { + mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT) + .setLaunchOptions(options) + .setPendingIntent(sender) + .setActivityIntent(intent) + .build()); + return this; + } + + /** + * Creates a new TaskFragment with the given options. + * @param taskFragmentOptions the options used to create the TaskFragment. + */ + @NonNull + public WindowContainerTransaction createTaskFragment( + @NonNull TaskFragmentCreationParams taskFragmentOptions) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT) + .setTaskFragmentCreationOptions(taskFragmentOptions) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** + * Deletes an existing TaskFragment. Any remaining activities below it will be destroyed. + * @param taskFragment the TaskFragment to be removed. + */ + @NonNull + public WindowContainerTransaction deleteTaskFragment( + @NonNull WindowContainerToken taskFragment) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT) + .setContainer(taskFragment.asBinder()) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** + * Starts an activity in the TaskFragment. + * @param fragmentToken client assigned unique token to create TaskFragment with specified in + * {@link TaskFragmentCreationParams#getFragmentToken()}. + * @param callerToken the activity token that initialized the activity launch. + * @param activityIntent intent to start the activity. + * @param activityOptions ActivityOptions to start the activity with. + * @see android.content.Context#startActivity(Intent, Bundle). + */ + @NonNull + public WindowContainerTransaction startActivityInTaskFragment( + @NonNull IBinder fragmentToken, @NonNull IBinder callerToken, + @NonNull Intent activityIntent, @Nullable Bundle activityOptions) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT) + .setContainer(fragmentToken) + .setReparentContainer(callerToken) + .setActivityIntent(activityIntent) + .setLaunchOptions(activityOptions) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** + * Moves an activity into the TaskFragment. + * @param fragmentToken client assigned unique token to create TaskFragment with specified in + * {@link TaskFragmentCreationParams#getFragmentToken()}. + * @param activityToken activity to be reparented. + */ + @NonNull + public WindowContainerTransaction reparentActivityToTaskFragment( + @NonNull IBinder fragmentToken, @NonNull IBinder activityToken) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT) + .setReparentContainer(fragmentToken) + .setContainer(activityToken) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** + * Reparents all children of one TaskFragment to another. + * @param oldParent children of this TaskFragment will be reparented. + * @param newParent the new parent TaskFragment to move the children to. If {@code null}, the + * children will be moved to the leaf Task. + */ + @NonNull + public WindowContainerTransaction reparentChildren( + @NonNull WindowContainerToken oldParent, + @Nullable WindowContainerToken newParent) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN) + .setContainer(oldParent.asBinder()) + .setReparentContainer(newParent != null ? newParent.asBinder() : null) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** + * Sets to TaskFragments adjacent to each other. Containers below two visible adjacent + * TaskFragments will be made invisible. This is similar to + * {@link #setAdjacentRoots(WindowContainerToken, WindowContainerToken)}, but can be used with + * fragmentTokens when that TaskFragments haven't been created (but will be created in the same + * {@link WindowContainerTransaction}). + * To reset it, pass {@code null} for {@code fragmentToken2}. + * @param fragmentToken1 client assigned unique token to create TaskFragment with specified + * in {@link TaskFragmentCreationParams#getFragmentToken()}. + * @param fragmentToken2 client assigned unique token to create TaskFragment with specified + * in {@link TaskFragmentCreationParams#getFragmentToken()}. If it is + * {@code null}, the transaction will reset the adjacent TaskFragment. + */ + @NonNull + public WindowContainerTransaction setAdjacentTaskFragments( + @NonNull IBinder fragmentToken1, @Nullable IBinder fragmentToken2, + @Nullable TaskFragmentAdjacentParams params) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS) + .setContainer(fragmentToken1) + .setReparentContainer(fragmentToken2) + .setLaunchOptions(params != null ? params.toBundle() : null) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** + * When this {@link WindowContainerTransaction} failed to finish on the server side, it will + * trigger callback with this {@param errorCallbackToken}. + * @param errorCallbackToken client provided token that will be passed back as parameter in + * the callback if there is an error on the server side. + * @see ITaskFragmentOrganizer#onTaskFragmentError + */ + @NonNull + public WindowContainerTransaction setErrorCallbackToken(@NonNull IBinder errorCallbackToken) { + if (mErrorCallbackToken != null) { + throw new IllegalStateException("Can't set multiple error token for one transaction."); + } + mErrorCallbackToken = errorCallbackToken; + return this; + } + + /** + * Sets the {@link TaskFragmentOrganizer} that applies this {@link WindowContainerTransaction}. + * When this is set, the server side will not check for the permission of + * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, but will ensure this WCT only + * contains operations that are allowed for this organizer, such as modifying TaskFragments that + * are organized by this organizer. + * @hide + */ + @NonNull + WindowContainerTransaction setTaskFragmentOrganizer(@NonNull ITaskFragmentOrganizer organizer) { + if (mTaskFragmentOrganizer != null) { + throw new IllegalStateException("Can't set multiple organizers for one transaction."); + } + mTaskFragmentOrganizer = organizer; + return this; + } + /** * Merges another WCT into this one. * @param transfer When true, this will transfer everything from other potentially leaving @@ -398,6 +608,23 @@ public final class WindowContainerTransaction implements Parcelable { mHierarchyOps.add(transfer ? other.mHierarchyOps.get(i) : new HierarchyOp(other.mHierarchyOps.get(i))); } + if (mErrorCallbackToken != null && other.mErrorCallbackToken != null && mErrorCallbackToken + != other.mErrorCallbackToken) { + throw new IllegalArgumentException("Can't merge two WCTs with different error token"); + } + final IBinder taskFragmentOrganizerAsBinder = mTaskFragmentOrganizer != null + ? mTaskFragmentOrganizer.asBinder() + : null; + final IBinder otherTaskFragmentOrganizerAsBinder = other.mTaskFragmentOrganizer != null + ? other.mTaskFragmentOrganizer.asBinder() + : null; + if (!Objects.equals(taskFragmentOrganizerAsBinder, otherTaskFragmentOrganizerAsBinder)) { + throw new IllegalArgumentException( + "Can't merge two WCTs from different TaskFragmentOrganizers"); + } + mErrorCallbackToken = mErrorCallbackToken != null + ? mErrorCallbackToken + : other.mErrorCallbackToken; } /** @hide */ @@ -415,10 +642,26 @@ public final class WindowContainerTransaction implements Parcelable { return mHierarchyOps; } + /** @hide */ + @Nullable + public IBinder getErrorCallbackToken() { + return mErrorCallbackToken; + } + + /** @hide */ + @Nullable + public ITaskFragmentOrganizer getTaskFragmentOrganizer() { + return mTaskFragmentOrganizer; + } + @Override @NonNull public String toString() { - return "WindowContainerTransaction { changes = " + mChanges + " hops = " + mHierarchyOps + return "WindowContainerTransaction {" + + " changes = " + mChanges + + " hops = " + mHierarchyOps + + " errorCallbackToken=" + mErrorCallbackToken + + " taskFragmentOrganizer=" + mTaskFragmentOrganizer + " }"; } @@ -427,6 +670,8 @@ public final class WindowContainerTransaction implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeMap(mChanges); dest.writeList(mHierarchyOps); + dest.writeStrongBinder(mErrorCallbackToken); + dest.writeStrongInterface(mTaskFragmentOrganizer); } @Override @@ -705,6 +950,13 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS = 4; public static final int HIERARCHY_OP_TYPE_LAUNCH_TASK = 5; public static final int HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT = 6; + public static final int HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT = 7; + public static final int HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT = 8; + public static final int HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT = 9; + public static final int HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT = 10; + public static final int HIERARCHY_OP_TYPE_REPARENT_CHILDREN = 11; + public static final int HIERARCHY_OP_TYPE_PENDING_INTENT = 12; + public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS = 13; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -713,75 +965,111 @@ public final class WindowContainerTransaction implements Parcelable { private final int mType; // Container we are performing the operation on. - private final IBinder mContainer; + @Nullable + private IBinder mContainer; // If this is same as mContainer, then only change position, don't reparent. - private final IBinder mReparent; + @Nullable + private IBinder mReparent; // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom. - private final boolean mToTop; + private boolean mToTop; - final private int[] mWindowingModes; - final private int[] mActivityTypes; + private boolean mReparentTopOnly; - private final Bundle mLaunchOptions; + // TODO(b/207185041): Remove this once having a single-top root for split screen. + private boolean mMoveAdjacentTogether; + + @Nullable + private int[] mWindowingModes; + + @Nullable + private int[] mActivityTypes; + + @Nullable + private Bundle mLaunchOptions; + + @Nullable + private Intent mActivityIntent; + + // Used as options for WindowContainerTransaction#createTaskFragment(). + @Nullable + private TaskFragmentCreationParams mTaskFragmentCreationOptions; + + @Nullable + private PendingIntent mPendingIntent; public static HierarchyOp createForReparent( @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) { - return new HierarchyOp(HIERARCHY_OP_TYPE_REPARENT, - container, reparent, null, null, toTop, null); + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT) + .setContainer(container) + .setReparentContainer(reparent) + .setToTop(toTop) + .build(); } public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop) { - return new HierarchyOp(HIERARCHY_OP_TYPE_REORDER, - container, container, null, null, toTop, null); + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REORDER) + .setContainer(container) + .setReparentContainer(container) + .setToTop(toTop) + .build(); } public static HierarchyOp createForChildrenTasksReparent(IBinder currentParent, - IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop) { - return new HierarchyOp(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT, - currentParent, newParent, windowingModes, activityTypes, onTop, null); + IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop, + boolean reparentTopOnly) { + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT) + .setContainer(currentParent) + .setReparentContainer(newParent) + .setWindowingModes(windowingModes) + .setActivityTypes(activityTypes) + .setToTop(onTop) + .setReparentTopOnly(reparentTopOnly) + .build(); } public static HierarchyOp createForSetLaunchRoot(IBinder container, int[] windowingModes, int[] activityTypes) { - return new HierarchyOp(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT, - container, null, windowingModes, activityTypes, false, null); + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT) + .setContainer(container) + .setWindowingModes(windowingModes) + .setActivityTypes(activityTypes) + .build(); } - public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2) { - return new HierarchyOp(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS, - root1, root2, null, null, false, null); + /** Create a hierarchy op for setting adjacent root tasks. */ + public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2, + boolean moveTogether) { + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS) + .setContainer(root1) + .setReparentContainer(root2) + .setMoveAdjacentTogether(moveTogether) + .build(); } /** Create a hierarchy op for launching a task. */ public static HierarchyOp createForTaskLaunch(int taskId, @Nullable Bundle options) { final Bundle fullOptions = options == null ? new Bundle() : options; fullOptions.putInt(LAUNCH_KEY_TASK_ID, taskId); - return new HierarchyOp(HIERARCHY_OP_TYPE_LAUNCH_TASK, null, null, null, null, true, - fullOptions); + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_LAUNCH_TASK) + .setToTop(true) + .setLaunchOptions(fullOptions) + .build(); } /** Create a hierarchy op for setting launch adjacent flag root. */ public static HierarchyOp createForSetLaunchAdjacentFlagRoot(IBinder container, boolean clearRoot) { - return new HierarchyOp(HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT, container, null, - null, null, clearRoot, null); + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT) + .setContainer(container) + .setToTop(clearRoot) + .build(); } - - private HierarchyOp(int type, @Nullable IBinder container, @Nullable IBinder reparent, - int[] windowingModes, int[] activityTypes, boolean toTop, - @Nullable Bundle launchOptions) { + /** Only creates through {@link Builder}. */ + private HierarchyOp(int type) { mType = type; - mContainer = container; - mReparent = reparent; - mWindowingModes = windowingModes != null ? - Arrays.copyOf(windowingModes, windowingModes.length) : null; - mActivityTypes = activityTypes != null ? - Arrays.copyOf(activityTypes, activityTypes.length) : null; - mToTop = toTop; - mLaunchOptions = launchOptions; } public HierarchyOp(@NonNull HierarchyOp copy) { @@ -789,9 +1077,14 @@ public final class WindowContainerTransaction implements Parcelable { mContainer = copy.mContainer; mReparent = copy.mReparent; mToTop = copy.mToTop; + mReparentTopOnly = copy.mReparentTopOnly; + mMoveAdjacentTogether = copy.mMoveAdjacentTogether; mWindowingModes = copy.mWindowingModes; mActivityTypes = copy.mActivityTypes; mLaunchOptions = copy.mLaunchOptions; + mActivityIntent = copy.mActivityIntent; + mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions; + mPendingIntent = copy.mPendingIntent; } protected HierarchyOp(Parcel in) { @@ -799,9 +1092,14 @@ public final class WindowContainerTransaction implements Parcelable { mContainer = in.readStrongBinder(); mReparent = in.readStrongBinder(); mToTop = in.readBoolean(); + mReparentTopOnly = in.readBoolean(); + mMoveAdjacentTogether = in.readBoolean(); mWindowingModes = in.createIntArray(); mActivityTypes = in.createIntArray(); mLaunchOptions = in.readBundle(); + mActivityIntent = in.readTypedObject(Intent.CREATOR); + mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR); + mPendingIntent = in.readTypedObject(PendingIntent.CREATOR); } public int getType() { @@ -827,10 +1125,23 @@ public final class WindowContainerTransaction implements Parcelable { return mReparent; } + @NonNull + public IBinder getCallingActivity() { + return mReparent; + } + public boolean getToTop() { return mToTop; } + public boolean getReparentTopOnly() { + return mReparentTopOnly; + } + + public boolean getMoveAdjacentTogether() { + return mMoveAdjacentTogether; + } + public int[] getWindowingModes() { return mWindowingModes; } @@ -844,17 +1155,33 @@ public final class WindowContainerTransaction implements Parcelable { return mLaunchOptions; } + @Nullable + public Intent getActivityIntent() { + return mActivityIntent; + } + + @Nullable + public TaskFragmentCreationParams getTaskFragmentCreationOptions() { + return mTaskFragmentCreationOptions; + } + + @Nullable + public PendingIntent getPendingIntent() { + return mPendingIntent; + } + @Override public String toString() { switch (mType) { case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "{ChildrenTasksReparent: from=" + mContainer + " to=" + mReparent - + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes - + " mActivityType=" + mActivityTypes + "}"; + + " mToTop=" + mToTop + " mReparentTopOnly=" + mReparentTopOnly + + " mWindowingMode=" + Arrays.toString(mWindowingModes) + + " mActivityType=" + Arrays.toString(mActivityTypes) + "}"; case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "{SetLaunchRoot: container=" + mContainer - + " mWindowingMode=" + mWindowingModes - + " mActivityType=" + mActivityTypes + "}"; + + " mWindowingMode=" + Arrays.toString(mWindowingModes) + + " mActivityType=" + Arrays.toString(mActivityTypes) + "}"; case HIERARCHY_OP_TYPE_REPARENT: return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ") + mReparent + "}"; @@ -862,16 +1189,34 @@ public final class WindowContainerTransaction implements Parcelable { return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}"; case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "{SetAdjacentRoot: container=" + mContainer - + " adjacentRoot=" + mReparent + "}"; + + " adjacentRoot=" + mReparent + " mMoveAdjacentTogether=" + + mMoveAdjacentTogether + "}"; case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "{LaunchTask: " + mLaunchOptions + "}"; case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "{SetAdjacentFlagRoot: container=" + mContainer + " clearRoot=" + mToTop + "}"; + case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: + return "{CreateTaskFragment: options=" + mTaskFragmentCreationOptions + "}"; + case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: + return "{DeleteTaskFragment: taskFragment=" + mContainer + "}"; + case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: + return "{StartActivityInTaskFragment: fragmentToken=" + mContainer + " intent=" + + mActivityIntent + " options=" + mLaunchOptions + "}"; + case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: + return "{ReparentActivityToTaskFragment: fragmentToken=" + mReparent + + " activity=" + mContainer + "}"; + case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: + return "{ReparentChildren: oldParent=" + mContainer + " newParent=" + mReparent + + "}"; + case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: + return "{SetAdjacentTaskFragments: container=" + mContainer + + " adjacentContainer=" + mReparent + "}"; default: return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent - + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes - + " mActivityType=" + mActivityTypes + "}"; + + " mToTop=" + mToTop + + " mWindowingMode=" + Arrays.toString(mWindowingModes) + + " mActivityType=" + Arrays.toString(mActivityTypes) + "}"; } } @@ -881,9 +1226,14 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeStrongBinder(mContainer); dest.writeStrongBinder(mReparent); dest.writeBoolean(mToTop); + dest.writeBoolean(mReparentTopOnly); + dest.writeBoolean(mMoveAdjacentTogether); dest.writeIntArray(mWindowingModes); dest.writeIntArray(mActivityTypes); dest.writeBundle(mLaunchOptions); + dest.writeTypedObject(mActivityIntent, flags); + dest.writeTypedObject(mTaskFragmentCreationOptions, flags); + dest.writeTypedObject(mPendingIntent, flags); } @Override @@ -902,5 +1252,182 @@ public final class WindowContainerTransaction implements Parcelable { return new HierarchyOp[size]; } }; + + private static class Builder { + + private final int mType; + + @Nullable + private IBinder mContainer; + + @Nullable + private IBinder mReparent; + + private boolean mToTop; + + private boolean mReparentTopOnly; + + private boolean mMoveAdjacentTogether; + + @Nullable + private int[] mWindowingModes; + + @Nullable + private int[] mActivityTypes; + + @Nullable + private Bundle mLaunchOptions; + + @Nullable + private Intent mActivityIntent; + + @Nullable + private TaskFragmentCreationParams mTaskFragmentCreationOptions; + + @Nullable + private PendingIntent mPendingIntent; + + Builder(int type) { + mType = type; + } + + Builder setContainer(@Nullable IBinder container) { + mContainer = container; + return this; + } + + Builder setReparentContainer(@Nullable IBinder reparentContainer) { + mReparent = reparentContainer; + return this; + } + + Builder setToTop(boolean toTop) { + mToTop = toTop; + return this; + } + + Builder setReparentTopOnly(boolean reparentTopOnly) { + mReparentTopOnly = reparentTopOnly; + return this; + } + + Builder setMoveAdjacentTogether(boolean moveAdjacentTogether) { + mMoveAdjacentTogether = moveAdjacentTogether; + return this; + } + + Builder setWindowingModes(@Nullable int[] windowingModes) { + mWindowingModes = windowingModes; + return this; + } + + Builder setActivityTypes(@Nullable int[] activityTypes) { + mActivityTypes = activityTypes; + return this; + } + + Builder setLaunchOptions(@Nullable Bundle launchOptions) { + mLaunchOptions = launchOptions; + return this; + } + + Builder setActivityIntent(@Nullable Intent activityIntent) { + mActivityIntent = activityIntent; + return this; + } + + Builder setPendingIntent(@Nullable PendingIntent sender) { + mPendingIntent = sender; + return this; + } + + Builder setTaskFragmentCreationOptions( + @Nullable TaskFragmentCreationParams taskFragmentCreationOptions) { + mTaskFragmentCreationOptions = taskFragmentCreationOptions; + return this; + } + + HierarchyOp build() { + final HierarchyOp hierarchyOp = new HierarchyOp(mType); + hierarchyOp.mContainer = mContainer; + hierarchyOp.mReparent = mReparent; + hierarchyOp.mWindowingModes = mWindowingModes != null + ? Arrays.copyOf(mWindowingModes, mWindowingModes.length) + : null; + hierarchyOp.mActivityTypes = mActivityTypes != null + ? Arrays.copyOf(mActivityTypes, mActivityTypes.length) + : null; + hierarchyOp.mToTop = mToTop; + hierarchyOp.mReparentTopOnly = mReparentTopOnly; + hierarchyOp.mMoveAdjacentTogether = mMoveAdjacentTogether; + hierarchyOp.mLaunchOptions = mLaunchOptions; + hierarchyOp.mActivityIntent = mActivityIntent; + hierarchyOp.mPendingIntent = mPendingIntent; + hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions; + + return hierarchyOp; + } + } + } + + /** + * Helper class for building an options Bundle that can be used to set adjacent rules of + * TaskFragments. + */ + public static class TaskFragmentAdjacentParams { + private static final String DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL = + "android:transaction.adjacent.option.delay_primary_removal"; + private static final String DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL = + "android:transaction.adjacent.option.delay_secondary_removal"; + + private boolean mDelayPrimaryLastActivityRemoval; + private boolean mDelaySecondaryLastActivityRemoval; + + public TaskFragmentAdjacentParams() { + } + + public TaskFragmentAdjacentParams(@NonNull Bundle bundle) { + mDelayPrimaryLastActivityRemoval = bundle.getBoolean( + DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL); + mDelaySecondaryLastActivityRemoval = bundle.getBoolean( + DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL); + } + + /** @see #shouldDelayPrimaryLastActivityRemoval() */ + public void setShouldDelayPrimaryLastActivityRemoval(boolean delay) { + mDelayPrimaryLastActivityRemoval = delay; + } + + /** @see #shouldDelaySecondaryLastActivityRemoval() */ + public void setShouldDelaySecondaryLastActivityRemoval(boolean delay) { + mDelaySecondaryLastActivityRemoval = delay; + } + + /** + * Whether to delay the last activity of the primary adjacent TaskFragment being immediately + * removed while finishing. + *

    + * It is usually set to {@code true} to give organizer an opportunity to perform other + * actions or animations. An example is to finish together with the adjacent TaskFragment. + *

    + */ + public boolean shouldDelayPrimaryLastActivityRemoval() { + return mDelayPrimaryLastActivityRemoval; + } + + /** + * Similar to {@link #shouldDelayPrimaryLastActivityRemoval()}, but for the secondary + * TaskFragment. + */ + public boolean shouldDelaySecondaryLastActivityRemoval() { + return mDelaySecondaryLastActivityRemoval; + } + + Bundle toBundle() { + final Bundle b = new Bundle(); + b.putBoolean(DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL, mDelayPrimaryLastActivityRemoval); + b.putBoolean(DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL, mDelaySecondaryLastActivityRemoval); + return b; + } } } diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java index 6d0a6bd559ae212b65a5f5171182cf43b0398dcd..cfccb712127e9c11c4c452cffdac40ea2ed8732e 100644 --- a/core/java/android/window/WindowContext.java +++ b/core/java/android/window/WindowContext.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.res.Configuration; import android.os.Bundle; +import android.view.Display; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; @@ -42,23 +43,33 @@ import java.lang.ref.Reference; * @hide */ @UiContext -public class WindowContext extends ContextWrapper { +public class WindowContext extends ContextWrapper implements WindowProvider { private final WindowManager mWindowManager; - private final @WindowManager.LayoutParams.WindowType int mType; - private final @Nullable Bundle mOptions; + @WindowManager.LayoutParams.WindowType + private final int mType; + @Nullable + private final Bundle mOptions; private final ComponentCallbacksController mCallbacksController = new ComponentCallbacksController(); private final WindowContextController mController; /** - * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to - * the token. + * Default implementation of {@link WindowContext} + *

    + * Note that the users should call {@link Context#createWindowContext(Display, int, Bundle)} + * to create a {@link WindowContext} instead of using this constructor + *

    + * Example usage: + *

    +     * Bundle options = new Bundle();
    +     * options.put(KEY_ROOT_DISPLAY_AREA_ID, displayAreaInfo.rootDisplayAreaId);
    +     * Context windowContext = context.createWindowContext(display, windowType, options);
    +     * 

    * - * @param base Base {@link Context} for this new instance. - * @param type Window type to be used with this context. + * @param base Base {@link Context} for this new instance. + * @param type Window type to be used with this context. * @param options A bundle used to pass window-related options. - * - * @hide + * @see DisplayAreaInfo#rootDisplayAreaId */ public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) { super(base); @@ -104,10 +115,13 @@ public class WindowContext extends ContextWrapper { @Override public void destroy() { - mCallbacksController.clearCallbacks(); - // Called to the base ContextImpl to do final clean-up. - getBaseContext().destroy(); - Reference.reachabilityFence(this); + try { + mCallbacksController.clearCallbacks(); + // Called to the base ContextImpl to do final clean-up. + getBaseContext().destroy(); + } finally { + Reference.reachabilityFence(this); + } } @Override @@ -124,4 +138,15 @@ public class WindowContext extends ContextWrapper { void dispatchConfigurationChanged(@NonNull Configuration newConfig) { mCallbacksController.dispatchConfigurationChanged(newConfig); } + + @Override + public int getWindowType() { + return mType; + } + + @Nullable + @Override + public Bundle getWindowContextOptions() { + return mOptions; + } } diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 505b450086632ec2c02af9b8784b6d9c6716b780..17b675f93f86b79b66eb062590a52f4eedad94b5 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -19,13 +19,10 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; import android.view.IWindowManager; import android.view.WindowManager.LayoutParams.WindowType; -import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; @@ -38,7 +35,6 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ public class WindowContextController { - private final IWindowManager mWms; /** * {@code true} to indicate that the {@code mToken} is associated with a * {@link com.android.server.wm.DisplayArea}. Note that {@code mToken} is able to attach a @@ -56,14 +52,7 @@ public class WindowContextController { * {@link Context#getWindowContextToken()}. */ public WindowContextController(@NonNull WindowTokenClient token) { - this(token, WindowManagerGlobal.getWindowManagerService()); - } - - /** Used for test only. DO NOT USE it in production code. */ - @VisibleForTesting - public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) { mToken = token; - mWms = mockWms; } /** @@ -80,18 +69,7 @@ public class WindowContextController { throw new IllegalStateException("A Window Context can be only attached to " + "a DisplayArea once."); } - try { - final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type, - displayId, options); - if (configuration != null) { - mAttachedToDisplayArea = true; - // Send the DisplayArea's configuration to WindowContext directly instead of - // waiting for dispatching from WMS. - mToken.onConfigurationChanged(configuration, displayId); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options); } /** @@ -119,22 +97,14 @@ public class WindowContextController { throw new IllegalStateException("The Window Context should have been attached" + " to a DisplayArea."); } - try { - mWms.attachWindowContextToWindowToken(mToken, windowToken); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mToken.attachToWindowToken(windowToken); } /** Detaches the window context from the node it's currently associated with. */ public void detachIfNeeded() { if (mAttachedToDisplayArea) { - try { - mWms.detachWindowContextFromWindowContainer(mToken); - mAttachedToDisplayArea = false; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mToken.detachFromWindowContainerIfNeeded(); + mAttachedToDisplayArea = false; } } } diff --git a/core/java/android/window/WindowInfosListener.java b/core/java/android/window/WindowInfosListener.java new file mode 100644 index 0000000000000000000000000000000000000000..4376e3eb572ef7f73feb7e2300848c6b897d01e6 --- /dev/null +++ b/core/java/android/window/WindowInfosListener.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.view.InputWindowHandle; + +import libcore.util.NativeAllocationRegistry; + +/** + * Listener for getting {@link InputWindowHandle} updates from SurfaceFlinger. + * @hide + */ +public abstract class WindowInfosListener { + private final long mNativeListener; + + public WindowInfosListener() { + NativeAllocationRegistry registry = NativeAllocationRegistry.createMalloced( + WindowInfosListener.class.getClassLoader(), nativeGetFinalizer()); + + mNativeListener = nativeCreate(this); + registry.registerNativeAllocation(this, mNativeListener); + } + + /** + * Called when WindowInfos in SurfaceFlinger have changed. + * @param windowHandles Reverse Z ordered array of window information that was on screen, + * where the first value is the topmost window. + */ + public abstract void onWindowInfosChanged(InputWindowHandle[] windowHandles); + + /** + * Register the WindowInfosListener. + */ + public void register() { + nativeRegister(mNativeListener); + } + + /** + * Unregisters the WindowInfosListener. + */ + public void unregister() { + nativeUnregister(mNativeListener); + } + + private static native long nativeCreate(WindowInfosListener thiz); + private static native void nativeRegister(long ptr); + private static native void nativeUnregister(long ptr); + private static native long nativeGetFinalizer(); +} diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 544d42240079105c89ef70b046c65a45674432aa..4ea5ea5694fa655eb504c4c8358f89548a1c9abc 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -25,6 +25,7 @@ import android.app.ActivityTaskManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Singleton; +import android.view.RemoteAnimationAdapter; /** * Base class for organizing specific types of windows like Tasks and DisplayAreas @@ -36,9 +37,16 @@ public class WindowOrganizer { /** * Apply multiple WindowContainer operations at once. + * + * Note that using this API requires the caller to hold + * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, unless the caller is using + * {@link TaskFragmentOrganizer}, in which case it is allowed to change TaskFragment that is + * created by itself. + * * @param t The transaction to apply. */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) + @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS, + conditional = true) public void applyTransaction(@NonNull WindowContainerTransaction t) { try { if (!t.isEmpty()) { @@ -51,6 +59,12 @@ public class WindowOrganizer { /** * Apply multiple WindowContainer operations at once. + * + * Note that using this API requires the caller to hold + * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, unless the caller is using + * {@link TaskFragmentOrganizer}, in which case it is allowed to change TaskFragment that is + * created by itself. + * * @param t The transaction to apply. * @param callback This transaction will use the synchronization scheme described in * BLASTSyncEngine.java. The SurfaceControl transaction containing the effects of this @@ -58,7 +72,8 @@ public class WindowOrganizer { * @return An ID for the sync operation which will later be passed to transactionReady callback. * This lets the caller differentiate overlapping sync operations. */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) + @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS, + conditional = true) public int applySyncTransaction(@NonNull WindowContainerTransaction t, @NonNull WindowContainerTransactionCallback callback) { try { @@ -110,6 +125,27 @@ public class WindowOrganizer { } } + /** + * Start a legacy transition. + * @param type The type of the transition. This is ignored if a transitionToken is provided. + * @param adapter An existing transition to start. If null, a new transition is created. + * @param t The set of window operations that are part of this transition. + * @return true on success, false if a transition was already running. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) + @NonNull + public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter, + @NonNull WindowContainerTransactionCallback syncCallback, + @NonNull WindowContainerTransaction t) { + try { + return getWindowOrganizerController().startLegacyTransition( + type, adapter, syncCallback.mInterface, t); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Register an ITransitionPlayer to handle transition animations. * @hide @@ -123,8 +159,19 @@ public class WindowOrganizer { } } - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) - IWindowOrganizerController getWindowOrganizerController() { + /** + * @see TransitionMetrics + * @hide + */ + public static ITransitionMetricsReporter getTransitionMetricsReporter() { + try { + return getWindowOrganizerController().getTransitionMetricsReporter(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + static IWindowOrganizerController getWindowOrganizerController() { return IWindowOrganizerControllerSingleton.get(); } diff --git a/core/java/android/window/WindowProvider.java b/core/java/android/window/WindowProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..b078b9362b9070b6001c72094550ebbf55220e04 --- /dev/null +++ b/core/java/android/window/WindowProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.window; + +import android.annotation.Nullable; +import android.os.Bundle; +import android.view.WindowManager.LayoutParams.WindowType; + +/** + * An interface to provide a non-activity window. + * Examples are {@link WindowContext} and {@link WindowProviderService}. + * + * @hide + */ +public interface WindowProvider { + /** @hide */ + String KEY_IS_WINDOW_PROVIDER_SERVICE = "android.windowContext.isWindowProviderService"; + + /** Gets the window type of this provider */ + @WindowType + int getWindowType(); + + /** Gets the launch options of this provider */ + @Nullable + Bundle getWindowContextOptions(); +} diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java index b8619fbcf3348c3e8b4da7f9da7c7e16b34ae671..f8484d15344e0b838c6d8c1140c34bc36b025e73 100644 --- a/core/java/android/window/WindowProviderService.java +++ b/core/java/android/window/WindowProviderService.java @@ -36,27 +36,45 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams.WindowType; import android.view.WindowManagerImpl; -// TODO(b/159767464): handle #onConfigurationChanged(Configuration) /** * A {@link Service} responsible for showing a non-activity window, such as software keyboards or * accessibility overlay windows. This {@link Service} has similar behavior to * {@link WindowContext}, but is represented as {@link Service}. * * @see android.inputmethodservice.InputMethodService - * @see android.accessibilityservice.AccessibilityService * * @hide */ @TestApi @UiContext -public abstract class WindowProviderService extends Service { +public abstract class WindowProviderService extends Service implements WindowProvider { + private final Bundle mOptions; private final WindowTokenClient mWindowToken = new WindowTokenClient(); private final WindowContextController mController = new WindowContextController(mWindowToken); private WindowManager mWindowManager; + private boolean mInitialized; /** - * Returns the type of this {@link WindowProviderService}. + * Returns {@code true} if the {@code windowContextOptions} declares that it is a + * {@link WindowProviderService}. + * + * @hide + */ + public static boolean isWindowProviderService(@Nullable Bundle windowContextOptions) { + if (windowContextOptions == null) { + return false; + } + return (windowContextOptions.getBoolean(KEY_IS_WINDOW_PROVIDER_SERVICE, false)); + } + + public WindowProviderService() { + mOptions = new Bundle(); + mOptions.putBoolean(KEY_IS_WINDOW_PROVIDER_SERVICE, true); + } + + /** + * Returns the window type of this {@link WindowProviderService}. * Each inheriting class must implement this method to provide the type of the window. It is * used similar to {@code type} of {@link Context#createWindowContext(int, Bundle)} * @@ -68,15 +86,24 @@ public abstract class WindowProviderService extends Service { @SuppressLint("OnNameExpected") // Suppress the lint because it is not a callback and users should provide window type // so we cannot make it final. - public abstract @WindowType int getWindowType(); + @WindowType + @Override + public abstract int getWindowType(); /** * Returns the option of this {@link WindowProviderService}. - * Default is {@code null}. The inheriting class can implement this method to provide the - * customization {@code option} of the window. It is used similar to {@code options} of - * {@link Context#createWindowContext(int, Bundle)} - * - * @see Context#createWindowContext(int, Bundle) + *

    + * The inheriting class can implement this method to provide the customization {@code option} of + * the window, but must be based on this method's returned value. + * It is used similar to {@code options} of {@link Context#createWindowContext(int, Bundle)} + *

    + *
    +     * public Bundle getWindowContextOptions() {
    +     *     final Bundle options = super.getWindowContextOptions();
    +     *     options.put(KEY_ROOT_DISPLAY_AREA_ID, displayAreaInfo.rootDisplayAreaId);
    +     *     return options;
    +     * }
    +     * 
    * * @hide */ @@ -85,8 +112,24 @@ public abstract class WindowProviderService extends Service { // Suppress the lint because it is not a callback and users may override this API to provide // launch option. Also, the return value of this API is null by default. @Nullable + @CallSuper + @Override public Bundle getWindowContextOptions() { - return null; + return mOptions; + } + + /** + * Returns the display ID to launch this {@link WindowProviderService}. + * + * @hide + */ + @TestApi + @SuppressLint({"OnNameExpected"}) + // Suppress the lint because it is not a callback and users may override this API to provide + // display. + @NonNull + public int getInitialDisplayId() { + return DEFAULT_DISPLAY; } /** @@ -104,19 +147,22 @@ public abstract class WindowProviderService extends Service { public final Context createServiceBaseContext(ActivityThread mainThread, LoadedApk packageInfo) { final Context context = super.createServiceBaseContext(mainThread, packageInfo); - // Always associate with the default display at initialization. - final Display defaultDisplay = context.getSystemService(DisplayManager.class) - .getDisplay(DEFAULT_DISPLAY); - return context.createTokenContext(mWindowToken, defaultDisplay); + final Display display = context.getSystemService(DisplayManager.class) + .getDisplay(getInitialDisplayId()); + return context.createTokenContext(mWindowToken, display); } - @CallSuper + /** @hide */ @Override - public void onCreate() { - super.onCreate(); - mWindowToken.attachContext(this); - mController.attachToDisplayArea(getWindowType(), getDisplayId(), getWindowContextOptions()); - mWindowManager = WindowManagerImpl.createWindowContextWindowManager(this); + protected void attachBaseContext(Context newBase) { + super.attachBaseContext(newBase); + if (!mInitialized) { + mWindowToken.attachContext(this); + mController.attachToDisplayArea(getWindowType(), getDisplayId(), + getWindowContextOptions()); + mWindowManager = WindowManagerImpl.createWindowContextWindowManager(this); + mInitialized = true; + } } @SuppressLint("OnNameExpected") diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 4dcd2e74a53fd1a4b5ccfa8dd6763d27b0cf5fd7..547535d90e5aaa70f35d242914d1a608257aad23 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -15,14 +15,30 @@ */ package android.window; +import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; +import static android.window.ConfigurationHelper.isDifferentDisplay; +import static android.window.ConfigurationHelper.shouldUpdateResources; + +import android.annotation.BinderThread; +import android.annotation.MainThread; import android.annotation.NonNull; -import android.app.ActivityThread; +import android.annotation.Nullable; import android.app.IWindowToken; import android.app.ResourcesManager; import android.content.Context; import android.content.res.Configuration; +import android.inputmethodservice.AbstractInputMethodService; +import android.os.Build; import android.os.Bundle; +import android.os.Debug; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import android.view.IWindowManager; +import android.view.WindowManager.LayoutParams.WindowType; +import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; @@ -40,6 +56,8 @@ import java.lang.ref.WeakReference; * @hide */ public class WindowTokenClient extends IWindowToken.Stub { + private static final String TAG = WindowTokenClient.class.getSimpleName(); + /** * Attached {@link Context} for this window token to update configuration and resources. * Initialized by {@link #attachContext(Context)}. @@ -48,6 +66,16 @@ public class WindowTokenClient extends IWindowToken.Stub { private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); + private IWindowManager mWms; + + private final Configuration mConfiguration = new Configuration(); + + private boolean mShouldDumpConfigForIme; + + private boolean mAttachToWindowContainer; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + /** * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient} * can only attach one {@link Context}. @@ -63,6 +91,92 @@ public class WindowTokenClient extends IWindowToken.Stub { throw new IllegalStateException("Context is already attached."); } mContextRef = new WeakReference<>(context); + mConfiguration.setTo(context.getResources().getConfiguration()); + mShouldDumpConfigForIme = Build.IS_DEBUGGABLE + && context instanceof AbstractInputMethodService; + } + + /** + * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. + * + * @param type The window type of the {@link WindowContext} + * @param displayId The {@link Context#getDisplayId() ID of display} to associate with + * @param options The window context launched option + * @return {@code true} if attaching successfully. + */ + public boolean attachToDisplayArea(@WindowType int type, int displayId, + @Nullable Bundle options) { + try { + final Configuration configuration = getWindowManagerService() + .attachWindowContextToDisplayArea(this, type, displayId, options); + if (configuration == null) { + return false; + } + onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); + mAttachToWindowContainer = true; + return true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}. + * + * @param displayId The {@link Context#getDisplayId() ID of display} to associate with + * @return {@code true} if attaching successfully. + */ + public boolean attachToDisplayContent(int displayId) { + final IWindowManager wms = getWindowManagerService(); + // #createSystemUiContext may call this method before WindowManagerService is initialized. + if (wms == null) { + return false; + } + try { + final Configuration configuration = wms.attachToDisplayContent(this, displayId); + if (configuration == null) { + return false; + } + mHandler.post(() -> onConfigurationChanged(configuration, displayId, + false /* shouldReportConfigChange */)); + mAttachToWindowContainer = true; + return true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Attaches this {@link WindowTokenClient} to a {@code windowToken}. + * + * @param windowToken the window token to associated with + */ + public void attachToWindowToken(IBinder windowToken) { + try { + getWindowManagerService().attachWindowContextToWindowToken(this, windowToken); + mAttachToWindowContainer = true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */ + public void detachFromWindowContainerIfNeeded() { + if (!mAttachToWindowContainer) { + return; + } + try { + getWindowManagerService().detachWindowContextFromWindowContainer(this); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private IWindowManager getWindowManagerService() { + if (mWms == null) { + mWms = WindowManagerGlobal.getWindowManagerService(); + } + return mWms; } /** @@ -71,36 +185,85 @@ public class WindowTokenClient extends IWindowToken.Stub { * @param newConfig the updated {@link Configuration} * @param newDisplayId the updated {@link android.view.Display} ID */ - @VisibleForTesting + @BinderThread @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { + mHandler.post(() -> onConfigurationChanged(newConfig, newDisplayId, + true /* shouldReportConfigChange */)); + } + + // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService + // are inherited from WindowProvider. + /** + * Called when {@link Configuration} updates from the server side receive. + * + * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control + * whether to dispatch configuration update or not. + */ + @MainThread + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void onConfigurationChanged(Configuration newConfig, int newDisplayId, + boolean shouldReportConfigChange) { final Context context = mContextRef.get(); if (context == null) { return; } - final int currentDisplayId = context.getDisplayId(); - final boolean displayChanged = newDisplayId != currentDisplayId; - final Configuration config = context.getResources().getConfiguration(); - final boolean configChanged = config.diff(newConfig) != 0; - if (displayChanged || configChanged) { + final boolean displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId); + final boolean shouldUpdateResources = shouldUpdateResources(this, mConfiguration, + newConfig, newConfig /* overrideConfig */, displayChanged, + null /* configChanged */); + + if (!shouldUpdateResources && mShouldDumpConfigForIme) { + Log.d(TAG, "Configuration not dispatch to IME because configuration is up" + + " to date. Current config=" + context.getResources().getConfiguration() + + ", reported config=" + mConfiguration + + ", updated config=" + newConfig); + } + + if (shouldUpdateResources) { // TODO(ag/9789103): update resource manager logic to track non-activity tokens mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); - if (context instanceof WindowContext) { - ActivityThread.currentActivityThread().getHandler().post( - () -> ((WindowContext) context).dispatchConfigurationChanged(newConfig)); + + if (shouldReportConfigChange && context instanceof WindowContext) { + final WindowContext windowContext = (WindowContext) context; + windowContext.dispatchConfigurationChanged(newConfig); } + + final int diff = mConfiguration.diffPublicOnly(newConfig); + if (shouldReportConfigChange && diff != 0 + && context instanceof WindowProviderService) { + final WindowProviderService windowProviderService = (WindowProviderService) context; + windowProviderService.onConfigurationChanged(newConfig); + } + freeTextLayoutCachesIfNeeded(diff); + if (mShouldDumpConfigForIme) { + if (!shouldReportConfigChange) { + Log.d(TAG, "Only apply configuration update to Resources because " + + "shouldReportConfigChange is false.\n" + Debug.getCallers(5)); + } else if (diff == 0) { + Log.d(TAG, "Configuration not dispatch to IME because configuration has no " + + " public difference with updated config. " + + " Current config=" + context.getResources().getConfiguration() + + ", reported config=" + mConfiguration + + ", updated config=" + newConfig); + } + } + mConfiguration.setTo(newConfig); } if (displayChanged) { context.updateDisplay(newDisplayId); } } + @BinderThread @Override public void onWindowTokenRemoved() { - final Context context = mContextRef.get(); - if (context != null) { - context.destroy(); - mContextRef.clear(); - } + mHandler.post(() -> { + final Context context = mContextRef.get(); + if (context != null) { + context.destroy(); + mContextRef.clear(); + } + }); } } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 0b92b93f2c882d7344cf12fd33389aae5c8435ac..874e3f4ae26a36690cf04242975a18d4750c2736 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -47,6 +47,8 @@ import java.util.List; public class AccessibilityShortcutChooserActivity extends Activity { @ShortcutType private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY; + private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE = + "accessibility_shortcut_menu_mode"; private final List mTargets = new ArrayList<>(); private AlertDialog mMenuDialog; private AlertDialog mPermissionDialog; @@ -66,14 +68,30 @@ public class AccessibilityShortcutChooserActivity extends Activity { mMenuDialog = createMenuDialog(); mMenuDialog.setOnShowListener(dialog -> updateDialogListeners()); mMenuDialog.show(); + + if (savedInstanceState != null) { + final int restoreShortcutMenuMode = + savedInstanceState.getInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, + ShortcutMenuMode.LAUNCH); + if (restoreShortcutMenuMode == ShortcutMenuMode.EDIT) { + onEditButtonClicked(); + } + } } @Override protected void onDestroy() { + mMenuDialog.setOnDismissListener(null); mMenuDialog.dismiss(); super.onDestroy(); } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, mTargetAdapter.getShortcutMenuMode()); + } + private void onTargetSelected(AdapterView parent, View view, int position, long id) { final AccessibilityTarget target = mTargets.get(position); target.onSelected(); diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java index c57afbc674941441b7ca8b02818d2ed067ae339b..179ac8b1c528b89f82ad459eda754c98d1e2634c 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java @@ -17,6 +17,7 @@ package com.android.internal.accessibility.util; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; @@ -30,6 +31,7 @@ import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON_LONG_PRESS; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY; @@ -152,19 +154,29 @@ public final class AccessibilityStatsLogUtils { convertToLoggingMagnificationMode(mode)); } - private static boolean isFloatingMenuEnabled(Context context) { + private static boolean isAccessibilityFloatingMenuEnabled(Context context) { return Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1) == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; } + private static boolean isAccessibilityGestureEnabled(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1) + == ACCESSIBILITY_BUTTON_MODE_GESTURE; + } + private static int convertToLoggingShortcutType(Context context, @ShortcutType int shortcutType) { switch (shortcutType) { case ACCESSIBILITY_BUTTON: - return isFloatingMenuEnabled(context) - ? ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU - : ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; + if (isAccessibilityFloatingMenuEnabled(context)) { + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; + } else if (isAccessibilityGestureEnabled(context)) { + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; + } else { + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; + } case ACCESSIBILITY_SHORTCUT_KEY: return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY; } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 786af5f0823e5ed420cbce033d637b9e143c7d51..7bb1ed8abc0a52ac65f939550a3346dae83c566f 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -163,9 +163,6 @@ public class ChooserActivity extends ResolverActivity implements private AppPredictor mWorkAppPredictor; private boolean mShouldDisplayLandscape; - private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4; - private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8; - @UnsupportedAppUsage public ChooserActivity() { } @@ -275,6 +272,7 @@ public class ChooserActivity extends ResolverActivity implements private int mCurrAvailableWidth = 0; private int mLastNumberOfChildren = -1; + private int mMaxTargetsPerRow = 1; private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment"; @@ -741,8 +739,9 @@ public class ChooserActivity extends ResolverActivity implements mCallerChooserTargets = targets; } - mShouldDisplayLandscape = shouldDisplayLandscape( - getResources().getConfiguration().orientation); + mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row); + mShouldDisplayLandscape = + shouldDisplayLandscape(getResources().getConfiguration().orientation); setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false)); super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); @@ -916,7 +915,7 @@ public class ChooserActivity extends ResolverActivity implements adapter, getPersonalProfileUserHandle(), /* workProfileUserHandle= */ null, - isSendAction(getTargetIntent()), getMaxTargetsPerRow()); + isSendAction(getTargetIntent()), mMaxTargetsPerRow); } private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles( @@ -945,7 +944,7 @@ public class ChooserActivity extends ResolverActivity implements selectedProfile, getPersonalProfileUserHandle(), getWorkProfileUserHandle(), - isSendAction(getTargetIntent()), getMaxTargetsPerRow()); + isSendAction(getTargetIntent()), mMaxTargetsPerRow); } private int findSelectedProfile() { @@ -1107,6 +1106,7 @@ public class ChooserActivity extends ResolverActivity implements } mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation); + mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row); adjustPreviewWidth(newConfig.orientation, null); updateStickyContentPreview(); } @@ -2690,7 +2690,7 @@ public class ChooserActivity extends ResolverActivity implements // and b/150936654 recyclerView.setAdapter(gridAdapter); ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount( - getMaxTargetsPerRow()); + mMaxTargetsPerRow); } UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle(); @@ -2855,7 +2855,7 @@ public class ChooserActivity extends ResolverActivity implements @Override // ChooserListCommunicator public int getMaxRankedTargets() { - return getMaxTargetsPerRow(); + return mMaxTargetsPerRow; } @Override // ChooserListCommunicator @@ -3203,13 +3203,6 @@ public class ChooserActivity extends ResolverActivity implements } } - int getMaxTargetsPerRow() { - int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT; - if (mShouldDisplayLandscape) { - maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE; - } - return maxTargets; - } /** * Adapter for all types of items and targets in ShareSheet. * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the @@ -3277,7 +3270,11 @@ public class ChooserActivity extends ResolverActivity implements return false; } - int newWidth = width / getMaxTargetsPerRow(); + // Limit width to the maximum width of the chooser activity + int maxWidth = getResources().getDimensionPixelSize(R.dimen.chooser_width); + width = Math.min(maxWidth, width); + + int newWidth = width / mMaxTargetsPerRow; if (newWidth != mChooserTargetWidth) { mChooserTargetWidth = newWidth; return true; @@ -3312,7 +3309,7 @@ public class ChooserActivity extends ResolverActivity implements + getAzLabelRowCount() + Math.ceil( (float) mChooserListAdapter.getAlphaTargetCount() - / getMaxTargetsPerRow()) + / mMaxTargetsPerRow) ); } @@ -3352,7 +3349,7 @@ public class ChooserActivity extends ResolverActivity implements public int getCallerAndRankedTargetRowCount() { return (int) Math.ceil( ((float) mChooserListAdapter.getCallerTargetCount() - + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow()); + + mChooserListAdapter.getRankedTargetCount()) / mMaxTargetsPerRow); } // There can be at most one row in the listview, that is internally @@ -3551,7 +3548,7 @@ public class ChooserActivity extends ResolverActivity implements parentGroup.addView(row2); mDirectShareViewHolder = new DirectShareViewHolder(parentGroup, - Lists.newArrayList(row1, row2), getMaxTargetsPerRow(), viewType); + Lists.newArrayList(row1, row2), mMaxTargetsPerRow, viewType); loadViewsIntoGroup(mDirectShareViewHolder); return mDirectShareViewHolder; @@ -3559,7 +3556,7 @@ public class ChooserActivity extends ResolverActivity implements ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent, false); ItemGroupViewHolder holder = - new SingleRowViewHolder(row, getMaxTargetsPerRow(), viewType); + new SingleRowViewHolder(row, mMaxTargetsPerRow, viewType); loadViewsIntoGroup(holder); return holder; @@ -3651,7 +3648,7 @@ public class ChooserActivity extends ResolverActivity implements final int serviceCount = mChooserListAdapter.getServiceTargetCount(); final int serviceRows = (int) Math.ceil((float) serviceCount / getMaxRankedTargets()); if (position < serviceRows) { - return position * getMaxTargetsPerRow(); + return position * mMaxTargetsPerRow; } position -= serviceRows; @@ -3660,7 +3657,7 @@ public class ChooserActivity extends ResolverActivity implements + mChooserListAdapter.getRankedTargetCount(); final int callerAndRankedRows = getCallerAndRankedTargetRowCount(); if (position < callerAndRankedRows) { - return serviceCount + position * getMaxTargetsPerRow(); + return serviceCount + position * mMaxTargetsPerRow; } position -= getAzLabelRowCount() + callerAndRankedRows; @@ -3673,7 +3670,7 @@ public class ChooserActivity extends ResolverActivity implements if (mDirectShareViewHolder != null && canExpandDirectShare) { mDirectShareViewHolder.handleScroll( mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy, - getMaxTargetsPerRow()); + mMaxTargetsPerRow); } } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index c8a4425409e82864a01af35d132c006c19344b32..998526209c72da00d2b61068d762f26b120cb02f 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -272,4 +272,14 @@ interface IVoiceInteractionManagerService { void triggerHardwareRecognitionEventForTest( in SoundTrigger.KeyphraseRecognitionEvent event, in IHotwordRecognitionStatusCallback callback); + + /** + * Starts to listen the status of visible activity. + */ + void startListeningVisibleActivityChanged(in IBinder token); + + /** + * Stops to listen the status of visible activity. + */ + void stopListeningVisibleActivityChanged(in IBinder token); } diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index d0719eeca04e4f2f587dde58bab292cb4c72adb1..b4ae56f234433542f40e89025baefff04736ea9e 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -158,6 +158,14 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O setListAdapter(mAdapter); } + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // In order to make the list view work with CollapsingToolbarLayout, + // we have to enable the nested scrolling feature of the list view. + getListView().setNestedScrollingEnabled(true); + } + @Override public boolean onOptionsItemSelected(MenuItem menuItem) { int id = menuItem.getItemId(); diff --git a/core/java/com/android/internal/infra/OWNERS b/core/java/com/android/internal/infra/OWNERS index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..45503582b2c5b5add9a022310e4923271f4cfd44 100644 --- a/core/java/com/android/internal/infra/OWNERS +++ b/core/java/com/android/internal/infra/OWNERS @@ -0,0 +1,6 @@ +per-file AndroidFuture.java = eugenesusla@google.com +per-file RemoteStream.java = eugenesusla@google.com +per-file PerUser.java = eugenesusla@google.com +per-file ServiceConnector.java = eugenesusla@google.com +per-file AndroidFuture.aidl = eugenesusla@google.com +per-file IAndroidFuture.aidl = eugenesusla@google.com \ No newline at end of file diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index a00b993749a52e90a5804388416a2d07271ebff4..bf094dbd8f1e7ce74159c6cd1c3d1ab27fa02424 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -236,6 +236,8 @@ public final class InputMethodDebug { return "HIDE_TOGGLE_SOFT_INPUT"; case SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API: return "SHOW_SOFT_INPUT_BY_INSETS_API"; + case SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE: + return "HIDE_DISPLAY_IME_POLICY_HIDE"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index e3713a3b897195ab81efd46e38bc94339c17b4b8..9e5776292031cf1d28591cc27ab0cea5416dcc6d 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -19,6 +19,7 @@ package com.android.internal.inputmethod; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; +import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import java.lang.annotation.Retention; @@ -53,7 +54,8 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY, SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT, SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT, - SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API}) + SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API, + SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE}) public @interface SoftInputShowHideReason { /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */ int SHOW_SOFT_INPUT = 0; @@ -195,4 +197,10 @@ public @interface SoftInputShowHideReason { * {@link android.view.InsetsController#show(int)}; */ int SHOW_SOFT_INPUT_BY_INSETS_API = 25; + + /** + * Hide soft input if Ime policy has been set to {@link WindowManager#DISPLAY_IME_POLICY_HIDE}. + * See also {@code InputMethodManagerService#mImeHiddenByDisplayPolicy}. + */ + int HIDE_DISPLAY_IME_POLICY_HIDE = 26; } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index d12c870d259181fc6974da13807f599927e588d1..d14054d4f9f739bb834c04119a400c9b76009ae5 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -45,6 +45,7 @@ import android.view.ThreadedRenderer; import android.view.ViewRootImpl; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.jank.InteractionJankMonitor.Session; import com.android.internal.util.FrameworkStatsLog; @@ -70,6 +71,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener static final int REASON_CANCEL_NORMAL = 16; static final int REASON_CANCEL_NOT_BEGUN = 17; static final int REASON_CANCEL_SAME_VSYNC = 18; + static final int REASON_CANCEL_TIMEOUT = 19; /** @hide */ @IntDef({ @@ -97,6 +99,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback; private final Handler mHandler; private final ChoreographerWrapper mChoreographer; + private final Object mLock = InteractionJankMonitor.getInstance().getLock(); + + @VisibleForTesting + public final boolean mSurfaceOnly; private long mBeginVsyncId = INVALID_ID; private long mEndVsyncId = INVALID_ID; @@ -138,90 +144,104 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } public FrameTracker(@NonNull Session session, @NonNull Handler handler, - @NonNull ThreadedRendererWrapper renderer, @NonNull ViewRootWrapper viewRootWrapper, + @Nullable ThreadedRendererWrapper renderer, @Nullable ViewRootWrapper viewRootWrapper, @NonNull SurfaceControlWrapper surfaceControlWrapper, @NonNull ChoreographerWrapper choreographer, - @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames, - int traceThresholdFrameTimeMillis, @Nullable FrameTrackerListener listener) { + @Nullable FrameMetricsWrapper metrics, + int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis, + @Nullable FrameTrackerListener listener, @NonNull Configuration config) { + mSurfaceOnly = config.isSurfaceOnly(); mSession = session; - mRendererWrapper = renderer; - mMetricsWrapper = metrics; - mViewRoot = viewRootWrapper; + mHandler = handler; mChoreographer = choreographer; mSurfaceControlWrapper = surfaceControlWrapper; - mHandler = handler; - mObserver = new HardwareRendererObserver( - this, mMetricsWrapper.getTiming(), handler, false /*waitForPresentTime*/); + + // HWUI instrumentation init. + mRendererWrapper = mSurfaceOnly ? null : renderer; + mMetricsWrapper = mSurfaceOnly ? null : metrics; + mViewRoot = mSurfaceOnly ? null : viewRootWrapper; + mObserver = mSurfaceOnly + ? null + : new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), + handler, /* waitForPresentTime= */ false); + mTraceThresholdMissedFrames = traceThresholdMissedFrames; mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis; mListener = listener; - // If the surface isn't valid yet, wait until it's created. - if (viewRootWrapper.getSurfaceControl().isValid()) { - mSurfaceControl = viewRootWrapper.getSurfaceControl(); - } - mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() { - @Override - public void surfaceCreated(SurfaceControl.Transaction t) { - synchronized (FrameTracker.this) { - if (mSurfaceControl == null) { - mSurfaceControl = viewRootWrapper.getSurfaceControl(); - if (mBeginVsyncId != INVALID_ID) { - mSurfaceControlWrapper.addJankStatsListener( - FrameTracker.this, mSurfaceControl); - postTraceStartMarker(); + if (mSurfaceOnly) { + mSurfaceControl = config.getSurfaceControl(); + mSurfaceChangedCallback = null; + } else { + // HWUI instrumentation init. + // If the surface isn't valid yet, wait until it's created. + if (mViewRoot.getSurfaceControl().isValid()) { + mSurfaceControl = mViewRoot.getSurfaceControl(); + } + + mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() { + @Override + public void surfaceCreated(SurfaceControl.Transaction t) { + synchronized (mLock) { + if (mSurfaceControl == null) { + mSurfaceControl = mViewRoot.getSurfaceControl(); + if (mBeginVsyncId != INVALID_ID) { + mSurfaceControlWrapper.addJankStatsListener( + FrameTracker.this, mSurfaceControl); + postTraceStartMarker(); + } } } } - } - @Override - public void surfaceReplaced(SurfaceControl.Transaction t) { - } + @Override + public void surfaceReplaced(SurfaceControl.Transaction t) { + } - @Override - public void surfaceDestroyed() { - - // Wait a while to give the system a chance for the remaining frames to arrive, then - // force finish the session. - mHandler.postDelayed(() -> { - synchronized (FrameTracker.this) { - if (DEBUG) { - Log.d(TAG, "surfaceDestroyed: " + mSession.getName() - + ", finalized=" + mMetricsFinalized - + ", info=" + mJankInfos.size() - + ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId); - } - if (!mMetricsFinalized) { - end(REASON_END_SURFACE_DESTROYED); - finish(mJankInfos.size() - 1); + @Override + public void surfaceDestroyed() { + + // Wait a while to give the system a chance for the remaining + // frames to arrive, then force finish the session. + mHandler.postDelayed(() -> { + synchronized (mLock) { + if (DEBUG) { + Log.d(TAG, "surfaceDestroyed: " + mSession.getName() + + ", finalized=" + mMetricsFinalized + + ", info=" + mJankInfos.size() + + ", vsync=" + mBeginVsyncId); + } + if (!mMetricsFinalized) { + end(REASON_END_SURFACE_DESTROYED); + finish(mJankInfos.size() - 1); + } } - } - }, 50); - } - }; - - // This callback has a reference to FrameTracker, remember to remove it to avoid leakage. - viewRootWrapper.addSurfaceChangedCallback(mSurfaceChangedCallback); + }, 50); + } + }; + // This callback has a reference to FrameTracker, + // remember to remove it to avoid leakage. + mViewRoot.addSurfaceChangedCallback(mSurfaceChangedCallback); + } } /** * Begin a trace session of the CUJ. */ - public synchronized void begin() { - mBeginVsyncId = mChoreographer.getVsyncId() + 1; - if (mSurfaceControl != null) { - postTraceStartMarker(); - } - mRendererWrapper.addObserver(mObserver); - if (DEBUG) { - Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId); - } - if (mSurfaceControl != null) { - mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); - } - if (mListener != null) { - mListener.onCujEvents(mSession, ACTION_SESSION_BEGIN); + public void begin() { + synchronized (mLock) { + mBeginVsyncId = mChoreographer.getVsyncId() + 1; + if (DEBUG) { + Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId); + } + if (mSurfaceControl != null) { + postTraceStartMarker(); + mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); + } + if (!mSurfaceOnly) { + mRendererWrapper.addObserver(mObserver); + } + notifyCujEvent(ACTION_SESSION_BEGIN); } } @@ -231,7 +251,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener @VisibleForTesting public void postTraceStartMarker() { mChoreographer.mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, () -> { - synchronized (FrameTracker.this) { + synchronized (mLock) { if (mCancelled || mEndVsyncId != INVALID_ID) { return; } @@ -244,86 +264,98 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener /** * End the trace session of the CUJ. */ - public synchronized void end(@Reasons int reason) { - if (mEndVsyncId != INVALID_ID) return; - mEndVsyncId = mChoreographer.getVsyncId(); - - // Cancel the session if: - // 1. The session begins and ends at the same vsync id. - // 2. The session never begun. - if (mBeginVsyncId == INVALID_ID) { - cancel(REASON_CANCEL_NOT_BEGUN); - } else if (mEndVsyncId <= mBeginVsyncId) { - cancel(REASON_CANCEL_SAME_VSYNC); - } else { - if (DEBUG) { - Log.d(TAG, "end: " + mSession.getName() - + ", end=" + mEndVsyncId + ", reason=" + reason); - } - Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); - mSession.setReason(reason); - if (mListener != null) { - mListener.onCujEvents(mSession, ACTION_SESSION_END); + public boolean end(@Reasons int reason) { + synchronized (mLock) { + if (mCancelled || mEndVsyncId != INVALID_ID) return false; + mEndVsyncId = mChoreographer.getVsyncId(); + // Cancel the session if: + // 1. The session begins and ends at the same vsync id. + // 2. The session never begun. + if (mBeginVsyncId == INVALID_ID) { + return cancel(REASON_CANCEL_NOT_BEGUN); + } else if (mEndVsyncId <= mBeginVsyncId) { + return cancel(REASON_CANCEL_SAME_VSYNC); + } else { + if (DEBUG) { + Log.d(TAG, "end: " + mSession.getName() + + ", end=" + mEndVsyncId + ", reason=" + reason); + } + Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); + mSession.setReason(reason); + + // We don't remove observer here, + // will remove it when all the frame metrics in this duration are called back. + // See onFrameMetricsAvailable for the logic of removing the observer. + // Waiting at most 10 seconds for all callbacks to finish. + mWaitForFinishTimedOut = () -> { + Log.e(TAG, "force finish cuj because of time out:" + mSession.getName()); + finish(mJankInfos.size() - 1); + }; + mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10)); + notifyCujEvent(ACTION_SESSION_END); + return true; } - // We don't remove observer here, - // will remove it when all the frame metrics in this duration are called back. - // See onFrameMetricsAvailable for the logic of removing the observer. - // Waiting at most 10 seconds for all callbacks to finish. - mWaitForFinishTimedOut = () -> { - Log.e(TAG, "force finish cuj because of time out:" + mSession.getName()); - finish(mJankInfos.size() - 1); - }; - mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10)); } } /** * Cancel the trace session of the CUJ. */ - public synchronized void cancel(@Reasons int reason) { - // We don't need to end the trace section if it never begun. - if (mTracingStarted) { - Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); - } - mCancelled = true; + public boolean cancel(@Reasons int reason) { + synchronized (mLock) { + final boolean cancelFromEnd = + reason == REASON_CANCEL_NOT_BEGUN || reason == REASON_CANCEL_SAME_VSYNC; + if (mCancelled || (mEndVsyncId != INVALID_ID && !cancelFromEnd)) return false; + mCancelled = true; + // We don't need to end the trace section if it never begun. + if (mTracingStarted) { + Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); + } - // Always remove the observers in cancel call to avoid leakage. - removeObservers(); + // Always remove the observers in cancel call to avoid leakage. + removeObservers(); - if (DEBUG) { - Log.d(TAG, "cancel: " + mSession.getName() - + ", begin=" + mBeginVsyncId + ", end=" + mEndVsyncId + ", reason=" + reason); - } + if (DEBUG) { + Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId + + ", end=" + mEndVsyncId + ", reason=" + reason); + } - mSession.setReason(reason); - // Notify the listener the session has been cancelled. - // We don't notify the listeners if the session never begun. - if (mListener != null) { - mListener.onCujEvents(mSession, ACTION_SESSION_CANCEL); + mSession.setReason(reason); + // Notify the listener the session has been cancelled. + // We don't notify the listeners if the session never begun. + notifyCujEvent(ACTION_SESSION_CANCEL); + return true; } } - @Override - public synchronized void onJankDataAvailable(SurfaceControl.JankData[] jankData) { - if (mCancelled) { - return; - } + private void notifyCujEvent(String action) { + if (mListener == null) return; + mListener.onCujEvents(mSession, action); + } - for (SurfaceControl.JankData jankStat : jankData) { - if (!isInRange(jankStat.frameVsyncId)) { - continue; + @Override + public void onJankDataAvailable(SurfaceControl.JankData[] jankData) { + synchronized (mLock) { + if (mCancelled) { + return; } - JankInfo info = findJankInfo(jankStat.frameVsyncId); - if (info != null) { - info.surfaceControlCallbackFired = true; - info.jankType = jankStat.jankType; - } else { - mJankInfos.put((int) jankStat.frameVsyncId, - JankInfo.createFromSurfaceControlCallback( - jankStat.frameVsyncId, jankStat.jankType)); + + for (SurfaceControl.JankData jankStat : jankData) { + if (!isInRange(jankStat.frameVsyncId)) { + continue; + } + JankInfo info = findJankInfo(jankStat.frameVsyncId); + if (info != null) { + info.surfaceControlCallbackFired = true; + info.jankType = jankStat.jankType; + } else { + mJankInfos.put((int) jankStat.frameVsyncId, + JankInfo.createFromSurfaceControlCallback( + jankStat.frameVsyncId, jankStat.jankType)); + } } + processJankInfos(); } - processJankInfos(); } private @Nullable JankInfo findJankInfo(long frameVsyncId) { @@ -338,31 +370,34 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } @Override - public synchronized void onFrameMetricsAvailable(int dropCountSinceLastInvocation) { - if (mCancelled) { - return; - } + public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) { + synchronized (mLock) { + if (mCancelled) { + return; + } - // Since this callback might come a little bit late after the end() call. - // We should keep tracking the begin / end timestamp. - // Then compare with vsync timestamp to check if the frame is in the duration of the CUJ. - long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION); - boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1; - long frameVsyncId = mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID]; + // Since this callback might come a little bit late after the end() call. + // We should keep tracking the begin / end timestamp that we can compare with + // vsync timestamp to check if the frame is in the duration of the CUJ. + long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION); + boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1; + long frameVsyncId = + mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID]; - if (!isInRange(frameVsyncId)) { - return; - } - JankInfo info = findJankInfo(frameVsyncId); - if (info != null) { - info.hwuiCallbackFired = true; - info.totalDurationNanos = totalDurationNanos; - info.isFirstFrame = isFirstFrame; - } else { - mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback( - frameVsyncId, totalDurationNanos, isFirstFrame)); + if (!isInRange(frameVsyncId)) { + return; + } + JankInfo info = findJankInfo(frameVsyncId); + if (info != null) { + info.hwuiCallbackFired = true; + info.totalDurationNanos = totalDurationNanos; + info.isFirstFrame = isFirstFrame; + } else { + mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback( + frameVsyncId, totalDurationNanos, isFirstFrame)); + } + processJankInfos(); } - processJankInfos(); } /** @@ -385,7 +420,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener for (int i = mJankInfos.size() - 1; i >= 0; i--) { JankInfo info = mJankInfos.valueAt(i); if (info.frameVsyncId >= mEndVsyncId) { - if (info.hwuiCallbackFired && info.surfaceControlCallbackFired) { + if (isLastIndexCandidate(info)) { lastIndex = i; } } else { @@ -403,6 +438,12 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener finish(indexOnOrAfterEnd); } + private boolean isLastIndexCandidate(JankInfo info) { + return mSurfaceOnly + ? info.surfaceControlCallbackFired + : info.hwuiCallbackFired && info.surfaceControlCallbackFired; + } + private void finish(int indexOnOrAfterEnd) { mHandler.removeCallbacks(mWaitForFinishTimedOut); mWaitForFinishTimedOut = null; @@ -419,7 +460,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener for (int i = 0; i <= indexOnOrAfterEnd; i++) { JankInfo info = mJankInfos.valueAt(i); - if (info.isFirstFrame) { + final boolean isFirstDrawn = !mSurfaceOnly && info.isFirstFrame; + if (isFirstDrawn) { continue; } if (info.surfaceControlCallbackFired) { @@ -444,11 +486,11 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } // TODO (b/174755489): Early latch currently gets fired way too often, so we have // to ignore it for now. - if (!info.hwuiCallbackFired) { + if (!mSurfaceOnly && !info.hwuiCallbackFired) { Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId); } } - if (info.hwuiCallbackFired) { + if (!mSurfaceOnly && info.hwuiCallbackFired) { maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos); if (!info.surfaceControlCallbackFired) { Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId); @@ -469,11 +511,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener (int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND)); // Trigger perfetto if necessary. - boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1 - && missedFramesCount >= mTraceThresholdMissedFrames; - boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1 - && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND; - if (overMissedFramesThreshold || overFrameTimeThreshold) { + if (shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) { triggerPerfetto(); } if (mSession.logToStatsd()) { @@ -482,12 +520,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mSession.getStatsdInteractionType(), totalFramesCount, missedFramesCount, - maxFrameTimeNanos, + maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */ missedSfFramesCount, missedAppFramesCount); - if (mListener != null) { - mListener.onCujEvents(mSession, ACTION_METRICS_LOGGED); - } + notifyCujEvent(ACTION_METRICS_LOGGED); } if (DEBUG) { Log.i(TAG, "finish: CUJ=" + mSession.getName() @@ -500,15 +536,26 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } } + private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) { + boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1 + && missedFramesCount >= mTraceThresholdMissedFrames; + boolean overFrameTimeThreshold = !mSurfaceOnly && mTraceThresholdFrameTimeMillis != -1 + && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND; + return overMissedFramesThreshold || overFrameTimeThreshold; + } + /** * Remove all the registered listeners, observers and callbacks. */ @VisibleForTesting public void removeObservers() { - mRendererWrapper.removeObserver(mObserver); mSurfaceControlWrapper.removeJankStatsListener(this); - if (mSurfaceChangedCallback != null) { - mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback); + if (!mSurfaceOnly) { + // HWUI part. + mRendererWrapper.removeObserver(mObserver); + if (mSurfaceChangedCallback != null) { + mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback); + } } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 610cd7339001fc5631e80dc763950b696eb4547a..ea38db304e6d71f299d43e8f9c1adf4458b35ea5 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -18,11 +18,11 @@ package com.android.internal.jank; import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; -import static com.android.internal.jank.FrameTracker.ChoreographerWrapper; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NOT_BEGUN; +import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; -import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; +import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; @@ -41,6 +41,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; @@ -57,7 +58,11 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; import android.annotation.IntDef; import android.annotation.NonNull; @@ -72,11 +77,15 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.Choreographer; +import android.view.SurfaceControl; import android.view.View; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.FrameTracker.ChoreographerWrapper; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; import com.android.internal.jank.FrameTracker.FrameTrackerListener; +import com.android.internal.jank.FrameTracker.Reasons; +import com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.FrameTracker.ViewRootWrapper; import com.android.internal.util.PerfettoTrigger; @@ -163,6 +172,11 @@ public class InteractionJankMonitor { public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32; public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33; public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34; + public static final int CUJ_PIP_TRANSITION = 35; + public static final int CUJ_WALLPAPER_TRANSITION = 36; + public static final int CUJ_USER_SWITCH = 37; + public static final int CUJ_SPLASHSCREEN_AVD = 38; + public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39; private static final int NO_STATSD_LOGGING = -1; @@ -206,6 +220,11 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM, }; private static volatile InteractionJankMonitor sInstance; @@ -213,10 +232,11 @@ public class InteractionJankMonitor { private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener = this::updateProperties; - private FrameMetricsWrapper mMetrics; - private SparseArray mRunningTrackers; - private SparseArray mTimeoutActions; - private HandlerThread mWorker; + private final FrameMetricsWrapper mMetrics; + private final SparseArray mRunningTrackers; + private final SparseArray mTimeoutActions; + private final HandlerThread mWorker; + private final Object mLock = new Object(); private boolean mEnabled = DEFAULT_ENABLED; private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; @@ -260,6 +280,11 @@ public class InteractionJankMonitor { CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + CUJ_PIP_TRANSITION, + CUJ_WALLPAPER_TRANSITION, + CUJ_USER_SWITCH, + CUJ_SPLASHSCREEN_AVD, + CUJ_SPLASHSCREEN_EXIT_ANIM, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -309,25 +334,36 @@ public class InteractionJankMonitor { mPropertiesChangedListener); } + Object getLock() { + return mLock; + } + /** - * Create a {@link FrameTracker} instance. + * Creates a {@link FrameTracker} instance. * + * @param config the config used in instrumenting * @param session the session associates with this tracker * @return instance of the FrameTracker */ @VisibleForTesting - public FrameTracker createFrameTracker(Configuration conf, Session session) { - final View v = conf.mView; - final Context c = v.getContext().getApplicationContext(); - final ThreadedRendererWrapper r = new ThreadedRendererWrapper(v.getThreadedRenderer()); - final ViewRootWrapper vr = new ViewRootWrapper(v.getViewRootImpl()); - final SurfaceControlWrapper sc = new SurfaceControlWrapper(); - final ChoreographerWrapper cg = new ChoreographerWrapper(Choreographer.getInstance()); - - synchronized (this) { - FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(c, act, s); - return new FrameTracker(session, mWorker.getThreadHandler(), r, vr, sc, cg, mMetrics, - mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, eventsListener); + public FrameTracker createFrameTracker(Configuration config, Session session) { + final View view = config.mView; + final ThreadedRendererWrapper threadedRenderer = + view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer()); + final ViewRootWrapper viewRoot = + view == null ? null : new ViewRootWrapper(view.getViewRootImpl()); + + final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper(); + final ChoreographerWrapper choreographer = + new ChoreographerWrapper(Choreographer.getInstance()); + + synchronized (mLock) { + FrameTrackerListener eventsListener = + (s, act) -> handleCujEvents(config.getContext(), act, s); + return new FrameTracker(session, mWorker.getThreadHandler(), + threadedRenderer, viewRoot, surfaceControl, choreographer, mMetrics, + mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, + eventsListener, config); } } @@ -341,7 +377,13 @@ public class InteractionJankMonitor { // Notify the receivers if necessary. if (session.shouldNotify()) { - notifyEvents(context, action, session); + if (context != null) { + notifyEvents(context, action, session); + } else { + throw new IllegalArgumentException( + "Can't notify cuj events due to lack of context: cuj=" + + session.getName() + ", action=" + action); + } } } @@ -349,11 +391,16 @@ public class InteractionJankMonitor { final boolean badEnd = action.equals(ACTION_SESSION_END) && session.getReason() != REASON_END_NORMAL; final boolean badCancel = action.equals(ACTION_SESSION_CANCEL) - && session.getReason() != REASON_CANCEL_NORMAL; + && !(session.getReason() == REASON_CANCEL_NORMAL + || session.getReason() == REASON_CANCEL_TIMEOUT); return badEnd || badCancel; } - private void notifyEvents(Context context, String action, Session session) { + /** + * Notifies who may interest in some CUJ events. + */ + @VisibleForTesting + public void notifyEvents(Context context, String action, Session session) { if (action.equals(ACTION_SESSION_CANCEL) && session.getReason() == REASON_CANCEL_NOT_BEGUN) { return; @@ -366,7 +413,7 @@ public class InteractionJankMonitor { } private void removeTimeout(@CujType int cujType) { - synchronized (this) { + synchronized (mLock) { Runnable timeout = mTimeoutActions.get(cujType); if (timeout != null) { mWorker.getThreadHandler().removeCallbacks(timeout); @@ -376,7 +423,7 @@ public class InteractionJankMonitor { } /** - * Begin a trace session. + * Begins a trace session. * * @param v an attached view. * @param cujType the specific {@link InteractionJankMonitor.CujType}. @@ -385,8 +432,7 @@ public class InteractionJankMonitor { public boolean begin(View v, @CujType int cujType) { try { return beginInternal( - new Configuration.Builder(cujType) - .setView(v) + Configuration.Builder.withView(cujType, v) .build()); } catch (IllegalArgumentException ex) { Log.d(TAG, "Build configuration failed!", ex); @@ -395,7 +441,7 @@ public class InteractionJankMonitor { } /** - * Begin a trace session. + * Begins a trace session. * * @param builder the builder of the configurations for instrumenting the CUJ. * @return boolean true if the tracker is started successfully, false otherwise. @@ -410,17 +456,9 @@ public class InteractionJankMonitor { } private boolean beginInternal(@NonNull Configuration conf) { - synchronized (this) { + synchronized (mLock) { int cujType = conf.mCujType; - boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0; - if (!mEnabled || !shouldSample) { - if (DEBUG) { - Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType) - + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED - + ", sample=" + shouldSample + ", interval=" + mSamplingInterval); - } - return false; - } + if (!shouldMonitor(cujType)) return false; FrameTracker tracker = getTracker(cujType); // Skip subsequent calls if we already have an ongoing tracing. if (tracker != null) return false; @@ -431,67 +469,103 @@ public class InteractionJankMonitor { tracker.begin(); // Cancel the trace if we don't get an end() call in specified duration. - Runnable timeoutAction = () -> cancel(cujType); - mTimeoutActions.put(cujType, timeoutAction); - mWorker.getThreadHandler().postDelayed(timeoutAction, conf.mTimeout); + scheduleTimeoutAction( + cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT)); return true; } } /** - * End a trace session. + * Check if the monitoring is enabled and if it should be sampled. + */ + @SuppressWarnings("RandomModInteger") + @VisibleForTesting + public boolean shouldMonitor(@CujType int cujType) { + boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0; + if (!mEnabled || !shouldSample) { + if (DEBUG) { + Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType) + + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED + + ", sample=" + shouldSample + ", interval=" + mSamplingInterval); + } + return false; + } + return true; + } + + /** + * Schedules a timeout action. + * @param cuj cuj type + * @param timeout duration to timeout + * @param action action once timeout + */ + @VisibleForTesting + public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) { + mTimeoutActions.put(cuj, action); + mWorker.getThreadHandler().postDelayed(action, timeout); + } + + /** + * Ends a trace session. * * @param cujType the specific {@link InteractionJankMonitor.CujType}. * @return boolean true if the tracker is ended successfully, false otherwise. */ public boolean end(@CujType int cujType) { - //TODO (163505250): This should be no-op if not in droid food rom. - synchronized (this) { - + synchronized (mLock) { // remove the timeout action first. removeTimeout(cujType); FrameTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. if (tracker == null) return false; - tracker.end(FrameTracker.REASON_END_NORMAL); - removeTracker(cujType); + // if the end call doesn't return true, another thread is handling end of the cuj. + if (tracker.end(REASON_END_NORMAL)) { + removeTracker(cujType); + } return true; } } /** - * Cancel the trace session. + * Cancels the trace session. * * @return boolean true if the tracker is cancelled successfully, false otherwise. */ public boolean cancel(@CujType int cujType) { - //TODO (163505250): This should be no-op if not in droid food rom. - synchronized (this) { + return cancel(cujType, REASON_CANCEL_NORMAL); + } + + /** + * Cancels the trace session. + * + * @return boolean true if the tracker is cancelled successfully, false otherwise. + */ + @VisibleForTesting + public boolean cancel(@CujType int cujType, @Reasons int reason) { + synchronized (mLock) { // remove the timeout action first. removeTimeout(cujType); FrameTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. if (tracker == null) return false; - tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); - removeTracker(cujType); + // if the cancel call doesn't return true, another thread is handling cancel of the cuj. + if (tracker.cancel(reason)) { + removeTracker(cujType); + } return true; } } private FrameTracker getTracker(@CujType int cuj) { - synchronized (this) { - return mRunningTrackers.get(cuj); - } + return mRunningTrackers.get(cuj); } private void removeTracker(@CujType int cuj) { - synchronized (this) { - mRunningTrackers.remove(cuj); - } + mRunningTrackers.remove(cuj); } private void updateProperties(DeviceConfig.Properties properties) { - synchronized (this) { + synchronized (mLock) { mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY, DEFAULT_SAMPLING_INTERVAL); mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED); @@ -509,14 +583,12 @@ public class InteractionJankMonitor { } /** - * Trigger the perfetto daemon to collect and upload data. + * Triggers the perfetto daemon to collect and upload data. */ @VisibleForTesting public void trigger(Session session) { - synchronized (this) { - mWorker.getThreadHandler().post( - () -> PerfettoTrigger.trigger(session.getPerfettoTrigger())); - } + mWorker.getThreadHandler().post( + () -> PerfettoTrigger.trigger(session.getPerfettoTrigger())); } /** @@ -608,6 +680,16 @@ public class InteractionJankMonitor { return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON"; case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP: return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP"; + case CUJ_PIP_TRANSITION: + return "PIP_TRANSITION"; + case CUJ_WALLPAPER_TRANSITION: + return "WALLPAPER_TRANSITION"; + case CUJ_USER_SWITCH: + return "USER_SWITCH"; + case CUJ_SPLASHSCREEN_AVD: + return "SPLASHSCREEN_AVD"; + case CUJ_SPLASHSCREEN_EXIT_ANIM: + return "SPLASHSCREEN_EXIT_ANIM"; } return "UNKNOWN"; } @@ -618,32 +700,65 @@ public class InteractionJankMonitor { */ public static class Configuration { private final View mView; + private final Context mContext; private final long mTimeout; private final String mTag; + private final boolean mSurfaceOnly; + private final SurfaceControl mSurfaceControl; private final @CujType int mCujType; /** - * A builder for building Configuration.
    + * A builder for building Configuration. {@link #setView(View)} is essential + * if {@link #setSurfaceOnly(boolean)} is not set, otherwise both + * {@link #setSurfaceControl(SurfaceControl)} and {@link #setContext(Context)} + * are necessary
    * It may refer to an attached view, don't use static reference for any purpose. */ public static class Builder { private View mAttrView = null; + private Context mAttrContext = null; private long mAttrTimeout = DEFAULT_TIMEOUT_MS; private String mAttrTag = ""; + private boolean mAttrSurfaceOnly; + private SurfaceControl mAttrSurfaceControl; private @CujType int mAttrCujType; /** + * Creates a builder which instruments only surface. * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}. + * @param context context + * @param surfaceControl surface control + * @return builder */ - public Builder(@CujType int cuj) { + public static Builder withSurface(@CujType int cuj, @NonNull Context context, + @NonNull SurfaceControl surfaceControl) { + return new Builder(cuj) + .setContext(context) + .setSurfaceControl(surfaceControl) + .setSurfaceOnly(true); + } + + /** + * Creates a builder which instruments both surface and view. + * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}. + * @param view view + * @return builder + */ + public static Builder withView(@CujType int cuj, @NonNull View view) { + return new Builder(cuj).setView(view) + .setContext(view.getContext()); + } + + private Builder(@CujType int cuj) { mAttrCujType = cuj; } /** + * Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false. * @param view an attached view * @return builder */ - public Builder setView(@NonNull View view) { + private Builder setView(@NonNull View view) { mAttrView = view; return this; } @@ -669,20 +784,56 @@ public class InteractionJankMonitor { } /** - * Build the {@link Configuration} instance + * Indicates if only instrument with surface, + * if true, must also setup with {@link #setContext(Context)} + * and {@link #setSurfaceControl(SurfaceControl)}. + * @param surfaceOnly true if only instrument with surface, false otherwise + * @return builder Surface only builder. + */ + private Builder setSurfaceOnly(boolean surfaceOnly) { + mAttrSurfaceOnly = surfaceOnly; + return this; + } + + /** + * Specifies a context, must set if {@link #setSurfaceOnly(boolean)} is set. + */ + private Builder setContext(Context context) { + mAttrContext = context; + return this; + } + + /** + * Specifies a surface control, must be set if {@link #setSurfaceOnly(boolean)} is set. + */ + private Builder setSurfaceControl(SurfaceControl surfaceControl) { + mAttrSurfaceControl = surfaceControl; + return this; + } + + /** + * Builds the {@link Configuration} instance * @return the instance of {@link Configuration} * @throws IllegalArgumentException if any invalid attribute is set */ public Configuration build() throws IllegalArgumentException { - return new Configuration(mAttrCujType, mAttrView, mAttrTag, mAttrTimeout); + return new Configuration( + mAttrCujType, mAttrView, mAttrTag, mAttrTimeout, + mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl); } } - private Configuration(@CujType int cuj, View view, String tag, long timeout) { + private Configuration(@CujType int cuj, View view, String tag, long timeout, + boolean surfaceOnly, Context context, SurfaceControl surfaceControl) { mCujType = cuj; mTag = tag; mTimeout = timeout; mView = view; + mSurfaceOnly = surfaceOnly; + mContext = context != null + ? context + : (view != null ? view.getContext().getApplicationContext() : null); + mSurfaceControl = surfaceControl; validate(); } @@ -698,14 +849,47 @@ public class InteractionJankMonitor { shouldThrow = true; msg.append("Invalid timeout value; "); } - if (mView == null || !mView.isAttachedToWindow()) { - shouldThrow = true; - msg.append("Null view or view is not attached yet; "); + if (mSurfaceOnly) { + if (mContext == null) { + shouldThrow = true; + msg.append("Must pass in a context if only instrument surface; "); + } + if (mSurfaceControl == null || !mSurfaceControl.isValid()) { + shouldThrow = true; + msg.append("Must pass in a valid surface control if only instrument surface; "); + } + } else { + if (mView == null || !mView.isAttachedToWindow()) { + shouldThrow = true; + msg.append("Null view or unattached view while instrumenting view; "); + } } if (shouldThrow) { throw new IllegalArgumentException(msg.toString()); } } + + /** + * @return true if only instrumenting surface, false otherwise + */ + public boolean isSurfaceOnly() { + return mSurfaceOnly; + } + + /** + * @return the surafce control which is instrumenting + */ + public SurfaceControl getSurfaceControl() { + return mSurfaceControl; + } + + View getView() { + return mView; + } + + Context getContext() { + return mContext; + } } /** @@ -715,8 +899,8 @@ public class InteractionJankMonitor { @CujType private final int mCujType; private final long mTimeStamp; - @FrameTracker.Reasons - private int mReason = FrameTracker.REASON_END_UNKNOWN; + @Reasons + private int mReason = REASON_END_UNKNOWN; private final boolean mShouldNotify; private final String mName; @@ -756,15 +940,15 @@ public class InteractionJankMonitor { return mTimeStamp; } - public void setReason(@FrameTracker.Reasons int reason) { + public void setReason(@Reasons int reason) { mReason = reason; } - public int getReason() { + public @Reasons int getReason() { return mReason; } - /** Determine if should notify the receivers of cuj events */ + /** Determines if should notify the receivers of cuj events */ public boolean shouldNotify() { return mShouldNotify; } diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java index 4ce6f609ef735d829d459ab3ae2215ae066529b4..3eb980465214fef2f07a4dc0fca7131662cf6912 100644 --- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java +++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java @@ -17,19 +17,27 @@ package com.android.internal.notification; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import com.android.internal.R; + +/** + * This class provides methods to create intents for NotificationAccessConfirmationActivity. + */ public final class NotificationAccessConfirmationActivityContract { - private static final ComponentName COMPONENT_NAME = new ComponentName( - "com.android.settings", - "com.android.settings.notification.NotificationAccessConfirmationActivity"); public static final String EXTRA_USER_ID = "user_id"; public static final String EXTRA_COMPONENT_NAME = "component_name"; public static final String EXTRA_PACKAGE_TITLE = "package_title"; - public static Intent launcherIntent(int userId, ComponentName component, String packageTitle) { + /** + * Creates a launcher intent for NotificationAccessConfirmationActivity. + */ + public static Intent launcherIntent(Context context, int userId, ComponentName component, + String packageTitle) { return new Intent() - .setComponent(COMPONENT_NAME) + .setComponent(ComponentName.unflattenFromString(context.getString( + R.string.config_notificationAccessConfirmationActivity))) .putExtra(EXTRA_USER_ID, userId) .putExtra(EXTRA_COMPONENT_NAME, component) .putExtra(EXTRA_PACKAGE_TITLE, packageTitle); diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java index 0307268a28b5a210882f9d233ed7fcc88c0eb5cd..94430704468f2c25c3ca6344a4fa9ff22f9058c8 100644 --- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java +++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT; + import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; @@ -29,11 +31,15 @@ import java.util.List; * Estimates power consumed by the ambient display */ public class AmbientDisplayPowerCalculator extends PowerCalculator { - private final UsageBasedPowerEstimator mPowerEstimator; + private final UsageBasedPowerEstimator[] mPowerEstimators; public AmbientDisplayPowerCalculator(PowerProfile powerProfile) { - mPowerEstimator = new UsageBasedPowerEstimator( - powerProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY)); + final int numDisplays = powerProfile.getNumDisplays(); + mPowerEstimators = new UsageBasedPowerEstimator[numDisplays]; + for (int display = 0; display < numDisplays; display++) { + mPowerEstimators[display] = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, display)); + } } /** @@ -47,8 +53,8 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator { final int powerModel = getPowerModel(measuredEnergyUC, query); final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); - final double powerMah = getMeasuredOrEstimatedPower(powerModel, - measuredEnergyUC, mPowerEstimator, durationMs); + final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs, + measuredEnergyUC); builder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, durationMs) @@ -68,9 +74,8 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator { final long measuredEnergyUC = batteryStats.getScreenDozeMeasuredBatteryConsumptionUC(); final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType); final int powerModel = getPowerModel(measuredEnergyUC); - final double powerMah = getMeasuredOrEstimatedPower(powerModel, - batteryStats.getScreenDozeMeasuredBatteryConsumptionUC(), - mPowerEstimator, durationMs); + final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs, + measuredEnergyUC); if (powerMah > 0) { BatterySipper bs = new BatterySipper(BatterySipper.DrainType.AMBIENT_DISPLAY, null, 0); bs.usagePowerMah = powerMah; @@ -83,4 +88,26 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator { private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) { return batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000; } + + private double calculateTotalPower(@BatteryConsumer.PowerModel int powerModel, + BatteryStats batteryStats, long rawRealtimeUs, long consumptionUC) { + switch (powerModel) { + case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY: + return uCtoMah(consumptionUC); + case BatteryConsumer.POWER_MODEL_POWER_PROFILE: + default: + return calculateEstimatedPower(batteryStats, rawRealtimeUs); + } + } + + private double calculateEstimatedPower(BatteryStats batteryStats, long rawRealtimeUs) { + final int numDisplays = mPowerEstimators.length; + double power = 0; + for (int display = 0; display < numDisplays; display++) { + final long dozeTime = batteryStats.getDisplayScreenDozeTime(display, rawRealtimeUs) + / 1000; + power += mPowerEstimators[display].calculatePower(dozeTime); + } + return power; + } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index a234743b9cc0e7c2cb0e01a80b2964ee159e6865..c9b652ca1142054ba436c06ed6b920bb52e2d595 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -689,7 +689,7 @@ public class BatteryStatsImpl extends BatteryStats { * Schedule a sync because of a screen state change. */ Future scheduleSyncDueToScreenStateChange(int flags, boolean onBattery, - boolean onBatteryScreenOff, int screenState); + boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates); Future scheduleCpuSyncDueToWakelockChange(long delayMillis); void cancelCpuSyncDueToWakelockChange(); Future scheduleSyncDueToBatteryLevelChange(long delayMillis); @@ -850,17 +850,91 @@ public class BatteryStatsImpl extends BatteryStats { public boolean mRecordAllHistory; boolean mNoAutoReset; + /** + * Overall screen state. For multidisplay devices, this represents the current highest screen + * state of the displays. + */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) protected int mScreenState = Display.STATE_UNKNOWN; + /** + * Overall screen on timer. For multidisplay devices, this represents the time spent with at + * least one display in the screen on state. + */ StopwatchTimer mScreenOnTimer; + /** + * Overall screen doze timer. For multidisplay devices, this represents the time spent with + * screen doze being the highest screen state. + */ StopwatchTimer mScreenDozeTimer; - + /** + * Overall screen brightness bin. For multidisplay devices, this represents the current + * brightest screen. + */ int mScreenBrightnessBin = -1; + /** + * Overall screen brightness timers. For multidisplay devices, the {@link mScreenBrightnessBin} + * timer will be active at any given time + */ final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS]; boolean mPretendScreenOff; + private static class DisplayBatteryStats { + /** + * Per display screen state. + */ + public int screenState = Display.STATE_UNKNOWN; + /** + * Per display screen on timers. + */ + public StopwatchTimer screenOnTimer; + /** + * Per display screen doze timers. + */ + public StopwatchTimer screenDozeTimer; + /** + * Per display screen brightness bins. + */ + public int screenBrightnessBin = -1; + /** + * Per display screen brightness timers. + */ + public StopwatchTimer[] screenBrightnessTimers = + new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS]; + /** + * Per display screen state the last time {@link #updateDisplayMeasuredEnergyStatsLocked} + * was called. + */ + public int screenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN; + + DisplayBatteryStats(Clocks clocks, TimeBase timeBase) { + screenOnTimer = new StopwatchTimer(clocks, null, -1, null, + timeBase); + screenDozeTimer = new StopwatchTimer(clocks, null, -1, null, + timeBase); + for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) { + screenBrightnessTimers[i] = new StopwatchTimer(clocks, null, -100 - i, null, + timeBase); + } + } + + /** + * Reset display timers. + */ + public void reset(long elapsedRealtimeUs) { + screenOnTimer.reset(false, elapsedRealtimeUs); + screenDozeTimer.reset(false, elapsedRealtimeUs); + for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) { + screenBrightnessTimers[i].reset(false, elapsedRealtimeUs); + } + } + } + + DisplayBatteryStats[] mPerDisplayBatteryStats; + + private int mDisplayMismatchWtfCount = 0; + boolean mInteractive; StopwatchTimer mInteractiveTimer; @@ -1005,8 +1079,6 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") @VisibleForTesting protected @Nullable MeasuredEnergyStats mGlobalMeasuredEnergyStats; - /** Last known screen state. Needed for apportioning display energy. */ - int mScreenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN; /** Bluetooth Power calculator for attributing measured bluetooth charge consumption to uids */ @Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null; /** Cpu Power calculator for attributing measured cpu charge consumption to uids */ @@ -4316,8 +4388,10 @@ public class BatteryStatsImpl extends BatteryStats { public void setPretendScreenOff(boolean pretendScreenOff) { if (mPretendScreenOff != pretendScreenOff) { mPretendScreenOff = pretendScreenOff; - noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON, - mClocks.elapsedRealtime(), mClocks.uptimeMillis(), mClocks.currentTimeMillis()); + final int primaryScreenState = mPerDisplayBatteryStats[0].screenState; + noteScreenStateLocked(0, primaryScreenState, + mClocks.elapsedRealtime(), mClocks.uptimeMillis(), + mClocks.currentTimeMillis()); } } @@ -4915,29 +4989,158 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - public void noteScreenStateLocked(int state) { - noteScreenStateLocked(state, mClocks.elapsedRealtime(), mClocks.uptimeMillis(), + public void noteScreenStateLocked(int display, int state) { + noteScreenStateLocked(display, state, mClocks.elapsedRealtime(), mClocks.uptimeMillis(), mClocks.currentTimeMillis()); } @GuardedBy("this") - public void noteScreenStateLocked(int state, + public void noteScreenStateLocked(int display, int displayState, long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) { - state = mPretendScreenOff ? Display.STATE_OFF : state; - // Battery stats relies on there being 4 states. To accommodate this, new states beyond the // original 4 are mapped to one of the originals. - if (state > MAX_TRACKED_SCREEN_STATE) { - switch (state) { - case Display.STATE_VR: - state = Display.STATE_ON; + if (displayState > MAX_TRACKED_SCREEN_STATE) { + if (Display.isOnState(displayState)) { + displayState = Display.STATE_ON; + } else if (Display.isDozeState(displayState)) { + if (Display.isSuspendedState(displayState)) { + displayState = Display.STATE_DOZE_SUSPEND; + } else { + displayState = Display.STATE_DOZE; + } + } else if (Display.isOffState(displayState)) { + displayState = Display.STATE_OFF; + } else { + Slog.wtf(TAG, "Unknown screen state (not mapped): " + displayState); + displayState = Display.STATE_UNKNOWN; + } + } + // As of this point, displayState should be mapped to one of: + // - Display.STATE_ON, + // - Display.STATE_DOZE + // - Display.STATE_DOZE_SUSPEND + // - Display.STATE_OFF + // - Display.STATE_UNKNOWN + + int state; + int overallBin = mScreenBrightnessBin; + int externalUpdateFlag = 0; + boolean shouldScheduleSync = false; + final int numDisplay = mPerDisplayBatteryStats.length; + if (display < 0 || display >= numDisplay) { + Slog.wtf(TAG, "Unexpected note screen state for display " + display + " (only " + + mPerDisplayBatteryStats.length + " displays exist...)"); + return; + } + final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display]; + final int oldDisplayState = displayStats.screenState; + + if (oldDisplayState == displayState) { + // Nothing changed + state = mScreenState; + } else { + displayStats.screenState = displayState; + + // Stop timer for previous display state. + switch (oldDisplayState) { + case Display.STATE_ON: + displayStats.screenOnTimer.stopRunningLocked(elapsedRealtimeMs); + final int bin = displayStats.screenBrightnessBin; + if (bin >= 0) { + displayStats.screenBrightnessTimers[bin].stopRunningLocked( + elapsedRealtimeMs); + } + overallBin = evaluateOverallScreenBrightnessBinLocked(); + shouldScheduleSync = true; + break; + case Display.STATE_DOZE: + // Transition from doze to doze suspend can be ignored. + if (displayState == Display.STATE_DOZE_SUSPEND) break; + displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs); + shouldScheduleSync = true; + break; + case Display.STATE_DOZE_SUSPEND: + // Transition from doze suspend to doze can be ignored. + if (displayState == Display.STATE_DOZE) break; + displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs); + shouldScheduleSync = true; + break; + case Display.STATE_OFF: // fallthrough + case Display.STATE_UNKNOWN: + // Not tracked by timers. break; default: - Slog.wtf(TAG, "Unknown screen state (not mapped): " + state); + Slog.wtf(TAG, + "Attempted to stop timer for unexpected display state " + display); + } + + // Start timer for new display state. + switch (displayState) { + case Display.STATE_ON: + displayStats.screenOnTimer.startRunningLocked(elapsedRealtimeMs); + final int bin = displayStats.screenBrightnessBin; + if (bin >= 0) { + displayStats.screenBrightnessTimers[bin].startRunningLocked( + elapsedRealtimeMs); + } + overallBin = evaluateOverallScreenBrightnessBinLocked(); + shouldScheduleSync = true; + break; + case Display.STATE_DOZE: + // Transition from doze suspend to doze can be ignored. + if (oldDisplayState == Display.STATE_DOZE_SUSPEND) break; + displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs); + shouldScheduleSync = true; break; + case Display.STATE_DOZE_SUSPEND: + // Transition from doze to doze suspend can be ignored. + if (oldDisplayState == Display.STATE_DOZE) break; + displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs); + shouldScheduleSync = true; + break; + case Display.STATE_OFF: // fallthrough + case Display.STATE_UNKNOWN: + // Not tracked by timers. + break; + default: + Slog.wtf(TAG, + "Attempted to start timer for unexpected display state " + displayState + + " for display " + display); + } + + if (shouldScheduleSync + && mGlobalMeasuredEnergyStats != null + && mGlobalMeasuredEnergyStats.isStandardBucketSupported( + MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON)) { + // Display measured energy stats is available. Prepare to schedule an + // external sync. + externalUpdateFlag |= ExternalStatsSync.UPDATE_DISPLAY; + } + + // Reevaluate most important display screen state. + state = Display.STATE_UNKNOWN; + for (int i = 0; i < numDisplay; i++) { + final int tempState = mPerDisplayBatteryStats[i].screenState; + if (tempState == Display.STATE_ON + || state == Display.STATE_ON) { + state = Display.STATE_ON; + } else if (tempState == Display.STATE_DOZE + || state == Display.STATE_DOZE) { + state = Display.STATE_DOZE; + } else if (tempState == Display.STATE_DOZE_SUSPEND + || state == Display.STATE_DOZE_SUSPEND) { + state = Display.STATE_DOZE_SUSPEND; + } else if (tempState == Display.STATE_OFF + || state == Display.STATE_OFF) { + state = Display.STATE_OFF; + } } } + final boolean batteryRunning = mOnBatteryTimeBase.isRunning(); + final boolean batteryScreenOffRunning = mOnBatteryScreenOffTimeBase.isRunning(); + + state = mPretendScreenOff ? Display.STATE_OFF : state; if (mScreenState != state) { recordDailyStatsIfNeededLocked(true, currentTimeMs); final int oldState = mScreenState; @@ -4991,11 +5194,11 @@ public class BatteryStatsImpl extends BatteryStats { + Display.stateToString(state)); addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } - // TODO: (Probably overkill) Have mGlobalMeasuredEnergyStats store supported flags and - // only update DISPLAY if it is. Currently overkill since CPU is scheduled anyway. - final int updateFlag = ExternalStatsSync.UPDATE_CPU | ExternalStatsSync.UPDATE_DISPLAY; - mExternalSync.scheduleSyncDueToScreenStateChange(updateFlag, - mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning(), state); + + // Per screen state Cpu stats needed. Prepare to schedule an external sync. + externalUpdateFlag |= ExternalStatsSync.UPDATE_CPU; + shouldScheduleSync = true; + if (Display.isOnState(state)) { updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state, uptimeMs * 1000, elapsedRealtimeMs * 1000); @@ -5013,33 +5216,116 @@ public class BatteryStatsImpl extends BatteryStats { updateDischargeScreenLevelsLocked(oldState, state); } } + + // Changing display states might have changed the screen used to determine the overall + // brightness. + maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs); + + if (shouldScheduleSync) { + final int numDisplays = mPerDisplayBatteryStats.length; + final int[] displayStates = new int[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + displayStates[i] = mPerDisplayBatteryStats[i].screenState; + } + mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag, + batteryRunning, batteryScreenOffRunning, state, displayStates); + } } @UnsupportedAppUsage public void noteScreenBrightnessLocked(int brightness) { - noteScreenBrightnessLocked(brightness, mClocks.elapsedRealtime(), mClocks.uptimeMillis()); + noteScreenBrightnessLocked(0, brightness); + } + + /** + * Note screen brightness change for a display. + */ + public void noteScreenBrightnessLocked(int display, int brightness) { + noteScreenBrightnessLocked(display, brightness, mClocks.elapsedRealtime(), + mClocks.uptimeMillis()); } - public void noteScreenBrightnessLocked(int brightness, long elapsedRealtimeMs, long uptimeMs) { + + /** + * Note screen brightness change for a display. + */ + public void noteScreenBrightnessLocked(int display, int brightness, long elapsedRealtimeMs, + long uptimeMs) { // Bin the brightness. int bin = brightness / (256/NUM_SCREEN_BRIGHTNESS_BINS); if (bin < 0) bin = 0; else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1; - if (mScreenBrightnessBin != bin) { - mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK) - | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT); - if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: " - + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); + + final int overallBin; + + final int numDisplays = mPerDisplayBatteryStats.length; + if (display < 0 || display >= numDisplays) { + Slog.wtf(TAG, "Unexpected note screen brightness for display " + display + " (only " + + mPerDisplayBatteryStats.length + " displays exist...)"); + return; + } + + final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display]; + final int oldBin = displayStats.screenBrightnessBin; + if (oldBin == bin) { + // Nothing changed + overallBin = mScreenBrightnessBin; + } else { + displayStats.screenBrightnessBin = bin; + if (displayStats.screenState == Display.STATE_ON) { + if (oldBin >= 0) { + displayStats.screenBrightnessTimers[oldBin].stopRunningLocked( + elapsedRealtimeMs); + } + displayStats.screenBrightnessTimers[bin].startRunningLocked( + elapsedRealtimeMs); + } + overallBin = evaluateOverallScreenBrightnessBinLocked(); + } + + maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs); + } + + private int evaluateOverallScreenBrightnessBinLocked() { + int overallBin = -1; + final int numDisplays = getDisplayCount(); + for (int display = 0; display < numDisplays; display++) { + final int displayBrightnessBin; + if (mPerDisplayBatteryStats[display].screenState == Display.STATE_ON) { + displayBrightnessBin = mPerDisplayBatteryStats[display].screenBrightnessBin; + } else { + displayBrightnessBin = -1; + } + if (displayBrightnessBin > overallBin) { + overallBin = displayBrightnessBin; + } + } + return overallBin; + } + + private void maybeUpdateOverallScreenBrightness(int overallBin, long elapsedRealtimeMs, + long uptimeMs) { + if (mScreenBrightnessBin != overallBin) { + if (overallBin >= 0) { + mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK) + | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT); + if (DEBUG_HISTORY) { + Slog.v(TAG, "Screen brightness " + overallBin + " to: " + + Integer.toHexString(mHistoryCur.states)); + } + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); + } if (mScreenState == Display.STATE_ON) { if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin] .stopRunningLocked(elapsedRealtimeMs); } - mScreenBrightnessTimer[bin] - .startRunningLocked(elapsedRealtimeMs); + if (overallBin >= 0) { + mScreenBrightnessTimer[overallBin] + .startRunningLocked(elapsedRealtimeMs); + } } - mScreenBrightnessBin = bin; + mScreenBrightnessBin = overallBin; } } @@ -6701,6 +6987,31 @@ public class BatteryStatsImpl extends BatteryStats { return mScreenBrightnessTimer[brightnessBin]; } + @Override + public int getDisplayCount() { + return mPerDisplayBatteryStats.length; + } + + @Override + public long getDisplayScreenOnTime(int display, long elapsedRealtimeUs) { + return mPerDisplayBatteryStats[display].screenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, + STATS_SINCE_CHARGED); + } + + @Override + public long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs) { + return mPerDisplayBatteryStats[display].screenDozeTimer.getTotalTimeLocked( + elapsedRealtimeUs, STATS_SINCE_CHARGED); + } + + @Override + public long getDisplayScreenBrightnessTime(int display, int brightnessBin, + long elapsedRealtimeUs) { + final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display]; + return displayStats.screenBrightnessTimers[brightnessBin].getTotalTimeLocked( + elapsedRealtimeUs, STATS_SINCE_CHARGED); + } + @Override public long getInteractiveTime(long elapsedRealtimeUs, int which) { return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -10702,6 +11013,10 @@ public class BatteryStatsImpl extends BatteryStats { mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null, mOnBatteryTimeBase); } + + mPerDisplayBatteryStats = new DisplayBatteryStats[1]; + mPerDisplayBatteryStats[0] = new DisplayBatteryStats(mClocks, mOnBatteryTimeBase); + mInteractiveTimer = new StopwatchTimer(mClocks, null, -10, null, mOnBatteryTimeBase); mPowerSaveModeEnabledTimer = new StopwatchTimer(mClocks, null, -2, null, mOnBatteryTimeBase); @@ -10814,6 +11129,8 @@ public class BatteryStatsImpl extends BatteryStats { // Initialize the estimated battery capacity to a known preset one. mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity(); } + + setDisplayCountLocked(mPowerProfile.getNumDisplays()); } PowerProfile getPowerProfile() { @@ -10846,6 +11163,16 @@ public class BatteryStatsImpl extends BatteryStats { mExternalSync = sync; } + /** + * Initialize and set multi display timers and states. + */ + public void setDisplayCountLocked(int numDisplays) { + mPerDisplayBatteryStats = new DisplayBatteryStats[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + mPerDisplayBatteryStats[i] = new DisplayBatteryStats(mClocks, mOnBatteryTimeBase); + } + } + public void updateDailyDeadlineLocked() { // Get the current time. long currentTimeMs = mDailyStartTimeMs = mClocks.currentTimeMillis(); @@ -11322,6 +11649,11 @@ public class BatteryStatsImpl extends BatteryStats { mScreenBrightnessTimer[i].reset(false, elapsedRealtimeUs); } + final int numDisplays = mPerDisplayBatteryStats.length; + for (int i = 0; i < numDisplays; i++) { + mPerDisplayBatteryStats[i].reset(elapsedRealtimeUs); + } + if (mPowerProfile != null) { mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity(); } else { @@ -12584,22 +12916,43 @@ public class BatteryStatsImpl extends BatteryStats { * is always 0 when the screen is not "ON" and whenever the rail energy is 0 (if supported). * To the extent that those assumptions are violated, the algorithm will err. * - * @param chargeUC amount of charge (microcoulombs) used by Display since this was last called. - * @param screenState screen state at the time this data collection was scheduled + * @param chargesUC amount of charge (microcoulombs) used by each Display since this was last + * called. + * @param screenStates each screen state at the time this data collection was scheduled */ @GuardedBy("this") - public void updateDisplayMeasuredEnergyStatsLocked(long chargeUC, int screenState, + public void updateDisplayMeasuredEnergyStatsLocked(long[] chargesUC, int[] screenStates, long elapsedRealtimeMs) { - if (DEBUG_ENERGY) Slog.d(TAG, "Updating display stats: " + chargeUC); + if (DEBUG_ENERGY) Slog.d(TAG, "Updating display stats: " + Arrays.toString(chargesUC)); if (mGlobalMeasuredEnergyStats == null) { return; } - final @StandardPowerBucket int powerBucket = - MeasuredEnergyStats.getDisplayPowerBucket(mScreenStateAtLastEnergyMeasurement); - mScreenStateAtLastEnergyMeasurement = screenState; + final int numDisplays; + if (mPerDisplayBatteryStats.length == screenStates.length) { + numDisplays = screenStates.length; + } else { + // if this point is reached, it will be reached every display state change. + // Rate limit the wtf logging to once every 100 display updates. + if (mDisplayMismatchWtfCount++ % 100 == 0) { + Slog.wtf(TAG, "Mismatch between PowerProfile reported display count (" + + mPerDisplayBatteryStats.length + + ") and PowerStatsHal reported display count (" + screenStates.length + + ")"); + } + // Keep the show going, use the shorter of the two. + numDisplays = mPerDisplayBatteryStats.length < screenStates.length + ? mPerDisplayBatteryStats.length : screenStates.length; + } - if (!mOnBatteryInternal || chargeUC <= 0) { + final int[] oldScreenStates = new int[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + final int screenState = screenStates[i]; + oldScreenStates[i] = mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement; + mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState; + } + + if (!mOnBatteryInternal) { // There's nothing further to update. return; } @@ -12614,17 +12967,31 @@ public class BatteryStatsImpl extends BatteryStats { return; } - mGlobalMeasuredEnergyStats.updateStandardBucket(powerBucket, chargeUC); + long totalScreenOnChargeUC = 0; + for (int i = 0; i < numDisplays; i++) { + final long chargeUC = chargesUC[i]; + if (chargeUC <= 0) { + // There's nothing further to update. + continue; + } + + final @StandardPowerBucket int powerBucket = + MeasuredEnergyStats.getDisplayPowerBucket(oldScreenStates[i]); + mGlobalMeasuredEnergyStats.updateStandardBucket(powerBucket, chargeUC); + if (powerBucket == MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON) { + totalScreenOnChargeUC += chargeUC; + } + } // Now we blame individual apps, but only if the display was ON. - if (powerBucket != MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON) { + if (totalScreenOnChargeUC <= 0) { return; } // TODO(b/175726779): Consider unifying the code with the non-rail display power blaming. // NOTE: fg time is NOT pooled. If two uids are both somehow in fg, then that time is // 'double counted' and will simply exceed the realtime that elapsed. - // If multidisplay becomes a reality, this is probably more reasonable than pooling. + // TODO(b/175726779): collect per display uid visibility for display power attribution. // Collect total time since mark so that we can normalize power. final SparseDoubleArray fgTimeUsArray = new SparseDoubleArray(); @@ -12637,7 +13004,8 @@ public class BatteryStatsImpl extends BatteryStats { if (fgTimeUs == 0) continue; fgTimeUsArray.put(uid.getUid(), (double) fgTimeUs); } - distributeEnergyToUidsLocked(powerBucket, chargeUC, fgTimeUsArray, 0); + distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON, + totalScreenOnChargeUC, fgTimeUsArray, 0); } /** @@ -14543,7 +14911,12 @@ public class BatteryStatsImpl extends BatteryStats { public void initMeasuredEnergyStatsLocked(@Nullable boolean[] supportedStandardBuckets, String[] customBucketNames) { boolean supportedBucketMismatch = false; - mScreenStateAtLastEnergyMeasurement = mScreenState; + + final int numDisplays = mPerDisplayBatteryStats.length; + for (int i = 0; i < numDisplays; i++) { + final int screenState = mPerDisplayBatteryStats[i].screenState; + mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState; + } if (supportedStandardBuckets == null) { if (mGlobalMeasuredEnergyStats != null) { diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java index 4979ecbae8cb73f5324dc5e1b4376b8919649446..93d562c571f87172ee02abbc1e0a91bc5bbb0ab3 100644 --- a/core/java/com/android/internal/os/PowerCalculator.java +++ b/core/java/com/android/internal/os/PowerCalculator.java @@ -132,32 +132,6 @@ public abstract class PowerCalculator { : BatteryConsumer.POWER_MODEL_POWER_PROFILE; } - /** - * Returns either the measured energy converted to mAh or a usage-based estimate. - */ - protected static double getMeasuredOrEstimatedPower(@BatteryConsumer.PowerModel int powerModel, - long measuredEnergyUC, UsageBasedPowerEstimator powerEstimator, long durationMs) { - switch (powerModel) { - case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY: - return uCtoMah(measuredEnergyUC); - case BatteryConsumer.POWER_MODEL_POWER_PROFILE: - default: - return powerEstimator.calculatePower(durationMs); - } - } - - /** - * Returns either the measured energy converted to mAh or a usage-based estimate. - */ - protected static double getMeasuredOrEstimatedPower( - long measuredEnergyUC, UsageBasedPowerEstimator powerEstimator, long durationMs) { - if (measuredEnergyUC != BatteryStats.POWER_DATA_UNAVAILABLE) { - return uCtoMah(measuredEnergyUC); - } else { - return powerEstimator.calculatePower(durationMs); - } - } - /** * Prints formatted amount of power in milli-amp-hours. */ diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index add2304afe9dc33bf2f034b1da9d695facb4052b..4d19b35b1e1663be9aeb3f1191e4db397011fd9c 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -17,10 +17,12 @@ package com.android.internal.os; +import android.annotation.StringDef; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -30,6 +32,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; @@ -40,6 +44,8 @@ import java.util.HashMap; */ public class PowerProfile { + public static final String TAG = "PowerProfile"; + /* * POWER_CPU_SUSPEND: Power consumption when CPU is in power collapse mode. * POWER_CPU_IDLE: Power consumption when CPU is awake (when a wake lock is held). This should @@ -145,12 +151,18 @@ public class PowerProfile { /** * Power consumption when screen is in doze/ambient/always-on mode, including backlight power. + * + * @deprecated Use {@link #POWER_GROUP_DISPLAY_AMBIENT} instead. */ + @Deprecated public static final String POWER_AMBIENT_DISPLAY = "ambient.on"; /** * Power consumption when screen is on, not including the backlight power. + * + * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_ON} instead. */ + @Deprecated @UnsupportedAppUsage public static final String POWER_SCREEN_ON = "screen.on"; @@ -175,7 +187,10 @@ public class PowerProfile { /** * Power consumption at full backlight brightness. If the backlight is at * 50% brightness, then this should be multiplied by 0.5 + * + * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_FULL} instead. */ + @Deprecated @UnsupportedAppUsage public static final String POWER_SCREEN_FULL = "screen.full"; @@ -220,6 +235,29 @@ public class PowerProfile { */ public static final String POWER_BATTERY_CAPACITY = "battery.capacity"; + /** + * Power consumption when a screen is in doze/ambient/always-on mode, including backlight power. + */ + public static final String POWER_GROUP_DISPLAY_AMBIENT = "ambient.on.display"; + + /** + * Power consumption when a screen is on, not including the backlight power. + */ + public static final String POWER_GROUP_DISPLAY_SCREEN_ON = "screen.on.display"; + + /** + * Power consumption of a screen at full backlight brightness. + */ + public static final String POWER_GROUP_DISPLAY_SCREEN_FULL = "screen.full.display"; + + @StringDef(prefix = { "POWER_GROUP_" }, value = { + POWER_GROUP_DISPLAY_AMBIENT, + POWER_GROUP_DISPLAY_SCREEN_ON, + POWER_GROUP_DISPLAY_SCREEN_FULL, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PowerGroup {} + /** * A map from Power Use Item to its power consumption. */ @@ -255,6 +293,7 @@ public class PowerProfile { readPowerValuesFromXml(context, forTest); } initCpuClusters(); + initDisplays(); } } @@ -424,6 +463,58 @@ public class PowerProfile { return 0; } + private int mNumDisplays; + + private void initDisplays() { + // Figure out how many displays are listed in the power profile. + mNumDisplays = 0; + while (!Double.isNaN( + getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, mNumDisplays, Double.NaN)) + || !Double.isNaN( + getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, mNumDisplays, Double.NaN)) + || !Double.isNaN( + getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, mNumDisplays, + Double.NaN))) { + mNumDisplays++; + } + + // Handle legacy display power constants. + final Double deprecatedAmbientDisplay = sPowerItemMap.get(POWER_AMBIENT_DISPLAY); + boolean legacy = false; + if (deprecatedAmbientDisplay != null && mNumDisplays == 0) { + final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_AMBIENT, 0); + Slog.w(TAG, POWER_AMBIENT_DISPLAY + " is deprecated! Use " + key + " instead."); + sPowerItemMap.put(key, deprecatedAmbientDisplay); + legacy = true; + } + + final Double deprecatedScreenOn = sPowerItemMap.get(POWER_SCREEN_ON); + if (deprecatedScreenOn != null && mNumDisplays == 0) { + final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_ON, 0); + Slog.w(TAG, POWER_SCREEN_ON + " is deprecated! Use " + key + " instead."); + sPowerItemMap.put(key, deprecatedScreenOn); + legacy = true; + } + + final Double deprecatedScreenFull = sPowerItemMap.get(POWER_SCREEN_FULL); + if (deprecatedScreenFull != null && mNumDisplays == 0) { + final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_FULL, 0); + Slog.w(TAG, POWER_SCREEN_FULL + " is deprecated! Use " + key + " instead."); + sPowerItemMap.put(key, deprecatedScreenFull); + legacy = true; + } + if (legacy) { + mNumDisplays = 1; + } + } + + /** + * Returns the number built in displays on the device as defined in the power_profile.xml. + */ + public int getNumDisplays() { + return mNumDisplays; + } + /** * Returns the number of memory bandwidth buckets defined in power_profile.xml, or a * default value if the subsystem has no recorded value. @@ -495,6 +586,32 @@ public class PowerProfile { } } + /** + * Returns the average current in mA consumed by an ordinaled subsystem, or the given + * default value if the subsystem has no recorded value. + * + * @param group the subsystem {@link PowerGroup}. + * @param ordinal which entity in the {@link PowerGroup}. + * @param defaultValue the value to return if the subsystem has no recorded value. + * @return the average current in milliAmps. + */ + public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal, + double defaultValue) { + final String type = getOrdinalPowerType(group, ordinal); + return getAveragePowerOrDefault(type, defaultValue); + } + + /** + * Returns the average current in mA consumed by an ordinaled subsystem. + * + * @param group the subsystem {@link PowerGroup}. + * @param ordinal which entity in the {@link PowerGroup}. + * @return the average current in milliAmps. + */ + public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal) { + return getAveragePowerForOrdinal(group, ordinal, 0); + } + /** * Returns the battery capacity, if available, in milli Amp Hours. If not available, * it returns zero. @@ -682,4 +799,9 @@ public class PowerProfile { } } } + + // Creates the key for an ordinaled power constant from the group and ordinal. + private static String getOrdinalPowerType(@PowerGroup String group, int ordinal) { + return group + ordinal; + } } diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java index 1b3bc234fc0fa91c0aaf3a18ffa3c9f299b17f9f..2b634598bbbc453a8a2cae653e5943ee35e6b2a7 100644 --- a/core/java/com/android/internal/os/ScreenPowerCalculator.java +++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java @@ -16,6 +16,9 @@ package com.android.internal.os; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON; + import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; @@ -41,8 +44,8 @@ public class ScreenPowerCalculator extends PowerCalculator { // Minimum amount of time the screen should be on to start smearing drain to apps public static final long MIN_ACTIVE_TIME_FOR_SMEARING = 10 * DateUtils.MINUTE_IN_MILLIS; - private final UsageBasedPowerEstimator mScreenOnPowerEstimator; - private final UsageBasedPowerEstimator mScreenFullPowerEstimator; + private final UsageBasedPowerEstimator[] mScreenOnPowerEstimators; + private final UsageBasedPowerEstimator[] mScreenFullPowerEstimators; private static class PowerAndDuration { public long durationMs; @@ -50,10 +53,16 @@ public class ScreenPowerCalculator extends PowerCalculator { } public ScreenPowerCalculator(PowerProfile powerProfile) { - mScreenOnPowerEstimator = new UsageBasedPowerEstimator( - powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON)); - mScreenFullPowerEstimator = new UsageBasedPowerEstimator( - powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL)); + final int numDisplays = powerProfile.getNumDisplays(); + mScreenOnPowerEstimators = new UsageBasedPowerEstimator[numDisplays]; + mScreenFullPowerEstimators = new UsageBasedPowerEstimator[numDisplays]; + for (int display = 0; display < numDisplays; display++) { + mScreenOnPowerEstimators[display] = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, display)); + mScreenFullPowerEstimators[display] = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, + display)); + } } @Override @@ -168,7 +177,7 @@ public class ScreenPowerCalculator extends PowerCalculator { case BatteryConsumer.POWER_MODEL_POWER_PROFILE: default: totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats, - rawRealtimeUs, statsType, totalPowerAndDuration.durationMs); + rawRealtimeUs); } } @@ -190,19 +199,25 @@ public class ScreenPowerCalculator extends PowerCalculator { return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000; } - private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, long rawRealtimeUs, - int statsType, long durationMs) { - double power = mScreenOnPowerEstimator.calculatePower(durationMs); - for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) { - final long brightnessTime = - batteryStats.getScreenBrightnessTime(i, rawRealtimeUs, statsType) / 1000; - final double binPowerMah = mScreenFullPowerEstimator.calculatePower(brightnessTime) - * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; - if (DEBUG && binPowerMah != 0) { - Slog.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime - + " power=" + formatCharge(binPowerMah)); + private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, + long rawRealtimeUs) { + final int numDisplays = mScreenOnPowerEstimators.length; + double power = 0; + for (int display = 0; display < numDisplays; display++) { + final long displayTime = batteryStats.getDisplayScreenOnTime(display, rawRealtimeUs) + / 1000; + power += mScreenOnPowerEstimators[display].calculatePower(displayTime); + for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) { + final long brightnessTime = batteryStats.getDisplayScreenBrightnessTime(display, + bin, rawRealtimeUs) / 1000; + final double binPowerMah = mScreenFullPowerEstimators[display].calculatePower( + brightnessTime) * (bin + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; + if (DEBUG && binPowerMah != 0) { + Slog.d(TAG, "Screen bin #" + bin + ": time=" + brightnessTime + + " power=" + formatCharge(binPowerMah)); + } + power += binPowerMah; } - power += binPowerMah; } return power; } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 424632fe82ebefab9bfadaacfc6242ee4a701562..6541b14b90702df5095d5109c2831b035683692a 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -270,8 +270,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Drawable mCaptionBackgroundDrawable; private Drawable mUserCaptionBackgroundDrawable; - private float mAvailableWidth; - String mLogTag = TAG; private final Rect mFloatingInsets = new Rect(); private boolean mApplyFloatingVerticalInsets = false; @@ -315,8 +313,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mSemiTransparentBarColor = context.getResources().getColor( R.color.system_bar_background_semi_transparent, null /* theme */); - updateAvailableWidth(); - setWindow(window); updateLogTag(params); @@ -697,7 +693,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); + final Resources res = getContext().getResources(); + final DisplayMetrics metrics = res.getDisplayMetrics(); final boolean isPortrait = getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; @@ -767,17 +764,19 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (!fixedWidth && widthMode == AT_MOST) { final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor; + final float availableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + res.getConfiguration().screenWidthDp, metrics); if (tv.type != TypedValue.TYPE_NULL) { final int min; if (tv.type == TypedValue.TYPE_DIMENSION) { - min = (int)tv.getDimension(metrics); + min = (int) tv.getDimension(metrics); } else if (tv.type == TypedValue.TYPE_FRACTION) { - min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth); + min = (int) tv.getFraction(availableWidth, availableWidth); } else { min = 0; } if (DEBUG_MEASURE) Log.d(mLogTag, "Adjust for min width: " + min + ", value::" - + tv.coerceToString() + ", mAvailableWidth=" + mAvailableWidth); + + tv.coerceToString() + ", mAvailableWidth=" + availableWidth); if (width < min) { widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); @@ -2144,7 +2143,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateDecorCaptionStatus(newConfig); - updateAvailableWidth(); initializeElevation(); } @@ -2616,12 +2614,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mLogTag = TAG + "[" + getTitleSuffix(params) + "]"; } - private void updateAvailableWidth() { - Resources res = getResources(); - mAvailableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - res.getConfiguration().screenWidthDp, res.getDisplayMetrics()); - } - /** * @hide */ diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl index 8e454db4cb040146664e9e58f5c0829680ead6eb..419b1f8feac7a48e39c02bdb23855fa256a46ce0 100644 --- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl +++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl @@ -20,5 +20,4 @@ interface IKeyguardStateCallback { void onSimSecureStateChanged(boolean simSecure); void onInputRestrictedStateChanged(boolean inputRestricted); void onTrustedChanged(boolean trusted); - void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper); } \ No newline at end of file diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java index 52172cf04362997f844900c3fa97b18d12b9ba96..ec62839228075536bdead733c902a187053bba74 100644 --- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -16,7 +16,9 @@ package com.android.internal.policy; +import android.content.Context; import android.content.res.Resources; +import android.view.RoundedCorners; import com.android.internal.R; @@ -29,23 +31,28 @@ public class ScreenDecorationsUtils { * Corner radius that should be used on windows in order to cover the display. * These values are expressed in pixels because they should not respect display or font * scaling, this means that we don't have to reload them on config changes. + * + * Note that if the context is not an UI context(not associated with Display), it will use + * default display. */ - public static float getWindowCornerRadius(Resources resources) { + public static float getWindowCornerRadius(Context context) { + final Resources resources = context.getResources(); if (!supportsRoundedCornersOnWindows(resources)) { return 0f; } - + // Use Context#getDisplayNoVerify() in case the context is not an UI context. + final String displayUniqueId = context.getDisplayNoVerify().getUniqueId(); // Radius that should be used in case top or bottom aren't defined. - float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius) - - resources.getDimension(R.dimen.rounded_corner_radius_adjustment); + float defaultRadius = RoundedCorners.getRoundedCornerRadius(resources, displayUniqueId) + - RoundedCorners.getRoundedCornerRadiusAdjustment(resources, displayUniqueId); - float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top) - - resources.getDimension(R.dimen.rounded_corner_radius_top_adjustment); + float topRadius = RoundedCorners.getRoundedCornerTopRadius(resources, displayUniqueId) + - RoundedCorners.getRoundedCornerRadiusTopAdjustment(resources, displayUniqueId); if (topRadius == 0f) { topRadius = defaultRadius; } - float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom) - - resources.getDimension(R.dimen.rounded_corner_radius_bottom_adjustment); + float bottomRadius = RoundedCorners.getRoundedCornerBottomRadius(resources, displayUniqueId) + - RoundedCorners.getRoundedCornerRadiusBottomAdjustment(resources, displayUniqueId); if (bottomRadius == 0f) { bottomRadius = defaultRadius; } diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..6bf1333097f704fa3a4c0216fde37b9d1c237151 --- /dev/null +++ b/core/java/com/android/internal/policy/SystemBarUtils.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Insets; +import android.util.RotationUtils; +import android.view.DisplayCutout; +import android.view.Surface; + +import com.android.internal.R; + +/** + * Utility functions for system bars used by both window manager and System UI. + * + * @hide + */ +public final class SystemBarUtils { + + /** + * Gets the status bar height. + */ + public static int getStatusBarHeight(Context context) { + return getStatusBarHeight(context.getResources(), context.getDisplay().getCutout()); + } + + /** + * Gets the status bar height with a specific display cutout. + */ + public static int getStatusBarHeight(Resources res, DisplayCutout cutout) { + final int defaultSize = res.getDimensionPixelSize(R.dimen.status_bar_height); + final int safeInsetTop = cutout == null ? 0 : cutout.getSafeInsetTop(); + final int waterfallInsetTop = cutout == null ? 0 : cutout.getWaterfallInsets().top; + // The status bar height should be: + // Max(top cutout size, (status bar default height + waterfall top size)) + return Math.max(safeInsetTop, defaultSize + waterfallInsetTop); + } + + /** + * Gets the status bar height for a specific rotation. + */ + public static int getStatusBarHeightForRotation( + Context context, @Surface.Rotation int targetRot) { + final int rotation = context.getDisplay().getRotation(); + final DisplayCutout cutout = context.getDisplay().getCutout(); + + Insets insets = cutout == null ? Insets.NONE : Insets.of(cutout.getSafeInsets()); + Insets waterfallInsets = cutout == null ? Insets.NONE : cutout.getWaterfallInsets(); + // rotate insets to target rotation if needed. + if (rotation != targetRot) { + if (!insets.equals(Insets.NONE)) { + insets = RotationUtils.rotateInsets( + insets, RotationUtils.deltaRotation(rotation, targetRot)); + } + if (!waterfallInsets.equals(Insets.NONE)) { + waterfallInsets = RotationUtils.rotateInsets( + waterfallInsets, RotationUtils.deltaRotation(rotation, targetRot)); + } + } + final int defaultSize = + context.getResources().getDimensionPixelSize(R.dimen.status_bar_height); + // The status bar height should be: + // Max(top cutout size, (status bar default height + waterfall top size)) + return Math.max(insets.top, defaultSize + waterfallInsets.top); + } + + /** + * Gets the height of area above QQS where battery/time go in notification panel. The height + * equals to status bar height if status bar height is bigger than the + * {@link R.dimen#quick_qs_offset_height}. + */ + public static int getQuickQsOffsetHeight(Context context) { + final int defaultSize = context.getResources().getDimensionPixelSize( + R.dimen.quick_qs_offset_height); + final int statusBarHeight = getStatusBarHeight(context); + // Equals to status bar height if status bar height is bigger. + return Math.max(defaultSize, statusBarHeight); + } +} diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 60a8d802861f9c6664074d9a1ee5c97d4083e359..d3224b13e312daa3f467d58f9ba5adde9add8279 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -16,16 +16,20 @@ package com.android.internal.policy; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN; +import static android.view.WindowManager.TRANSIT_OPEN; +import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -34,12 +38,18 @@ import android.content.res.Configuration; import android.content.res.ResourceId; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Picture; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.hardware.HardwareBuffer; import android.os.SystemProperties; import android.util.Slog; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.TransitionOldType; +import android.view.WindowManager.TransitionType; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; @@ -56,11 +66,17 @@ import java.util.List; /** @hide */ public class TransitionAnimation { + public static final int WALLPAPER_TRANSITION_NONE = 0; + public static final int WALLPAPER_TRANSITION_OPEN = 1; + public static final int WALLPAPER_TRANSITION_CLOSE = 2; + public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 3; + public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 4; + // These are the possible states for the enter/exit activities during a thumbnail transition - public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0; - public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1; - public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2; - public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3; + private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0; + private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1; + private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2; + private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3; /** * Maximum duration for the clip reveal animation. This is used when there is a lot of movement @@ -72,9 +88,15 @@ public class TransitionAnimation { public static final int DEFAULT_APP_TRANSITION_DURATION = 336; + /** Fraction of animation at which the recents thumbnail stays completely transparent */ + private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f; /** Fraction of animation at which the recents thumbnail becomes completely transparent */ private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f; + /** Interpolator to be used for animations that respond directly to a touch */ + static final Interpolator TOUCH_RESPONSE_INTERPOLATOR = + new PathInterpolator(0.3f, 0f, 0.1f, 1f); + private static final String DEFAULT_PACKAGE = "android"; private final Context mContext; @@ -86,7 +108,9 @@ public class TransitionAnimation { new PathInterpolator(0.3f, 0f, 0.1f, 1f); private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f); private final Interpolator mDecelerateInterpolator; + private final Interpolator mFastOutLinearInInterpolator; private final Interpolator mLinearOutSlowInInterpolator; + private final Interpolator mThumbnailFadeInInterpolator; private final Interpolator mThumbnailFadeOutInterpolator; private final Rect mTmpFromClipRect = new Rect(); private final Rect mTmpToClipRect = new Rect(); @@ -107,8 +131,19 @@ public class TransitionAnimation { mDecelerateInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.decelerate_cubic); + mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_linear_in); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.linear_out_slow_in); + mThumbnailFadeInInterpolator = input -> { + // Linear response for first fraction, then complete after that. + if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) { + return 0f; + } + float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION) + / (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION); + return mFastOutLinearInInterpolator.getInterpolation(t); + }; mThumbnailFadeOutInterpolator = input -> { // Linear response for first fraction, then complete after that. if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) { @@ -181,6 +216,13 @@ public class TransitionAnimation { DEFAULT_PACKAGE, com.android.internal.R.anim.cross_profile_apps_thumbnail_enter); } + @Nullable + public Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) { + final Animation animation = loadCrossProfileAppThumbnailEnterAnimation(); + return prepareThumbnailAnimationWithDuration(animation, appRect.width(), + appRect.height(), 0, null); + } + /** Load animation by resource Id from specific package. */ @Nullable public Animation loadAnimationRes(String packageName, int resId) { @@ -347,8 +389,15 @@ public class TransitionAnimation { } } - public Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame, - Rect displayFrame, Rect startRect) { + public Animation createClipRevealAnimationLocked(@TransitionType int transit, + int wallpaperTransit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) { + return createClipRevealAnimationLockedCompat( + getTransitCompatType(transit, wallpaperTransit), enter, appFrame, displayFrame, + startRect); + } + + public Animation createClipRevealAnimationLockedCompat(@TransitionOldType int transit, + boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) { final Animation anim; if (enter) { final int appWidth = appFrame.width(); @@ -458,8 +507,14 @@ public class TransitionAnimation { return anim; } - public Animation createScaleUpAnimationLocked(int transit, boolean enter, - Rect containingFrame, Rect startRect) { + public Animation createScaleUpAnimationLocked(@TransitionType int transit, int wallpaperTransit, + boolean enter, Rect containingFrame, Rect startRect) { + return createScaleUpAnimationLockedCompat(getTransitCompatType(transit, wallpaperTransit), + enter, containingFrame, startRect); + } + + public Animation createScaleUpAnimationLockedCompat(@TransitionOldType int transit, + boolean enter, Rect containingFrame, Rect startRect) { Animation a; setupDefaultNextAppTransitionStartRect(startRect, mTmpRect); final int appWidth = containingFrame.width(); @@ -514,12 +569,19 @@ public class TransitionAnimation { return a; } + public Animation createThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp, + Rect containingFrame, @TransitionType int transit, int wallpaperTransit, + HardwareBuffer thumbnailHeader, Rect startRect) { + return createThumbnailEnterExitAnimationLockedCompat(enter, scaleUp, containingFrame, + getTransitCompatType(transit, wallpaperTransit), thumbnailHeader, startRect); + } + /** * This animation is created when we are doing a thumbnail transition, for the activity that is * leaving, and the activity that is entering. */ - public Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, - Rect containingFrame, int transit, HardwareBuffer thumbnailHeader, + public Animation createThumbnailEnterExitAnimationLockedCompat(boolean enter, boolean scaleUp, + Rect containingFrame, @TransitionOldType int transit, HardwareBuffer thumbnailHeader, Rect startRect) { final int appWidth = containingFrame.width(); final int appHeight = containingFrame.height(); @@ -529,6 +591,7 @@ public class TransitionAnimation { final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight; final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; + final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp); switch (thumbTransitState) { case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: { @@ -587,8 +650,8 @@ public class TransitionAnimation { * This alternate animation is created when we are doing a thumbnail transition, for the * activity that is leaving, and the activity that is entering. */ - public Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState, - int orientation, int transit, Rect containingFrame, Rect contentInsets, + public Animation createAspectScaledThumbnailEnterExitAnimationLocked(boolean enter, + boolean scaleUp, int orientation, int transit, Rect containingFrame, Rect contentInsets, @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform, Rect startRect, Rect defaultStartRect) { Animation a; @@ -601,11 +664,11 @@ public class TransitionAnimation { final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left; final int thumbStartY = mTmpRect.top - containingFrame.top; + final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp); switch (thumbTransitState) { case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: { - final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP; if (freeform && scaleUp) { a = createAspectScaledThumbnailEnterFreeformAnimationLocked( containingFrame, surfaceInsets, startRect, defaultStartRect); @@ -719,11 +782,152 @@ public class TransitionAnimation { THUMBNAIL_APP_TRANSITION_DURATION, mTouchResponseInterpolator); } + /** + * This animation runs for the thumbnail that gets cross faded with the enter/exit activity + * when a thumbnail is specified with the pending animation override. + */ + public Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, + @Nullable Rect contentInsets, HardwareBuffer thumbnailHeader, int orientation, + Rect startRect, Rect defaultStartRect, boolean scaleUp) { + Animation a; + final int thumbWidthI = thumbnailHeader.getWidth(); + final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; + final int thumbHeightI = thumbnailHeader.getHeight(); + final int appWidth = appRect.width(); + + float scaleW = appWidth / thumbWidth; + getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect); + final float fromX; + float fromY; + final float toX; + float toY; + final float pivotX; + final float pivotY; + if (shouldScaleDownThumbnailTransition(orientation)) { + fromX = mTmpRect.left; + fromY = mTmpRect.top; + + // For the curved translate animation to work, the pivot points needs to be at the + // same absolute position as the one from the real surface. + toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left; + toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top; + pivotX = mTmpRect.width() / 2; + pivotY = appRect.height() / 2 / scaleW; + if (mGridLayoutRecentsEnabled) { + // In the grid layout, the header is displayed above the thumbnail instead of + // overlapping it. + fromY -= thumbHeightI; + toY -= thumbHeightI * scaleW; + } + } else { + pivotX = 0; + pivotY = 0; + fromX = mTmpRect.left; + fromY = mTmpRect.top; + toX = appRect.left; + toY = appRect.top; + } + if (scaleUp) { + // Animation up from the thumbnail to the full screen + Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY); + scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); + scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); + Animation alpha = new AlphaAnimation(1f, 0f); + alpha.setInterpolator(mThumbnailFadeOutInterpolator); + alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); + Animation translate = createCurvedMotion(fromX, toX, fromY, toY); + translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); + translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); + + mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI); + mTmpToClipRect.set(appRect); + + // Containing frame is in screen space, but we need the clip rect in the + // app space. + mTmpToClipRect.offsetTo(0, 0); + mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW); + mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW); + + if (contentInsets != null) { + mTmpToClipRect.inset((int) (-contentInsets.left * scaleW), + (int) (-contentInsets.top * scaleW), + (int) (-contentInsets.right * scaleW), + (int) (-contentInsets.bottom * scaleW)); + } + + Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect); + clipAnim.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); + clipAnim.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); + + // This AnimationSet uses the Interpolators assigned above. + AnimationSet set = new AnimationSet(false); + set.addAnimation(scale); + if (!mGridLayoutRecentsEnabled) { + // In the grid layout, the header should be shown for the whole animation. + set.addAnimation(alpha); + } + set.addAnimation(translate); + set.addAnimation(clipAnim); + a = set; + } else { + // Animation down from the full screen to the thumbnail + Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY); + scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); + scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); + Animation alpha = new AlphaAnimation(0f, 1f); + alpha.setInterpolator(mThumbnailFadeInInterpolator); + alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); + Animation translate = createCurvedMotion(toX, fromX, toY, fromY); + translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); + translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); + + // This AnimationSet uses the Interpolators assigned above. + AnimationSet set = new AnimationSet(false); + set.addAnimation(scale); + if (!mGridLayoutRecentsEnabled) { + // In the grid layout, the header should be shown for the whole animation. + set.addAnimation(alpha); + } + set.addAnimation(translate); + a = set; + + } + return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0, + null); + } + + /** + * Creates an overlay with a background color and a thumbnail for the cross profile apps + * animation. + */ + public HardwareBuffer createCrossProfileAppsThumbnail( + @DrawableRes int thumbnailDrawableRes, Rect frame) { + final int width = frame.width(); + final int height = frame.height(); + + final Picture picture = new Picture(); + final Canvas canvas = picture.beginRecording(width, height); + canvas.drawColor(Color.argb(0.6f, 0, 0, 0)); + final int thumbnailSize = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.cross_profile_apps_thumbnail_size); + final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes); + drawable.setBounds( + (width - thumbnailSize) / 2, + (height - thumbnailSize) / 2, + (width + thumbnailSize) / 2, + (height + thumbnailSize) / 2); + drawable.setTint(mContext.getColor(android.R.color.white)); + drawable.draw(canvas); + picture.endRecording(); + + return Bitmap.createBitmap(picture).getHardwareBuffer(); + } + /** * Prepares the specified animation with a standard duration, interpolator, etc. */ private Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, - int transit) { + @TransitionOldType int transit) { // Pick the desired duration. If this is an inter-activity transition, // it is the standard duration for that. Otherwise we use the longer // task transition duration. @@ -820,6 +1024,22 @@ public class TransitionAnimation { return anim; } + private static @TransitionOldType int getTransitCompatType(@TransitionType int transit, + int wallpaperTransit) { + if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { + return TRANSIT_OLD_WALLPAPER_INTRA_OPEN; + } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) { + return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE; + } else if (transit == TRANSIT_OPEN) { + return TRANSIT_OLD_ACTIVITY_OPEN; + } else if (transit == TRANSIT_CLOSE) { + return TRANSIT_OLD_ACTIVITY_CLOSE; + } + + // We only do some special handle for above type, so use type NONE for default behavior. + return TRANSIT_OLD_NONE; + } + /** * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that * the start rect is outside of the target rect, and there is a lot of movement going on. @@ -842,11 +1062,34 @@ public class TransitionAnimation { * (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION)); } + /** + * Return the current thumbnail transition state. + */ + private int getThumbnailTransitionState(boolean enter, boolean scaleUp) { + if (enter) { + if (scaleUp) { + return THUMBNAIL_TRANSITION_ENTER_SCALE_UP; + } else { + return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN; + } + } else { + if (scaleUp) { + return THUMBNAIL_TRANSITION_EXIT_SCALE_UP; + } else { + return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN; + } + } + } + /** * Prepares the specified animation with a standard duration, interpolator, etc. */ - private static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth, + public static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth, int appHeight, long duration, Interpolator interpolator) { + if (a == null) { + return null; + } + if (duration > 0) { a.setDuration(duration); } diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index ce3efd35ee48440cca49db54d0d40b723d8685fc..5ac493637822ab56fef255ba1e9fec996af82af1 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -80,6 +80,11 @@ public enum ProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM), WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), + WM_DEBUG_WINDOW_INSETS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + Consts.TAG_WM), + WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + Consts.TAG_WM), + WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java index 10224a4b9db6e647d9b269fc3c58868917c26b12..353c6c083d9dfbb1ac15da7194c3bc1f28fac8ff 100644 --- a/core/java/com/android/internal/protolog/ProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java @@ -28,7 +28,7 @@ import java.io.File; */ public class ProtoLogImpl extends BaseProtoLogImpl { private static final int BUFFER_CAPACITY = 1024 * 1024; - private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.pb"; + private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.winscope"; private static final String VIEWER_CONFIG_FILENAME = "/system/etc/protolog.conf.json.gz"; private static ProtoLogImpl sServiceInstance = null; diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index ed6415d749a39caea23d4b955362d518f6ea27bd..ad4f280b1e8d319bc101428a4e21bc856d94502e 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -25,6 +25,7 @@ import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.service.notification.StatusBarNotification; +import android.view.InsetsVisibilities; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.view.AppearanceRegion; @@ -182,7 +183,7 @@ oneway interface IStatusBar /** * Notifies System UI side of system bar attribute change on the specified display. * - * @param displayId the ID of the display to notify + * @param displayId the ID of the display to notify. * @param appearance the appearance of the focused window. The light top bar appearance is not * controlled here, but primaryAppearance and secondaryAppearance. * @param appearanceRegions a set of appearances which will be only applied in their own bounds. @@ -191,11 +192,12 @@ oneway interface IStatusBar * stacks. * @param navbarColorManagedByIme {@code true} if navigation bar color is managed by IME. * @param behavior the behavior of the focused window. - * @param isFullscreen whether any of status or navigation bar is requested invisible. + * @param requestedVisibilities the collection of the requested visibilities of system insets. + * @param packageName the package name of the focused app. */ void onSystemBarAttributesChanged(int displayId, int appearance, in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, - int behavior, boolean isFullscreen); + int behavior, in InsetsVisibilities requestedVisibilities, String packageName); /** * Notifies System UI to show transient bars. The transient bars are system bars, e.g., status @@ -203,8 +205,10 @@ oneway interface IStatusBar * * @param displayId the ID of the display to notify. * @param types the internal insets types of the bars are about to show transiently. + * @param isGestureOnSystemBar whether the gesture to show the transient bar was a gesture on + * one of the bars itself. */ - void showTransient(int displayId, in int[] types); + void showTransient(int displayId, in int[] types, boolean isGestureOnSystemBar); /** * Notifies System UI to abort the transient state of system bars, which prevents the bars being diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java index 8fb2f9cd8bf9df259067a6d8272231fabdfa3ed6..4dcc82e2e5726de0acd33a6750073af361fd00c0 100644 --- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java +++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java @@ -21,6 +21,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import android.view.InsetsVisibilities; import com.android.internal.view.AppearanceRegion; @@ -39,14 +40,15 @@ public final class RegisterStatusBarResult implements Parcelable { public final IBinder mImeToken; public final boolean mNavbarColorManagedByIme; public final int mBehavior; - public final boolean mAppFullscreen; + public final InsetsVisibilities mRequestedVisibilities; + public final String mPackageName; public final int[] mTransientBarTypes; public RegisterStatusBarResult(ArrayMap icons, int disabledFlags1, int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis, int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken, - boolean navbarColorManagedByIme, int behavior, boolean appFullscreen, - @NonNull int[] transientBarTypes) { + boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities, + String packageName, @NonNull int[] transientBarTypes) { mIcons = new ArrayMap<>(icons); mDisabledFlags1 = disabledFlags1; mAppearance = appearance; @@ -58,7 +60,8 @@ public final class RegisterStatusBarResult implements Parcelable { mImeToken = imeToken; mNavbarColorManagedByIme = navbarColorManagedByIme; mBehavior = behavior; - mAppFullscreen = appFullscreen; + mRequestedVisibilities = requestedVisibilities; + mPackageName = packageName; mTransientBarTypes = transientBarTypes; } @@ -80,7 +83,8 @@ public final class RegisterStatusBarResult implements Parcelable { dest.writeStrongBinder(mImeToken); dest.writeBoolean(mNavbarColorManagedByIme); dest.writeInt(mBehavior); - dest.writeBoolean(mAppFullscreen); + dest.writeTypedObject(mRequestedVisibilities, 0); + dest.writeString(mPackageName); dest.writeIntArray(mTransientBarTypes); } @@ -104,12 +108,14 @@ public final class RegisterStatusBarResult implements Parcelable { final IBinder imeToken = source.readStrongBinder(); final boolean navbarColorManagedByIme = source.readBoolean(); final int behavior = source.readInt(); - final boolean appFullscreen = source.readBoolean(); + final InsetsVisibilities requestedVisibilities = + source.readTypedObject(InsetsVisibilities.CREATOR); + final String packageName = source.readString(); final int[] transientBarTypes = source.createIntArray(); return new RegisterStatusBarResult(icons, disabledFlags1, appearance, appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher, disabledFlags2, imeToken, navbarColorManagedByIme, behavior, - appFullscreen, transientBarTypes); + requestedVisibilities, packageName, transientBarTypes); } @Override diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index f040462dafdcd7282016398ad4f27ccf2a3b90be..4c519f4c779f3096c45b40429ec9659ec70e7885 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -14,15 +14,20 @@ package com.android.internal.util; +import static android.os.Trace.TRACE_TAG_APP; + import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.os.Build; import android.os.SystemClock; import android.os.Trace; import android.provider.DeviceConfig; +import android.text.TextUtils; import android.util.EventLog; import android.util.Log; -import android.util.SparseLongArray; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.EventLogTags; @@ -31,6 +36,7 @@ import com.android.internal.os.BackgroundThread; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; /** * Class to track various latencies in SystemUI. It then writes the latency to statsd and also @@ -44,6 +50,7 @@ public class LatencyTracker { private static final String TAG = "LatencyTracker"; private static final String SETTINGS_ENABLED_KEY = "enabled"; private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; + private static final boolean DEBUG = false; /** Default to being enabled on debug builds. */ private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE; /** Default to collecting data for 1/5 of all actions (randomly sampled). */ @@ -110,6 +117,11 @@ public class LatencyTracker { */ public static final int ACTION_LOCKSCREEN_UNLOCK = 11; + /** + * Time it takes to switch users. + */ + public static final int ACTION_USER_SWITCH = 12; + private static final int[] ACTIONS_ALL = { ACTION_EXPAND_PANEL, ACTION_TOGGLE_RECENTS, @@ -122,7 +134,8 @@ public class LatencyTracker { ACTION_START_RECENTS_ANIMATION, ACTION_ROTATE_SCREEN_SENSOR, ACTION_ROTATE_SCREEN_CAMERA_CHECK, - ACTION_LOCKSCREEN_UNLOCK + ACTION_LOCKSCREEN_UNLOCK, + ACTION_USER_SWITCH }; /** @hide */ @@ -138,7 +151,8 @@ public class LatencyTracker { ACTION_START_RECENTS_ANIMATION, ACTION_ROTATE_SCREEN_SENSOR, ACTION_ROTATE_SCREEN_CAMERA_CHECK, - ACTION_LOCKSCREEN_UNLOCK + ACTION_LOCKSCREEN_UNLOCK, + ACTION_USER_SWITCH }) @Retention(RetentionPolicy.SOURCE) public @interface Action { @@ -156,13 +170,15 @@ public class LatencyTracker { FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION, FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR, FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK + FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK, + FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH }; private static LatencyTracker sLatencyTracker; private final Object mLock = new Object(); - private final SparseLongArray mStartRtc = new SparseLongArray(); + @GuardedBy("mLock") + private final SparseArray mSessions = new SparseArray<>(); @GuardedBy("mLock") private final int[] mTraceThresholdPerAction = new int[ACTIONS_ALL.length]; @GuardedBy("mLock") @@ -239,13 +255,19 @@ public class LatencyTracker { return "ACTION_ROTATE_SCREEN_SENSOR"; case 12: return "ACTION_LOCKSCREEN_UNLOCK"; + case 13: + return "ACTION_USER_SWITCH"; default: throw new IllegalArgumentException("Invalid action"); } } - private static String getTraceNameOfAction(@Action int action) { - return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">"; + private static String getTraceNameOfAction(@Action int action, String tag) { + if (TextUtils.isEmpty(tag)) { + return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">"; + } else { + return "L<" + getNameOfAction(STATSD_ACTION[action]) + "::" + tag + ">"; + } } private static String getTraceTriggerNameForAction(@Action int action) { @@ -263,35 +285,82 @@ public class LatencyTracker { } /** - * Notifies that an action is starting. This needs to be called from the main thread. + * Notifies that an action is starting. This needs to be called from the main thread. * * @param action The action to start. One of the ACTION_* values. */ public void onActionStart(@Action int action) { - if (!isEnabled()) { - return; + onActionStart(action, null); + } + + /** + * Notifies that an action is starting. This needs to be called from the main thread. + * + * @param action The action to start. One of the ACTION_* values. + * @param tag The brief description of the action. + */ + public void onActionStart(@Action int action, String tag) { + synchronized (mLock) { + if (!isEnabled()) { + return; + } + // skip if the action is already instrumenting. + if (mSessions.get(action) != null) { + return; + } + Session session = new Session(action, tag); + session.begin(() -> onActionCancel(action)); + mSessions.put(action, session); + + if (DEBUG) { + Log.d(TAG, "onActionStart: " + session.name() + ", start=" + session.mStartRtc); + } } - Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0); - mStartRtc.put(action, SystemClock.elapsedRealtime()); } /** - * Notifies that an action has ended. This needs to be called from the main thread. + * Notifies that an action has ended. This needs to be called from the main thread. * * @param action The action to end. One of the ACTION_* values. */ public void onActionEnd(@Action int action) { - if (!isEnabled()) { - return; + synchronized (mLock) { + if (!isEnabled()) { + return; + } + Session session = mSessions.get(action); + if (session == null) { + return; + } + session.end(); + mSessions.delete(action); + logAction(action, session.duration()); + + if (DEBUG) { + Log.d(TAG, "onActionEnd:" + session.name() + ", duration=" + session.duration()); + } } - long endRtc = SystemClock.elapsedRealtime(); - long startRtc = mStartRtc.get(action, -1); - if (startRtc == -1) { - return; + } + + /** + * Notifies that an action has canceled. This needs to be called from the main thread. + * + * @param action The action to cancel. One of the ACTION_* values. + * @hide + */ + public void onActionCancel(@Action int action) { + synchronized (mLock) { + Session session = mSessions.get(action); + if (session == null) { + return; + } + session.cancel(); + mSessions.delete(action); + + if (DEBUG) { + Log.d(TAG, "onActionCancel: " + session.name()); + } } - mStartRtc.delete(action); - Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0); - logAction(action, (int) (endRtc - startRtc)); } /** @@ -332,4 +401,57 @@ public class LatencyTracker { FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration); } } + + static class Session { + @Action + private final int mAction; + private final String mTag; + private final String mName; + private Runnable mTimeoutRunnable; + private long mStartRtc = -1; + private long mEndRtc = -1; + + Session(@Action int action, @Nullable String tag) { + mAction = action; + mTag = tag; + mName = TextUtils.isEmpty(mTag) + ? getNameOfAction(STATSD_ACTION[mAction]) + : getNameOfAction(STATSD_ACTION[mAction]) + "::" + mTag; + } + + String name() { + return mName; + } + + String traceName() { + return getTraceNameOfAction(mAction, mTag); + } + + void begin(@NonNull Runnable timeoutAction) { + mStartRtc = SystemClock.elapsedRealtime(); + Trace.asyncTraceBegin(TRACE_TAG_APP, traceName(), 0); + + // start counting timeout. + mTimeoutRunnable = timeoutAction; + BackgroundThread.getHandler() + .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(15)); + } + + void end() { + mEndRtc = SystemClock.elapsedRealtime(); + Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0); + BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable); + mTimeoutRunnable = null; + } + + void cancel() { + Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0); + BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable); + mTimeoutRunnable = null; + } + + int duration() { + return (int) (mEndRtc - mStartRtc); + } + } } diff --git a/core/java/com/android/internal/util/function/pooled/OWNERS b/core/java/com/android/internal/util/function/pooled/OWNERS index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..da723b3b67dabe948987711a199395048b32206c 100644 --- a/core/java/com/android/internal/util/function/pooled/OWNERS +++ b/core/java/com/android/internal/util/function/pooled/OWNERS @@ -0,0 +1 @@ +eugenesusla@google.com \ No newline at end of file diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 8d82e33dc29fffe8c53bdd5f12c4a2ba2773a9d7..5354afbd667b9e2c014c706f599124b2bfe5cd3e 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -35,7 +35,7 @@ import com.android.internal.view.InlineSuggestionsRequestInfo; * {@hide} */ oneway interface IInputMethod { - void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps, + void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, int configChanges); void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo, diff --git a/core/java/com/android/internal/view/ScrollCaptureInternal.java b/core/java/com/android/internal/view/ScrollCaptureInternal.java index e3a9fda7b000fa6ce4f005594768e1d099424da1..72b5488f4bac411bb5425dcc792ea256eff5b8d4 100644 --- a/core/java/com/android/internal/view/ScrollCaptureInternal.java +++ b/core/java/com/android/internal/view/ScrollCaptureInternal.java @@ -25,6 +25,7 @@ import android.util.Log; import android.view.ScrollCaptureCallback; import android.view.View; import android.view.ViewGroup; +import android.webkit.WebView; import android.widget.ListView; /** @@ -43,7 +44,7 @@ public class ScrollCaptureInternal { private static final int DOWN = 1; /** - * Not a ViewGroup, or cannot scroll according to View APIs. + * Cannot scroll according to {@link View#canScrollVertically}. */ public static final int TYPE_FIXED = 0; @@ -60,7 +61,7 @@ public class ScrollCaptureInternal { public static final int TYPE_RECYCLING = 2; /** - * The ViewGroup scrolls, but has no child views in + * Unknown scrollable view with no child views (or not a subclass of ViewGroup). */ private static final int TYPE_OPAQUE = 3; @@ -73,16 +74,6 @@ public class ScrollCaptureInternal { * as excluded during scroll capture search. */ private static int detectScrollingType(View view) { - // Must be a ViewGroup - if (!(view instanceof ViewGroup)) { - if (DEBUG_VERBOSE) { - Log.v(TAG, "hint: not a subclass of ViewGroup"); - } - return TYPE_FIXED; - } - if (DEBUG_VERBOSE) { - Log.v(TAG, "hint: is a subclass of ViewGroup"); - } // Confirm that it can scroll. if (!(view.canScrollVertically(DOWN) || view.canScrollVertically(UP))) { // Nothing to scroll here, move along. @@ -94,6 +85,17 @@ public class ScrollCaptureInternal { if (DEBUG_VERBOSE) { Log.v(TAG, "hint: can be scrolled up or down"); } + // Must be a ViewGroup + if (!(view instanceof ViewGroup)) { + if (DEBUG_VERBOSE) { + Log.v(TAG, "hint: not a subclass of ViewGroup"); + } + return TYPE_OPAQUE; + } + if (DEBUG_VERBOSE) { + Log.v(TAG, "hint: is a subclass of ViewGroup"); + } + // ScrollViews accept only a single child. if (((ViewGroup) view).getChildCount() > 1) { if (DEBUG_VERBOSE) { @@ -188,6 +190,18 @@ public class ScrollCaptureInternal { } return new ScrollCaptureViewSupport<>((ViewGroup) view, new RecyclerViewCaptureHelper()); + case TYPE_OPAQUE: + if (DEBUG) { + Log.d(TAG, "scroll capture: FOUND " + view.getClass().getName() + + "[" + resolveId(view.getContext(), view.getId()) + "]" + + " -> TYPE_OPAQUE"); + } + if (view instanceof WebView) { + Log.d(TAG, "scroll capture: Using WebView support"); + return new ScrollCaptureViewSupport<>((WebView) view, + new WebViewCaptureHelper()); + } + break; case TYPE_FIXED: // ignore break; diff --git a/core/java/com/android/internal/view/WebViewCaptureHelper.java b/core/java/com/android/internal/view/WebViewCaptureHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..e6a311cfcda5c3e6e50a6a57a4d37de6735dc63b --- /dev/null +++ b/core/java/com/android/internal/view/WebViewCaptureHelper.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view; + +import static android.util.MathUtils.constrain; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +import android.annotation.NonNull; +import android.graphics.Rect; +import android.webkit.WebView; + +/** + * ScrollCapture for WebView. + */ +class WebViewCaptureHelper implements ScrollCaptureViewHelper { + private static final String TAG = "WebViewScrollCapture"; + + private final Rect mRequestWebViewLocal = new Rect(); + private final Rect mWebViewBounds = new Rect(); + + private int mOriginScrollY; + private int mOriginScrollX; + + @Override + public boolean onAcceptSession(@NonNull WebView view) { + return view.isVisibleToUser() + && (view.getContentHeight() * view.getScale()) > view.getHeight(); + } + + @Override + public void onPrepareForStart(@NonNull WebView view, @NonNull Rect scrollBounds) { + mOriginScrollX = view.getScrollX(); + mOriginScrollY = view.getScrollY(); + } + + @NonNull + @Override + public ScrollResult onScrollRequested(@NonNull WebView view, @NonNull Rect scrollBounds, + @NonNull Rect requestRect) { + + int scrollDelta = view.getScrollY() - mOriginScrollY; + + ScrollResult result = new ScrollResult(); + result.requestedArea = new Rect(requestRect); + result.availableArea = new Rect(); + result.scrollDelta = scrollDelta; + + mWebViewBounds.set(0, 0, view.getWidth(), view.getHeight()); + + if (!view.isVisibleToUser()) { + return result; + } + + // Map the request into local coordinates + mRequestWebViewLocal.set(requestRect); + mRequestWebViewLocal.offset(0, -scrollDelta); + + // Offset to center the rect vertically, clamp to available content + int upLimit = min(0, -view.getScrollY()); + int contentHeightPx = (int) (view.getContentHeight() * view.getScale()); + int downLimit = max(0, (contentHeightPx - view.getHeight()) - view.getScrollY()); + int scrollToCenter = mRequestWebViewLocal.centerY() - mWebViewBounds.centerY(); + int scrollMovement = constrain(scrollToCenter, upLimit, downLimit); + + // Scroll and update relative based on the new position + view.scrollBy(mOriginScrollX, scrollMovement); + scrollDelta = view.getScrollY() - mOriginScrollY; + mRequestWebViewLocal.offset(0, -scrollMovement); + result.scrollDelta = scrollDelta; + + if (mRequestWebViewLocal.intersect(mWebViewBounds)) { + result.availableArea = new Rect(mRequestWebViewLocal); + result.availableArea.offset(0, result.scrollDelta); + } + return result; + } + + @Override + public void onPrepareForEnd(@NonNull WebView view) { + view.scrollTo(mOriginScrollX, mOriginScrollY); + } + +} + diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index fd6038fd655d4720a501c58cd4bc4bc4388b4689..4fc135c515b0ea1a56a6fae744095242c67f0a01 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -19,6 +19,7 @@ package com.android.internal.widget; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Insets; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; import android.graphics.Path; @@ -136,6 +137,7 @@ public class PointerLocationView extends View implements InputDeviceListener, private final FontMetricsInt mTextMetrics = new FontMetricsInt(); private int mHeaderBottom; private int mHeaderPaddingTop = 0; + private Insets mWaterfallInsets = Insets.NONE; @UnsupportedAppUsage private boolean mCurDown; @UnsupportedAppUsage @@ -229,8 +231,10 @@ public class PointerLocationView extends View implements InputDeviceListener, public WindowInsets onApplyWindowInsets(WindowInsets insets) { if (insets.getDisplayCutout() != null) { mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop(); + mWaterfallInsets = insets.getDisplayCutout().getWaterfallInsets(); } else { mHeaderPaddingTop = 0; + mWaterfallInsets = Insets.NONE; } return super.onApplyWindowInsets(insets); } @@ -266,11 +270,6 @@ public class PointerLocationView extends View implements InputDeviceListener, @Override protected void onDraw(Canvas canvas) { - final int w = getWidth(); - final int itemW = w/7; - final int base = mHeaderPaddingTop-mTextMetrics.ascent+1; - final int bottom = mHeaderBottom; - final int NP = mPointers.size(); if (!mSystemGestureExclusion.isEmpty()) { @@ -286,71 +285,7 @@ public class PointerLocationView extends View implements InputDeviceListener, } // Labels - if (mActivePointerId >= 0) { - final PointerState ps = mPointers.get(mActivePointerId); - - canvas.drawRect(0, mHeaderPaddingTop, itemW-1, bottom,mTextBackgroundPaint); - canvas.drawText(mText.clear() - .append("P: ").append(mCurNumPointers) - .append(" / ").append(mMaxNumPointers) - .toString(), 1, base, mTextPaint); - - final int N = ps.mTraceCount; - if ((mCurDown && ps.mCurDown) || N == 0) { - canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom, - mTextBackgroundPaint); - canvas.drawText(mText.clear() - .append("X: ").append(ps.mCoords.x, 1) - .toString(), 1 + itemW, base, mTextPaint); - canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom, - mTextBackgroundPaint); - canvas.drawText(mText.clear() - .append("Y: ").append(ps.mCoords.y, 1) - .toString(), 1 + itemW * 2, base, mTextPaint); - } else { - float dx = ps.mTraceX[N - 1] - ps.mTraceX[0]; - float dy = ps.mTraceY[N - 1] - ps.mTraceY[0]; - canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom, - Math.abs(dx) < mVC.getScaledTouchSlop() - ? mTextBackgroundPaint : mTextLevelPaint); - canvas.drawText(mText.clear() - .append("dX: ").append(dx, 1) - .toString(), 1 + itemW, base, mTextPaint); - canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom, - Math.abs(dy) < mVC.getScaledTouchSlop() - ? mTextBackgroundPaint : mTextLevelPaint); - canvas.drawText(mText.clear() - .append("dY: ").append(dy, 1) - .toString(), 1 + itemW * 2, base, mTextPaint); - } - - canvas.drawRect(itemW * 3, mHeaderPaddingTop, (itemW * 4) - 1, bottom, - mTextBackgroundPaint); - canvas.drawText(mText.clear() - .append("Xv: ").append(ps.mXVelocity, 3) - .toString(), 1 + itemW * 3, base, mTextPaint); - - canvas.drawRect(itemW * 4, mHeaderPaddingTop, (itemW * 5) - 1, bottom, - mTextBackgroundPaint); - canvas.drawText(mText.clear() - .append("Yv: ").append(ps.mYVelocity, 3) - .toString(), 1 + itemW * 4, base, mTextPaint); - - canvas.drawRect(itemW * 5, mHeaderPaddingTop, (itemW * 6) - 1, bottom, - mTextBackgroundPaint); - canvas.drawRect(itemW * 5, mHeaderPaddingTop, - (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, bottom, mTextLevelPaint); - canvas.drawText(mText.clear() - .append("Prs: ").append(ps.mCoords.pressure, 2) - .toString(), 1 + itemW * 5, base, mTextPaint); - - canvas.drawRect(itemW * 6, mHeaderPaddingTop, w, bottom, mTextBackgroundPaint); - canvas.drawRect(itemW * 6, mHeaderPaddingTop, - (itemW * 6) + (ps.mCoords.size * itemW) - 1, bottom, mTextLevelPaint); - canvas.drawText(mText.clear() - .append("Size: ").append(ps.mCoords.size, 2) - .toString(), 1 + itemW * 6, base, mTextPaint); - } + drawLabels(canvas); // Pointer trace. for (int p = 0; p < NP; p++) { @@ -463,6 +398,84 @@ public class PointerLocationView extends View implements InputDeviceListener, } } + private void drawLabels(Canvas canvas) { + if (mActivePointerId < 0) { + return; + } + + final int w = getWidth() - mWaterfallInsets.left - mWaterfallInsets.right; + final int itemW = w / 7; + final int base = mHeaderPaddingTop - mTextMetrics.ascent + 1; + final int bottom = mHeaderBottom; + + canvas.save(); + canvas.translate(mWaterfallInsets.left, 0); + final PointerState ps = mPointers.get(mActivePointerId); + + canvas.drawRect(0, mHeaderPaddingTop, itemW - 1, bottom, mTextBackgroundPaint); + canvas.drawText(mText.clear() + .append("P: ").append(mCurNumPointers) + .append(" / ").append(mMaxNumPointers) + .toString(), 1, base, mTextPaint); + + final int count = ps.mTraceCount; + if ((mCurDown && ps.mCurDown) || count == 0) { + canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom, + mTextBackgroundPaint); + canvas.drawText(mText.clear() + .append("X: ").append(ps.mCoords.x, 1) + .toString(), 1 + itemW, base, mTextPaint); + canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom, + mTextBackgroundPaint); + canvas.drawText(mText.clear() + .append("Y: ").append(ps.mCoords.y, 1) + .toString(), 1 + itemW * 2, base, mTextPaint); + } else { + float dx = ps.mTraceX[count - 1] - ps.mTraceX[0]; + float dy = ps.mTraceY[count - 1] - ps.mTraceY[0]; + canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom, + Math.abs(dx) < mVC.getScaledTouchSlop() + ? mTextBackgroundPaint : mTextLevelPaint); + canvas.drawText(mText.clear() + .append("dX: ").append(dx, 1) + .toString(), 1 + itemW, base, mTextPaint); + canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom, + Math.abs(dy) < mVC.getScaledTouchSlop() + ? mTextBackgroundPaint : mTextLevelPaint); + canvas.drawText(mText.clear() + .append("dY: ").append(dy, 1) + .toString(), 1 + itemW * 2, base, mTextPaint); + } + + canvas.drawRect(itemW * 3, mHeaderPaddingTop, (itemW * 4) - 1, bottom, + mTextBackgroundPaint); + canvas.drawText(mText.clear() + .append("Xv: ").append(ps.mXVelocity, 3) + .toString(), 1 + itemW * 3, base, mTextPaint); + + canvas.drawRect(itemW * 4, mHeaderPaddingTop, (itemW * 5) - 1, bottom, + mTextBackgroundPaint); + canvas.drawText(mText.clear() + .append("Yv: ").append(ps.mYVelocity, 3) + .toString(), 1 + itemW * 4, base, mTextPaint); + + canvas.drawRect(itemW * 5, mHeaderPaddingTop, (itemW * 6) - 1, bottom, + mTextBackgroundPaint); + canvas.drawRect(itemW * 5, mHeaderPaddingTop, + (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, bottom, mTextLevelPaint); + canvas.drawText(mText.clear() + .append("Prs: ").append(ps.mCoords.pressure, 2) + .toString(), 1 + itemW * 5, base, mTextPaint); + + canvas.drawRect(itemW * 6, mHeaderPaddingTop, w, bottom, mTextBackgroundPaint); + canvas.drawRect(itemW * 6, mHeaderPaddingTop, + (itemW * 6) + (ps.mCoords.size * itemW) - 1, bottom, mTextLevelPaint); + canvas.drawText(mText.clear() + .append("Size: ").append(ps.mCoords.size, 2) + .toString(), 1 + itemW * 6, base, mTextPaint); + canvas.restore(); + } + private void logMotionEvent(String type, MotionEvent event) { final int action = event.getAction(); final int N = event.getHistorySize(); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 1d181dd9f434b04d9c75da82156446afad936427..96e4d186ab7e13b0b039b614d392fed1243d5474 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -223,6 +223,7 @@ cc_library_shared { "fd_utils.cpp", "android_hardware_input_InputWindowHandle.cpp", "android_hardware_input_InputApplicationHandle.cpp", + "android_window_WindowInfosListener.cpp", ], static_libs: [ @@ -230,15 +231,19 @@ cc_library_shared { "libbinderthreadstateutils", "libdmabufinfo", "libgif", + "libgui_window_info_static", "libseccomp_policy", "libgrallocusage", "libscrypt_static", "libstatssocket_lazy", + "libskia", ], shared_libs: [ "audioclient-types-aidl-cpp", "audioflinger-aidl-cpp", + "audiopolicy-types-aidl-cpp", + "spatializer-aidl-cpp", "av-types-aidl-cpp", "android.hardware.camera.device@3.2", "libandroid_net", @@ -369,6 +374,7 @@ cc_library_shared { "libinput", "libbinderthreadstateutils", "libsqlite", + "libgui_window_info_static", ], shared_libs: [ // libbinder needs to be shared since it has global state diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 1c4107a12b747e097ac443fd0f4bedc2982efab2..194002e9352fcdeb85c9c0e2ee2be715fcabfa04 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -207,6 +207,7 @@ extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_security_VerityUtils(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); +extern int register_android_window_WindowInfosListener(JNIEnv* env); // Namespace for Android Runtime flags applied during boot time. static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot"; @@ -1652,6 +1653,8 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader), REG_JNI(register_com_android_internal_os_KernelSingleProcessCpuThreadReader), REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader), + + REG_JNI(register_android_window_WindowInfosListener), }; /* diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 3a9957bcffeefb142df47d4cb31547f6d8a19781..8cac2e8c2b24140d92359affe44cc7d626ddbf38 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -1,9 +1,9 @@ # Camera per-file *Camera*,*camera* = cychen@google.com, epeev@google.com, etalvala@google.com -per-file *Camera*,*camera* = shuzhenwang@google.com, zhijunhe@google.com +per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhijunhe@google.com # Connectivity -per-file android_net_* = jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com +per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com # CPU per-file *Cpu* = file:/core/java/com/android/internal/os/CPU_OWNERS @@ -22,6 +22,7 @@ per-file android_view_PointerIcon.* = file:/services/core/java/com/android/serve # WindowManager per-file android_graphics_BLASTBufferQueue.cpp = file:/services/core/java/com/android/server/wm/OWNERS per-file android_view_Surface* = file:/services/core/java/com/android/server/wm/OWNERS +per-file android_window_WindowInfosListener.cpp = file:/services/core/java/com/android/server/wm/OWNERS # Resources per-file android_content_res_* = file:/core/java/android/content/res/OWNERS diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index d4ae6d769cf70d4baffc3cd8df992a7edd62759a..1382a99c467398eed221adfb43a164c36b290350 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -67,21 +67,19 @@ private: } }; -static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl, - jlong width, jlong height, jint format) { - String8 str8; - if (jName) { - const jchar* str16 = env->GetStringCritical(jName, nullptr); - if (str16) { - str8 = String8(reinterpret_cast(str16), env->GetStringLength(jName)); - env->ReleaseStringCritical(jName, str16); - str16 = nullptr; - } - } - std::string name = str8.string(); +static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName) { + ScopedUtfChars name(env, jName); + sp queue = new BLASTBufferQueue(name.c_str()); + queue->incStrong((void*)nativeCreate); + return reinterpret_cast(queue.get()); +} + +static jlong nativeCreateAndUpdate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl, + jlong width, jlong height, jint format) { + ScopedUtfChars name(env, jName); sp queue = - new BLASTBufferQueue(name, reinterpret_cast(surfaceControl), width, - height, format); + new BLASTBufferQueue(name.c_str(), reinterpret_cast(surfaceControl), + width, height, format); queue->incStrong((void*)nativeCreate); return reinterpret_cast(queue.get()); } @@ -112,11 +110,6 @@ static void nativeUpdate(JNIEnv* env, jclass clazz, jlong ptr, jlong surfaceCont transaction); } -static void nativeFlushShadowQueue(JNIEnv* env, jclass clazz, jlong ptr) { - sp queue = reinterpret_cast(ptr); - queue->flushShadowQueue(); -} - static void nativeMergeWithNextTransaction(JNIEnv*, jclass clazz, jlong ptr, jlong transactionPtr, jlong framenumber) { sp queue = reinterpret_cast(ptr); @@ -139,19 +132,25 @@ static void nativeSetTransactionCompleteCallback(JNIEnv* env, jclass clazz, jlon } } +static jlong nativeGetLastAcquiredFrameNum(JNIEnv* env, jclass clazz, jlong ptr) { + sp queue = reinterpret_cast(ptr); + return queue->getLastAcquiredFrameNum(); +} + static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ // clang-format off - {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate}, + {"nativeCreate", "(Ljava/lang/String;)J", (void*)nativeCreate}, + {"nativeCreateAndUpdate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreateAndUpdate}, {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface}, {"nativeDestroy", "(J)V", (void*)nativeDestroy}, {"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction}, {"nativeUpdate", "(JJJJIJ)V", (void*)nativeUpdate}, - {"nativeFlushShadowQueue", "(J)V", (void*)nativeFlushShadowQueue}, {"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction}, {"nativeSetTransactionCompleteCallback", "(JJLandroid/graphics/BLASTBufferQueue$TransactionCompleteCallback;)V", - (void*)nativeSetTransactionCompleteCallback} + (void*)nativeSetTransactionCompleteCallback}, + {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum}, // clang-format on }; diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp index 995bfa97ab2e8b50704b081bce9f7c881d5bb357..24d35316ef2087b65ef5922dced38048ff7b1a1a 100644 --- a/core/jni/android_hardware_input_InputApplicationHandle.cpp +++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp @@ -28,6 +28,8 @@ namespace android { static struct { + jclass clazz; + jmethodID ctor; jfieldID ptr; jfieldID name; jfieldID dispatchingTimeoutMillis; @@ -101,6 +103,15 @@ std::shared_ptr android_view_InputApplicationHandle_getH return *handle; } +jobject android_view_InputApplicationHandle_fromInputApplicationInfo( + JNIEnv* env, gui::InputApplicationInfo inputApplicationInfo) { + jobject binderObject = javaObjectForIBinder(env, inputApplicationInfo.token); + ScopedLocalRef name(env, env->NewStringUTF(inputApplicationInfo.name.data())); + return env->NewObject(gInputApplicationHandleClassInfo.clazz, + gInputApplicationHandleClassInfo.ctor, binderObject, name.get(), + inputApplicationInfo.dispatchingTimeoutMillis); +} + // --- JNI --- static void android_view_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) { @@ -131,6 +142,10 @@ static const JNINativeMethod gInputApplicationHandleMethods[] = { var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! (var), "Unable to find field " fieldName); +#define GET_METHOD_ID(var, clazz, methodName, methodSignature) \ + var = env->GetMethodID(clazz, methodName, methodSignature); \ + LOG_ALWAYS_FATAL_IF(!(var), "Unable to find method " methodName); + int register_android_view_InputApplicationHandle(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "android/view/InputApplicationHandle", gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods)); @@ -139,6 +154,10 @@ int register_android_view_InputApplicationHandle(JNIEnv* env) { jclass clazz; FIND_CLASS(clazz, "android/view/InputApplicationHandle"); + gInputApplicationHandleClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + + GET_METHOD_ID(gInputApplicationHandleClassInfo.ctor, clazz, "", + "(Landroid/os/IBinder;Ljava/lang/String;J)V"); GET_FIELD_ID(gInputApplicationHandleClassInfo.ptr, clazz, "ptr", "J"); diff --git a/core/jni/android_hardware_input_InputApplicationHandle.h b/core/jni/android_hardware_input_InputApplicationHandle.h index ec99d6da5b8e283c6bf202f00e7401a115d8aa3c..5d88d8e2516078fb218cc4f72159876806145e78 100644 --- a/core/jni/android_hardware_input_InputApplicationHandle.h +++ b/core/jni/android_hardware_input_InputApplicationHandle.h @@ -19,7 +19,7 @@ #include -#include +#include #include #include "jni.h" @@ -42,6 +42,9 @@ private: extern std::shared_ptr android_view_InputApplicationHandle_getHandle( JNIEnv* env, jobject inputApplicationHandleObj); +extern jobject android_view_InputApplicationHandle_fromInputApplicationInfo( + JNIEnv* env, gui::InputApplicationInfo inputApplicationInfo); + } // namespace android #endif // _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp index 463d909821b144746de668ff17efd23e7aff4753..e4ef7d39d77ca60f7ebcde53f518bbc2dbadc50c 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -26,14 +26,19 @@ #include #include +#include +#include +#include "SkRegion.h" #include "android_hardware_input_InputApplicationHandle.h" #include "android_util_Binder.h" #include "core_jni_helpers.h" -#include "input/InputWindow.h" #include "jni.h" namespace android { +using gui::TouchOcclusionMode; +using gui::WindowInfo; + struct WeakRefHandleField { jfieldID ctrl; jmethodID get; @@ -41,6 +46,8 @@ struct WeakRefHandleField { }; static struct { + jclass clazz; + jmethodID ctor; jfieldID ptr; jfieldID inputApplicationHandle; jfieldID token; @@ -66,11 +73,18 @@ static struct { jfieldID packageName; jfieldID inputFeatures; jfieldID displayId; - jfieldID portalToDisplayId; jfieldID replaceTouchableRegionWithCrop; WeakRefHandleField touchableRegionSurfaceControl; + jfieldID transform; + jfieldID windowToken; } gInputWindowHandleClassInfo; +static struct { + jclass clazz; + jmethodID ctor; + jfieldID nativeRegion; +} gRegionClassInfo; + static Mutex gHandleMutex; @@ -115,9 +129,9 @@ bool NativeInputWindowHandle::updateInfo() { mInfo.name = getStringField(env, obj, gInputWindowHandleClassInfo.name, ""); - mInfo.flags = Flags( + mInfo.flags = Flags( env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsFlags)); - mInfo.type = static_cast( + mInfo.type = static_cast( env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsType)); mInfo.dispatchingTimeout = std::chrono::milliseconds( env->GetLongField(obj, gInputWindowHandleClassInfo.dispatchingTimeoutMillis)); @@ -159,12 +173,10 @@ bool NativeInputWindowHandle::updateInfo() { mInfo.ownerUid = env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid); mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, ""); - mInfo.inputFeatures = static_cast( + mInfo.inputFeatures = static_cast( env->GetIntField(obj, gInputWindowHandleClassInfo.inputFeatures)); mInfo.displayId = env->GetIntField(obj, gInputWindowHandleClassInfo.displayId); - mInfo.portalToDisplayId = env->GetIntField(obj, - gInputWindowHandleClassInfo.portalToDisplayId); jobject inputApplicationHandleObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.inputApplicationHandle); @@ -204,6 +216,14 @@ bool NativeInputWindowHandle::updateInfo() { mInfo.touchableRegionCropHandle.clear(); } + jobject windowTokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.windowToken); + if (windowTokenObj) { + mInfo.windowToken = ibinderForJavaObject(env, windowTokenObj); + env->DeleteLocalRef(windowTokenObj); + } else { + mInfo.windowToken.clear(); + } + env->DeleteLocalRef(obj); return true; } @@ -233,6 +253,81 @@ sp android_view_InputWindowHandle_getHandle( return handle; } +jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env, gui::WindowInfo windowInfo) { + ScopedLocalRef + applicationHandle(env, + android_view_InputApplicationHandle_fromInputApplicationInfo( + env, windowInfo.applicationInfo)); + + jobject inputWindowHandle = + env->NewObject(gInputWindowHandleClassInfo.clazz, gInputWindowHandleClassInfo.ctor, + applicationHandle.get(), windowInfo.displayId); + env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.token, + javaObjectForIBinder(env, windowInfo.token)); + env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.name, + env->NewStringUTF(windowInfo.name.data())); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.layoutParamsFlags, + static_cast(windowInfo.flags.get())); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.layoutParamsType, + static_cast(windowInfo.type)); + env->SetLongField(inputWindowHandle, gInputWindowHandleClassInfo.dispatchingTimeoutMillis, + std::chrono::duration_cast( + windowInfo.dispatchingTimeout) + .count()); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.frameLeft, + windowInfo.frameLeft); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.frameTop, windowInfo.frameTop); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.frameRight, + windowInfo.frameRight); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.frameBottom, + windowInfo.frameBottom); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.surfaceInset, + windowInfo.surfaceInset); + env->SetFloatField(inputWindowHandle, gInputWindowHandleClassInfo.scaleFactor, + windowInfo.globalScaleFactor); + + SkRegion* region = new SkRegion(); + for (const auto& r : windowInfo.touchableRegion) { + region->op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op); + } + ScopedLocalRef regionObj(env, + env->NewObject(gRegionClassInfo.clazz, + gRegionClassInfo.ctor)); + env->SetLongField(regionObj.get(), gRegionClassInfo.nativeRegion, + reinterpret_cast(region)); + env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.touchableRegion, + regionObj.get()); + + env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.visible, + windowInfo.visible); + env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.focusable, + windowInfo.focusable); + env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.hasWallpaper, + windowInfo.hasWallpaper); + env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.paused, windowInfo.paused); + env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.trustedOverlay, + windowInfo.trustedOverlay); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.touchOcclusionMode, + static_cast(windowInfo.touchOcclusionMode)); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerPid, windowInfo.ownerPid); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerUid, windowInfo.ownerUid); + env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.packageName, + env->NewStringUTF(windowInfo.packageName.data())); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.inputFeatures, + static_cast(windowInfo.inputFeatures.get())); + + float transformVals[9]; + for (int i = 0; i < 9; i++) { + transformVals[i] = windowInfo.transform[i % 3][i / 3]; + } + ScopedLocalRef matrixObj(env, AMatrix_newInstance(env, transformVals)); + env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.transform, matrixObj.get()); + + env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.windowToken, + javaObjectForIBinder(env, windowInfo.windowToken)); + + return inputWindowHandle; +} // --- JNI --- @@ -275,6 +370,10 @@ int register_android_view_InputWindowHandle(JNIEnv* env) { jclass clazz; FIND_CLASS(clazz, "android/view/InputWindowHandle"); + gInputWindowHandleClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + + GET_METHOD_ID(gInputWindowHandleClassInfo.ctor, clazz, "", + "(Landroid/view/InputApplicationHandle;I)V"); GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, clazz, "ptr", "J"); @@ -348,12 +447,15 @@ int register_android_view_InputWindowHandle(JNIEnv* env) { GET_FIELD_ID(gInputWindowHandleClassInfo.displayId, clazz, "displayId", "I"); - GET_FIELD_ID(gInputWindowHandleClassInfo.portalToDisplayId, clazz, - "portalToDisplayId", "I"); - GET_FIELD_ID(gInputWindowHandleClassInfo.replaceTouchableRegionWithCrop, clazz, "replaceTouchableRegionWithCrop", "Z"); + GET_FIELD_ID(gInputWindowHandleClassInfo.transform, clazz, "transform", + "Landroid/graphics/Matrix;"); + + GET_FIELD_ID(gInputWindowHandleClassInfo.windowToken, clazz, "windowToken", + "Landroid/os/IBinder;"); + jclass weakRefClazz; FIND_CLASS(weakRefClazz, "java/lang/ref/Reference"); @@ -368,6 +470,11 @@ int register_android_view_InputWindowHandle(JNIEnv* env) { GET_FIELD_ID(gInputWindowHandleClassInfo.touchableRegionSurfaceControl.mNativeObject, surfaceControlClazz, "mNativeObject", "J"); + jclass regionClazz; + FIND_CLASS(regionClazz, "android/graphics/Region"); + gRegionClassInfo.clazz = MakeGlobalRefOrDie(env, regionClazz); + GET_METHOD_ID(gRegionClassInfo.ctor, gRegionClassInfo.clazz, "", "()V"); + GET_FIELD_ID(gRegionClassInfo.nativeRegion, gRegionClassInfo.clazz, "mNativeRegion", "J"); return 0; } diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h index de5bd6ef97f4fbb4ef0055a2cc0c30f72a4e8707..408e0f1bfa367bc73150e63598d36ce64292d50e 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.h +++ b/core/jni/android_hardware_input_InputWindowHandle.h @@ -17,14 +17,14 @@ #ifndef _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H #define _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H -#include +#include #include #include "jni.h" namespace android { -class NativeInputWindowHandle : public InputWindowHandle { +class NativeInputWindowHandle : public gui::WindowInfoHandle { public: NativeInputWindowHandle(jweak objWeak); virtual ~NativeInputWindowHandle(); @@ -37,10 +37,12 @@ private: jweak mObjWeak; }; - extern sp android_view_InputWindowHandle_getHandle( JNIEnv* env, jobject inputWindowHandleObj); +extern jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env, + gui::WindowInfo windowInfo); + } // namespace android #endif // _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H diff --git a/core/jni/android_media_AudioDeviceAttributes.cpp b/core/jni/android_media_AudioDeviceAttributes.cpp index 2a16dce99125dc02d231011f05e4d57acf3ba3b6..6879a6008f5a9b571b4e9bb9cd4841ada22f8d2d 100644 --- a/core/jni/android_media_AudioDeviceAttributes.cpp +++ b/core/jni/android_media_AudioDeviceAttributes.cpp @@ -24,6 +24,11 @@ using namespace android; static jclass gAudioDeviceAttributesClass; static jmethodID gAudioDeviceAttributesCstor; +static struct { + jfieldID mAddress; + jfieldID mNativeType; + // other fields unused by JNI +} gAudioDeviceAttributesFields; namespace android { @@ -33,12 +38,25 @@ jint createAudioDeviceAttributesFromNative(JNIEnv *env, jobject *jAudioDeviceAtt jint jNativeType = (jint)devTypeAddr->mType; ScopedLocalRef jAddress(env, env->NewStringUTF(devTypeAddr->getAddress())); - *jAudioDeviceAttributes = env->NewObject(gAudioDeviceAttributesClass, gAudioDeviceAttributesCstor, - jNativeType, jAddress.get()); + *jAudioDeviceAttributes = + env->NewObject(gAudioDeviceAttributesClass, gAudioDeviceAttributesCstor, + jNativeType, jAddress.get()); return jStatus; } +jint createAudioDeviceTypeAddrFromJava(JNIEnv *env, AudioDeviceTypeAddr *devTypeAddr, + const jobject jAudioDeviceAttributes) { + devTypeAddr->mType = (audio_devices_t)env->GetIntField(jAudioDeviceAttributes, + gAudioDeviceAttributesFields.mNativeType); + + jstring jAddress = (jstring)env->GetObjectField(jAudioDeviceAttributes, + gAudioDeviceAttributesFields.mAddress); + devTypeAddr->setAddress(ScopedUtfChars(env, jAddress).c_str()); + + return AUDIO_JAVA_SUCCESS; +} + } // namespace android int register_android_media_AudioDeviceAttributes(JNIEnv *env) { @@ -48,5 +66,10 @@ int register_android_media_AudioDeviceAttributes(JNIEnv *env) { gAudioDeviceAttributesCstor = GetMethodIDOrDie(env, audioDeviceTypeAddressClass, "", "(ILjava/lang/String;)V"); + gAudioDeviceAttributesFields.mNativeType = + GetFieldIDOrDie(env, gAudioDeviceAttributesClass, "mNativeType", "I"); + gAudioDeviceAttributesFields.mAddress = + GetFieldIDOrDie(env, gAudioDeviceAttributesClass, "mAddress", "Ljava/lang/String;"); + return 0; } diff --git a/core/jni/android_media_AudioDeviceAttributes.h b/core/jni/android_media_AudioDeviceAttributes.h index b49d9ba515b84b5bc4e4914aada30a51f88dc5ac..4a1f40d9bb7c769a36c2d3303cf62c203df07295 100644 --- a/core/jni/android_media_AudioDeviceAttributes.h +++ b/core/jni/android_media_AudioDeviceAttributes.h @@ -28,6 +28,9 @@ namespace android { extern jint createAudioDeviceAttributesFromNative(JNIEnv *env, jobject *jAudioDeviceAttributes, const AudioDeviceTypeAddr *devTypeAddr); + +extern jint createAudioDeviceTypeAddrFromJava(JNIEnv *env, AudioDeviceTypeAddr *devTypeAddr, + const jobject jAudioDeviceAttributes); } // namespace android #endif \ No newline at end of file diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index c847e4d7654f1f27e1d2b460b21a8ea22135abcf..b2dd15307e7dcd015d4cd04ebc8b19ea31eca297 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -27,6 +27,8 @@ #include "core_jni_helpers.h" #include +#include +#include #include #include #include @@ -207,6 +209,7 @@ static struct { jmethodID getId; jmethodID getResonantFrequency; jmethodID getQFactor; + jmethodID getMaxAmplitude; } gVibratorMethods; static Mutex gLock; @@ -2022,6 +2025,18 @@ android_media_AudioSystem_registerRoutingCallback(JNIEnv *env, jobject thiz) AudioSystem::setRoutingCallback(android_media_AudioSystem_routing_callback); } +void javaAudioFormatToNativeAudioConfig(JNIEnv *env, audio_config_t *nConfig, + const jobject jFormat, bool isInput) { + *nConfig = AUDIO_CONFIG_INITIALIZER; + nConfig->format = audioFormatToNative(env->GetIntField(jFormat, gAudioFormatFields.mEncoding)); + nConfig->sample_rate = env->GetIntField(jFormat, gAudioFormatFields.mSampleRate); + jint jChannelMask = env->GetIntField(jFormat, gAudioFormatFields.mChannelMask); + if (isInput) { + nConfig->channel_mask = inChannelMaskToNative(jChannelMask); + } else { + nConfig->channel_mask = outChannelMaskToNative(jChannelMask); + } +} static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, @@ -2042,13 +2057,7 @@ static jint convertAudioMixToNative(JNIEnv *env, nAudioMix->mCbFlags = env->GetIntField(jAudioMix, gAudioMixFields.mCallbackFlags); jobject jFormat = env->GetObjectField(jAudioMix, gAudioMixFields.mFormat); - nAudioMix->mFormat = AUDIO_CONFIG_INITIALIZER; - nAudioMix->mFormat.sample_rate = env->GetIntField(jFormat, - gAudioFormatFields.mSampleRate); - nAudioMix->mFormat.channel_mask = outChannelMaskToNative(env->GetIntField(jFormat, - gAudioFormatFields.mChannelMask)); - nAudioMix->mFormat.format = audioFormatToNative(env->GetIntField(jFormat, - gAudioFormatFields.mEncoding)); + javaAudioFormatToNativeAudioConfig(env, &nAudioMix->mFormat, jFormat, false /*isInput*/); env->DeleteLocalRef(jFormat); jobject jRule = env->GetObjectField(jAudioMix, gAudioMixFields.mRule); @@ -2704,11 +2713,65 @@ static jint android_media_AudioSystem_setVibratorInfos(JNIEnv *env, jobject thiz vibratorInfo.resonantFrequency = env->CallFloatMethod(jVibrator.get(), gVibratorMethods.getResonantFrequency); vibratorInfo.qFactor = env->CallFloatMethod(jVibrator.get(), gVibratorMethods.getQFactor); + vibratorInfo.maxAmplitude = + env->CallFloatMethod(jVibrator.get(), gVibratorMethods.getMaxAmplitude); vibratorInfos.push_back(vibratorInfo); } return (jint)check_AudioSystem_Command(AudioSystem::setVibratorInfos(vibratorInfos)); } +static jobject android_media_AudioSystem_getSpatializer(JNIEnv *env, jobject thiz, + jobject jISpatializerCallback) { + sp nISpatializerCallback + = interface_cast( + ibinderForJavaObject(env, jISpatializerCallback)); + sp nSpatializer; + status_t status = AudioSystem::getSpatializer(nISpatializerCallback, + &nSpatializer); + if (status != NO_ERROR) { + return nullptr; + } + return javaObjectForIBinder(env, IInterface::asBinder(nSpatializer)); +} + +static jboolean android_media_AudioSystem_canBeSpatialized(JNIEnv *env, jobject thiz, + jobject jaa, jobject jFormat, + jobjectArray jDeviceArray) { + JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return false; + } + + AudioDeviceTypeAddrVector nDevices; + + const size_t numDevices = env->GetArrayLength(jDeviceArray); + for (size_t i = 0; i < numDevices; ++i) { + AudioDeviceTypeAddr device; + jobject jDevice = env->GetObjectArrayElement(jDeviceArray, i); + if (jDevice == nullptr) { + return false; + } + jStatus = createAudioDeviceTypeAddrFromJava(env, &device, jDevice); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return false; + } + nDevices.push_back(device); + } + + audio_config_t nConfig; + javaAudioFormatToNativeAudioConfig(env, &nConfig, jFormat, false /*isInput*/); + + bool canBeSpatialized; + status_t status = + AudioSystem::canBeSpatialized(paa.get(), &nConfig, nDevices, &canBeSpatialized); + if (status != NO_ERROR) { + ALOGW("%s native returned error %d", __func__, status); + return false; + } + return canBeSpatialized; +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = @@ -2845,7 +2908,15 @@ static const JNINativeMethod gMethods[] = (void *)android_media_AudioSystem_removeUserIdDeviceAffinities}, {"setCurrentImeUid", "(I)I", (void *)android_media_AudioSystem_setCurrentImeUid}, {"setVibratorInfos", "(Ljava/util/List;)I", - (void *)android_media_AudioSystem_setVibratorInfos}}; + (void *)android_media_AudioSystem_setVibratorInfos}, + {"nativeGetSpatializer", + "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;", + (void *)android_media_AudioSystem_getSpatializer}, + {"canBeSpatialized", + "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;" + "[Landroid/media/AudioDeviceAttributes;)Z", + (void *)android_media_AudioSystem_canBeSpatialized}}; + static const JNINativeMethod gEventHandlerMethods[] = { {"native_setup", @@ -3070,6 +3141,8 @@ int register_android_media_AudioSystem(JNIEnv *env) gVibratorMethods.getResonantFrequency = GetMethodIDOrDie(env, vibratorClass, "getResonantFrequency", "()F"); gVibratorMethods.getQFactor = GetMethodIDOrDie(env, vibratorClass, "getQFactor", "()F"); + gVibratorMethods.getMaxAmplitude = + GetMethodIDOrDie(env, vibratorClass, "getHapticChannelMaximumAmplitude", "()F"); AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback); diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index e93b00d7b148b00a81c47a25dad0dfb027c532dc..86d781033e5e39211178ef3ac786852e9b26e035 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -92,6 +92,7 @@ static struct configuration_offsets_t { jfieldID mSmallestScreenWidthDpOffset; jfieldID mScreenWidthDpOffset; jfieldID mScreenHeightDpOffset; + jfieldID mScreenLayoutOffset; } gConfigurationOffsets; static struct arraymap_offsets_t { @@ -1019,6 +1020,7 @@ static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config& config.smallestScreenWidthDp); env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp); env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp); + env->SetIntField(result, gConfigurationOffsets.mScreenLayoutOffset, config.screenLayout); return result; } @@ -1553,6 +1555,8 @@ int register_android_content_AssetManager(JNIEnv* env) { GetFieldIDOrDie(env, configurationClass, "screenWidthDp", "I"); gConfigurationOffsets.mScreenHeightDpOffset = GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I"); + gConfigurationOffsets.mScreenLayoutOffset = + GetFieldIDOrDie(env, configurationClass, "screenLayout", "I"); jclass arrayMapClass = FindClassOrDie(env, "android/util/ArrayMap"); gArrayMapOffsets.classObject = MakeGlobalRefOrDie(env, arrayMapClass); diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index 45e3d1b97e8332f107149916a76cb9bb9660ff87..16366a4e5bec382185aa1de311fabd67f760a769 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -155,6 +155,7 @@ status_t NativeInputEventSender::sendMotionEvent(uint32_t seq, const MotionEvent event->getYPrecision(), event->getRawXCursorPosition(), event->getRawYCursorPosition(), + event->getDisplayOrientation(), event->getDisplaySize().x, event->getDisplaySize().y, event->getDownTime(), event->getHistoricalEventTime(i), diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index 6971301cec32d7b66b9026d31ceac9346764e5f8..cabf3abe350a25fcd90c610b484df1647b5ef098 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,8 @@ static struct { jfieldID toolMajor; jfieldID toolMinor; jfieldID orientation; + jfieldID relativeX; + jfieldID relativeY; } gPointerCoordsClassInfo; static struct { @@ -212,6 +215,12 @@ static void pointerCoordsToNative(JNIEnv* env, jobject pointerCoordsObj, env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.toolMinor)); outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.orientation)); + outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, + env->GetFloatField(pointerCoordsObj, + gPointerCoordsClassInfo.relativeX)); + outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, + env->GetFloatField(pointerCoordsObj, + gPointerCoordsClassInfo.relativeY)); BitSet64 bits = BitSet64(env->GetLongField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits)); @@ -261,6 +270,12 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer float rawY = rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y); vec2 transformed = transform.transform(rawX, rawY); + float rawRelX = rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + float rawRelY = rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + // Apply only rotation and scale, not translation. + const vec2 transformedOrigin = transform.transform(0, 0); + const vec2 transformedRel = transform.transform(rawRelX, rawRelY) - transformedOrigin; + env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.x, transformed.x); env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.y, transformed.y); env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.pressure, @@ -277,6 +292,8 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR)); env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.orientation, rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); + env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.relativeX, transformedRel.x); + env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.relativeY, transformedRel.y); uint64_t outBits = 0; BitSet64 bits = BitSet64(rawPointerCoords->bits); @@ -289,6 +306,8 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MAJOR); bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MINOR); bits.clearBit(AMOTION_EVENT_AXIS_ORIENTATION); + bits.clearBit(AMOTION_EVENT_AXIS_RELATIVE_X); + bits.clearBit(AMOTION_EVENT_AXIS_RELATIVE_Y); if (!bits.isEmpty()) { uint32_t packedAxesCount = bits.count(); jfloatArray outValuesArray = obtainPackedAxisValuesArray(env, packedAxesCount, @@ -378,8 +397,8 @@ static jlong android_view_MotionEvent_nativeInitialize( flags, edgeFlags, metaState, buttonState, static_cast(classification), transform, xPrecision, yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE, - AMOTION_EVENT_INVALID_DISPLAY_SIZE, downTimeNanos, eventTimeNanos, + AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0, + INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, downTimeNanos, eventTimeNanos, pointerCount, pointerProperties, rawPointerCoords); return reinterpret_cast(event.release()); @@ -872,6 +891,8 @@ int register_android_view_MotionEvent(JNIEnv* env) { gPointerCoordsClassInfo.toolMajor = GetFieldIDOrDie(env, clazz, "toolMajor", "F"); gPointerCoordsClassInfo.toolMinor = GetFieldIDOrDie(env, clazz, "toolMinor", "F"); gPointerCoordsClassInfo.orientation = GetFieldIDOrDie(env, clazz, "orientation", "F"); + gPointerCoordsClassInfo.relativeX = GetFieldIDOrDie(env, clazz, "relativeX", "F"); + gPointerCoordsClassInfo.relativeY = GetFieldIDOrDie(env, clazz, "relativeY", "F"); clazz = FindClassOrDie(env, "android/view/MotionEvent$PointerProperties"); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index e47718305b7cfe922f418ee9e53bef89878874c3..1452c67ae3c6804e98d946d1a2759896742ea5fc 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -62,6 +62,8 @@ namespace android { +using gui::FocusRequest; + static void doThrowNPE(JNIEnv* env) { jniThrowNullPointerException(env, NULL); } @@ -868,6 +870,13 @@ static void nativeSetFixedTransformHint(JNIEnv* env, jclass clazz, jlong transac transaction->setFixedTransformHint(ctrl, transformHint); } +static void nativeSetDropInputMode(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jint mode) { + auto transaction = reinterpret_cast(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast(nativeObject); + transaction->setDropInputMode(ctrl, static_cast(mode)); +} + static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) { const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds(); jlongArray array = env->NewLongArray(displayIds.size()); @@ -1020,6 +1029,17 @@ static void nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz, } } +static void nativeSetDisplayFlags(JNIEnv* env, jclass clazz, jlong transactionObj, jobject tokenObj, + jint flags) { + sp token(ibinderForJavaObject(env, tokenObj)); + if (token == NULL) return; + + { + auto transaction = reinterpret_cast(transactionObj); + transaction->setDisplayFlags(token, flags); + } +} + static void nativeSetDisplayProjection(JNIEnv* env, jclass clazz, jlong transactionObj, jobject tokenObj, jint orientation, @@ -1795,16 +1815,18 @@ static void nativeSetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfac if (surface == nullptr) { return; } - surface->setTransformHint( - ui::Transform::toRotationFlags(static_cast(transformHint))); + surface->setTransformHint(transformHint); } static jint nativeGetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) { sp surface(reinterpret_cast(nativeSurfaceControl)); - ui::Transform::RotationFlags transformHintRotationFlags = - static_cast(surface->getTransformHint()); + return surface->getTransformHint(); +} + +static jint nativeGetLayerId(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) { + sp surface(reinterpret_cast(nativeSurfaceControl)); - return toRotationInt(ui::Transform::toRotation((transformHintRotationFlags))); + return surface->getLayerId(); } // ---------------------------------------------------------------------------- @@ -1897,6 +1919,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetDisplaySurface }, {"nativeSetDisplayLayerStack", "(JLandroid/os/IBinder;I)V", (void*)nativeSetDisplayLayerStack }, + {"nativeSetDisplayFlags", "(JLandroid/os/IBinder;I)V", + (void*)nativeSetDisplayFlags }, {"nativeSetDisplayProjection", "(JLandroid/os/IBinder;IIIIIIIII)V", (void*)nativeSetDisplayProjection }, {"nativeSetDisplaySize", "(JLandroid/os/IBinder;II)V", @@ -2002,6 +2026,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetTransformHint }, {"nativeSetTrustedOverlay", "(JJZ)V", (void*)nativeSetTrustedOverlay }, + {"nativeSetDropInputMode", "(JJI)V", + (void*)nativeSetDropInputMode }, + {"nativeGetLayerId", "(J)I", + (void*)nativeGetLayerId }, // clang-format on }; diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ab88b537f96bce9a14367c2629a7df3764bafbd1 --- /dev/null +++ b/core/jni/android_window_WindowInfosListener.cpp @@ -0,0 +1,134 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "WindowInfosListener" + +#include +#include +#include +#include +#include + +#include "android_hardware_input_InputWindowHandle.h" +#include "core_jni_helpers.h" + +namespace android { + +using gui::WindowInfo; + +namespace { + +static struct { + jclass clazz; + jmethodID onWindowInfosChanged; +} gListenerClassInfo; + +static jclass gInputWindowHandleClass; + +struct WindowInfosListener : public gui::WindowInfosListener { + WindowInfosListener(JNIEnv* env, jobject listener) + : mListener(env->NewWeakGlobalRef(listener)) {} + + void onWindowInfosChanged(const std::vector& windowInfos) override { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onWindowInfoChanged."); + + jobject listener = env->NewGlobalRef(mListener); + if (listener == nullptr) { + // Weak reference went out of scope + return; + } + + jobjectArray jWindowHandlesArray = + env->NewObjectArray(windowInfos.size(), gInputWindowHandleClass, nullptr); + for (int i = 0; i < windowInfos.size(); i++) { + ScopedLocalRef + jWindowHandle(env, + android_view_InputWindowHandle_fromWindowInfo(env, + windowInfos[i])); + env->SetObjectArrayElement(jWindowHandlesArray, i, jWindowHandle.get()); + } + + env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged, jWindowHandlesArray); + env->DeleteGlobalRef(listener); + + if (env->ExceptionCheck()) { + ALOGE("WindowInfosListener.onWindowInfosChanged() failed."); + LOGE_EX(env); + env->ExceptionClear(); + } + } + + ~WindowInfosListener() override { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(mListener); + } + +private: + jweak mListener; +}; + +jlong nativeCreate(JNIEnv* env, jclass clazz, jobject obj) { + WindowInfosListener* listener = new WindowInfosListener(env, obj); + listener->incStrong((void*)nativeCreate); + return reinterpret_cast(listener); +} + +void destroyNativeService(void* ptr) { + WindowInfosListener* listener = reinterpret_cast(ptr); + listener->decStrong((void*)nativeCreate); +} + +void nativeRegister(JNIEnv* env, jclass clazz, jlong ptr) { + sp listener = reinterpret_cast(ptr); + SurfaceComposerClient::getDefault()->addWindowInfosListener(listener); +} + +void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) { + sp listener = reinterpret_cast(ptr); + SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener); +} + +static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) { + return static_cast(reinterpret_cast(&destroyNativeService)); +} + +const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + {"nativeCreate", "(Landroid/window/WindowInfosListener;)J", (void*)nativeCreate}, + {"nativeRegister", "(J)V", (void*)nativeRegister}, + {"nativeUnregister", "(J)V", (void*)nativeUnregister}, + {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer}}; + +} // namespace + +int register_android_window_WindowInfosListener(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/window/WindowInfosListener", gMethods, + NELEM(gMethods)); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + + jclass clazz = env->FindClass("android/window/WindowInfosListener"); + gListenerClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gListenerClassInfo.onWindowInfosChanged = + env->GetMethodID(gListenerClassInfo.clazz, "onWindowInfosChanged", + "([Landroid/view/InputWindowHandle;)V"); + + clazz = env->FindClass("android/view/InputWindowHandle"); + gInputWindowHandleClass = MakeGlobalRefOrDie(env, clazz); + return 0; +} + +} // namespace android diff --git a/core/proto/OWNERS b/core/proto/OWNERS index 43d2439b7f56a352de52f695d3e2be6ba54ef180..a4463e4e96c5a2173aba1eef76f54dc6e498047d 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -5,6 +5,7 @@ joeo@google.com singhtejinder@google.com yanmin@google.com yaochen@google.com +yro@google.com zhouwenjie@google.com # Frameworks @@ -12,7 +13,7 @@ ogunwale@google.com jjaggi@google.com kwekua@google.com roosa@google.com -per-file package_item_info.proto = patb@google.com +per-file package_item_info.proto = toddke@google.com,patb@google.com per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 1bba12ff7fa6a1a2c35ca466d2591af27ba4a9bb..ba4a5b0ce222f26ba55b6f8cd46456d8998500bd 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -79,7 +79,7 @@ message SecureSettingsProto { optional SettingProto accessibility_magnification_mode = 34 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto button_targets = 35 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_magnification_capability = 36 [ (android.privacy).dest = DEST_AUTOMATIC ]; - // Settings for accessibility button mode (navigation bar or floating action menu). + // Settings for accessibility button related config optional SettingProto accessibility_button_mode = 37 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_floating_menu_size = 38 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_floating_menu_icon_type = 39 [ (android.privacy).dest = DEST_AUTOMATIC ]; diff --git a/core/proto/android/server/accessibilitytrace.proto b/core/proto/android/server/accessibilitytrace.proto index 1fc4a01936b17d8927e9a2c0a870818df3e7069f..41fecfd6cdb7cf35faac290ff67643747ddfb38a 100644 --- a/core/proto/android/server/accessibilitytrace.proto +++ b/core/proto/android/server/accessibilitytrace.proto @@ -46,17 +46,17 @@ message AccessibilityTraceProto { /* required: elapsed realtime in nanos since boot of when this entry was logged */ optional fixed64 elapsed_realtime_nanos = 1; optional string calendar_time = 2; - - optional string process_name = 3; - optional string thread_id_name = 4; + repeated string logging_type = 3; + optional string process_name = 4; + optional string thread_id_name = 5; /* where the trace originated */ - optional string where = 5; + optional string where = 6; - optional string calling_pkg = 6; - optional string calling_params = 7; - optional string calling_stacks = 8; + optional string calling_pkg = 7; + optional string calling_params = 8; + optional string calling_stacks = 9; - optional AccessibilityDumpProto accessibility_service = 9; - optional com.android.server.wm.WindowManagerServiceDumpProto window_manager_service = 10; + optional AccessibilityDumpProto accessibility_service = 10; + optional com.android.server.wm.WindowManagerServiceDumpProto window_manager_service = 11; } diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index a62ddd084aab2806db1bb4ec3d44f3ce4a715958..6faa04690473e40f250b1f93193f8356c3e6073d 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -69,6 +69,7 @@ message RootWindowContainerProto { // know what activity types to check for when invoking splitscreen multi-window. optional bool is_home_recents_component = 6; repeated IdentifierProto pending_activities = 7 [deprecated=true]; + optional int32 default_min_size_resizable_task = 8; } message BarControllerProto { @@ -201,7 +202,6 @@ message DisplayContentProto { optional .com.android.server.wm.IdentifierProto resumed_activity = 24; repeated TaskProto tasks = 25 [deprecated=true]; optional bool display_ready = 26; - optional WindowStateProto input_method_target = 27; optional WindowStateProto input_method_input_target = 28; optional WindowStateProto input_method_control_target = 29; @@ -211,6 +211,8 @@ message DisplayContentProto { optional DisplayRotationProto display_rotation = 33; optional int32 ime_policy = 34; + optional bool is_sleeping = 36; + repeated string sleep_tokens = 37; } /* represents DisplayArea object */ @@ -278,7 +280,7 @@ message PinnedTaskControllerProto { message TaskProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional WindowContainerProto window_container = 1; + optional WindowContainerProto window_container = 1 [deprecated=true]; optional int32 id = 2; reserved 3; // activity optional bool fills_parent = 4; @@ -295,12 +297,12 @@ message TaskProto { optional string real_activity = 13; optional string orig_activity = 14; - optional int32 display_id = 15; + optional int32 display_id = 15 [deprecated=true]; optional int32 root_task_id = 16; - optional int32 activity_type = 17 [(.android.typedef) = "android.app.WindowConfiguration.ActivityType"]; + optional int32 activity_type = 17 [(.android.typedef) = "android.app.WindowConfiguration.ActivityType", deprecated=true] ; optional int32 resize_mode = 18 [(.android.typedef) = "android.appwidget.AppWidgetProviderInfo.ResizeModeFlags"]; - optional int32 min_width = 19; - optional int32 min_height = 20; + optional int32 min_width = 19 [deprecated=true]; + optional int32 min_height = 20 [deprecated=true]; optional .android.graphics.RectProto adjusted_bounds = 21; optional .android.graphics.RectProto last_non_fullscreen_bounds = 22; @@ -312,6 +314,18 @@ message TaskProto { optional bool created_by_organizer = 28; optional string affinity = 29; optional bool has_child_pip_activity = 30; + optional TaskFragmentProto task_fragment = 31; +} + +/* represents TaskFragment */ +message TaskFragmentProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional WindowContainerProto window_container = 1; + optional int32 display_id = 2; + optional int32 activity_type = 3 [(.android.typedef) = "android.app.WindowConfiguration.ActivityType"]; + optional int32 min_width = 4; + optional int32 min_height = 5; } /* represents ActivityRecordProto */ @@ -352,6 +366,7 @@ message ActivityRecordProto { optional bool pip_auto_enter_enabled = 31; optional bool in_size_compat_mode = 32; optional float min_aspect_ratio = 33; + optional bool provides_max_bounds = 34; } /* represents WindowToken */ @@ -469,6 +484,7 @@ message WindowContainerProto { optional SurfaceAnimatorProto surface_animator = 4; repeated WindowContainerChildProto children = 5; optional IdentifierProto identifier = 6; + optional .android.view.SurfaceControlProto surface_control = 7; } /* represents a generic child of a WindowContainer */ @@ -494,6 +510,8 @@ message WindowContainerChildProto { optional WindowTokenProto window_token = 7; /* represents a WindowState child */ optional WindowStateProto window = 8; + /* represents a WindowState child */ + optional TaskFragmentProto task_fragment = 9; } /* represents ConfigurationContainer */ diff --git a/core/proto/android/view/surfacecontrol.proto b/core/proto/android/view/surfacecontrol.proto index cbb243ba7872f1f7a1cc2f0984120d4ddddc1d0e..5a5f035412b2c5a720ebe9d8811fe31948ccc0b0 100644 --- a/core/proto/android/view/surfacecontrol.proto +++ b/core/proto/android/view/surfacecontrol.proto @@ -29,4 +29,5 @@ message SurfaceControlProto { optional int32 hash_code = 1; optional string name = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; + optional int32 layerId = 3; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 111b694d65f3ea700e25caaf0434106cc888510b..2e1777993e013e56f5eda7e29fb8bdef0bdf2270 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4906,6 +4906,18 @@ + + + + + + @@ -5710,6 +5722,10 @@ + + diff --git a/core/res/OWNERS b/core/res/OWNERS index b7df255afa807185d495a952ec1ed11b0de95662..a20b89540a040a283d01e3436bbbcd28e59b2ad1 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -22,6 +22,7 @@ patb@google.com shanh@google.com svetoslavganov@android.com svetoslavganov@google.com +toddke@google.com tsuji@google.com yamasani@google.com diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml b/core/res/res/anim/task_fragment_close_enter.xml similarity index 55% rename from packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml rename to core/res/res/anim/task_fragment_close_enter.xml index 088e82bb42601e7f5d5e255db112515c202438d3..c940552d53ad7fe782f237dfdc551c8dd80ff9f3 100644 --- a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml +++ b/core/res/res/anim/task_fragment_close_enter.xml @@ -13,14 +13,20 @@ 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. - --> + --> - - - - - - - - + + + \ No newline at end of file diff --git a/core/res/res/anim/task_fragment_close_exit.xml b/core/res/res/anim/task_fragment_close_exit.xml new file mode 100644 index 0000000000000000000000000000000000000000..8998f764ff9b8df66061de2b32728cacf60299e6 --- /dev/null +++ b/core/res/res/anim/task_fragment_close_exit.xml @@ -0,0 +1,42 @@ + + + + + + + diff --git a/core/res/res/anim/task_fragment_open_enter.xml b/core/res/res/anim/task_fragment_open_enter.xml new file mode 100644 index 0000000000000000000000000000000000000000..6bc47deb2de43539fe6c617ddd0bea51b503ef32 --- /dev/null +++ b/core/res/res/anim/task_fragment_open_enter.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-ar/strings.xml b/core/res/res/anim/task_fragment_open_exit.xml similarity index 52% rename from packages/SettingsLib/BannerMessagePreference/res/values-ar/strings.xml rename to core/res/res/anim/task_fragment_open_exit.xml index 0f1b9acbd15be590301db02bd7e8d8d43384350d..160eb84223dae8582a7a5f4f2bdab4c881994675 100644 --- a/packages/SettingsLib/BannerMessagePreference/res/values-ar/strings.xml +++ b/core/res/res/anim/task_fragment_open_exit.xml @@ -1,5 +1,5 @@ - - - - "إغلاق" - + + + \ No newline at end of file diff --git a/core/res/res/drawable-nodpi/default_wallpaper.png b/core/res/res/drawable-nodpi/default_wallpaper.png index ce546f0a11e7617627a93fec9a30e44489464224..5152972d2a80a4a832f5d292bdb398f17c77821e 100644 Binary files a/core/res/res/drawable-nodpi/default_wallpaper.png and b/core/res/res/drawable-nodpi/default_wallpaper.png differ diff --git a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png index af8e2512385a541569e0745d24361467eefeb5c9..26376fb87cbefcbce469e1b984cff0341723e09a 100644 Binary files a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png and b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png differ diff --git a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png index cb00d82a826fa820787239bfaae22ea28e211f62..490ebeeb75c1a4a9bf941f5c497a064fe5bfb63a 100644 Binary files a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png and b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png differ diff --git a/core/res/res/drawable/ic_corp_badge.xml b/core/res/res/drawable/ic_corp_badge.xml index 16df452903022ead1d4c8d312c79ce216c83cc44..6c40f10a03c38c97fdd858bfd098e491709c0f18 100644 --- a/core/res/res/drawable/ic_corp_badge.xml +++ b/core/res/res/drawable/ic_corp_badge.xml @@ -22,8 +22,5 @@ Copyright (C) 2018 The Android Open Source Project android:viewportHeight="24"> - + android:pathData="@string/config_work_badge_path_24"/> \ No newline at end of file diff --git a/core/res/res/drawable/ic_corp_badge_case.xml b/core/res/res/drawable/ic_corp_badge_case.xml index 1cd995eea171432458381e4a57a903d1f4e4023d..838426ecd9a0ad62aaaf1914995402a1d1aa80df 100644 --- a/core/res/res/drawable/ic_corp_badge_case.xml +++ b/core/res/res/drawable/ic_corp_badge_case.xml @@ -1,9 +1,9 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/core/res/res/drawable/ic_corp_badge_no_background.xml b/core/res/res/drawable/ic_corp_badge_no_background.xml index 8f7fb70d5b835dd4d15f66cb59b388146c2f8eb7..e81e69f1b5d5d8faf33a543f8fbf0c02e06b96a2 100644 --- a/core/res/res/drawable/ic_corp_badge_no_background.xml +++ b/core/res/res/drawable/ic_corp_badge_no_background.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/core/res/res/drawable/ic_corp_icon.xml b/core/res/res/drawable/ic_corp_icon.xml index 48531dd8c539f330316dcadf1ddec323983bb4fd..86bb98e0996b84392841a0b247aff295b433968c 100644 --- a/core/res/res/drawable/ic_corp_icon.xml +++ b/core/res/res/drawable/ic_corp_icon.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> \ No newline at end of file diff --git a/core/res/res/drawable/ic_corp_icon_badge_case.xml b/core/res/res/drawable/ic_corp_icon_badge_case.xml index 50551d401120ee8fe9c11af5c77ffc0dbb329c91..09cf9cb89abeb763e78c8cb19783c787344b01cb 100644 --- a/core/res/res/drawable/ic_corp_icon_badge_case.xml +++ b/core/res/res/drawable/ic_corp_icon_badge_case.xml @@ -22,9 +22,15 @@ Copyright (C) 2016 The Android Open Source Project - + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_corp_statusbar_icon.xml b/core/res/res/drawable/ic_corp_statusbar_icon.xml index 8f7fb70d5b835dd4d15f66cb59b388146c2f8eb7..e81e69f1b5d5d8faf33a543f8fbf0c02e06b96a2 100644 --- a/core/res/res/drawable/ic_corp_statusbar_icon.xml +++ b/core/res/res/drawable/ic_corp_statusbar_icon.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/core/res/res/drawable/ic_corp_user_badge.xml b/core/res/res/drawable/ic_corp_user_badge.xml index a08f2d4845e1fc6dd73b5c76745a734348d6156f..76ba4506426d4e56fa4d14e0a3b08dcd32d4e337 100644 --- a/core/res/res/drawable/ic_corp_user_badge.xml +++ b/core/res/res/drawable/ic_corp_user_badge.xml @@ -7,9 +7,6 @@ - diff --git a/core/res/res/layout-car/car_alert_dialog.xml b/core/res/res/layout-car/car_alert_dialog.xml index 569e5948e2e08aa78c43b1b6679c518cc5d05140..2e7b62ceb723f5620e3e77b06837ecbd2c592025 100644 --- a/core/res/res/layout-car/car_alert_dialog.xml +++ b/core/res/res/layout-car/car_alert_dialog.xml @@ -54,7 +54,7 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/text_view_start_margin" android:layout_marginEnd="@dimen/text_view_end_margin" - style="@style/CarBody2"/> + style="@style/CarBody4"/> diff --git a/core/res/res/layout/accessibility_shortcut_chooser_item.xml b/core/res/res/layout/accessibility_shortcut_chooser_item.xml index 7cca1292af68a3dfe23ad3eb899759df5baed394..4d7946b2138bc4c8b146fbac1a5181235e33dc6f 100644 --- a/core/res/res/layout/accessibility_shortcut_chooser_item.xml +++ b/core/res/res/layout/accessibility_shortcut_chooser_item.xml @@ -39,15 +39,20 @@ android:layout_height="48dp" android:scaleType="fitCenter"/> - + android:layout_weight="1"> + + +