diff options
108 files changed, 3673 insertions, 769 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index a9f1d4513de4..12546d81353a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -17342,12 +17342,14 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SCALER_AVAILABLE_ROTATE_AND_CROP_MODES; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SCALER_AVAILABLE_STREAM_USE_CASES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap> SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION; @@ -17441,6 +17443,8 @@ package android.hardware.camera2 { } public final class CameraExtensionCharacteristics { + method @NonNull public java.util.Set<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(int); + method @NonNull public java.util.Set<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(int); method @Nullable public android.util.Range<java.lang.Long> getEstimatedCaptureLatencyRangeMillis(int, @NonNull android.util.Size, int); method @NonNull public <T> java.util.List<android.util.Size> getExtensionSupportedSizes(int, @NonNull Class<T>); method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int); @@ -17465,6 +17469,7 @@ package android.hardware.camera2 { ctor public CameraExtensionSession.ExtensionCaptureCallback(); method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest); method public void onCaptureProcessStarted(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest); + method public void onCaptureResultAvailable(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @NonNull android.hardware.camera2.TotalCaptureResult); method public void onCaptureSequenceAborted(@NonNull android.hardware.camera2.CameraExtensionSession, int); method public void onCaptureSequenceCompleted(@NonNull android.hardware.camera2.CameraExtensionSession, int); method public void onCaptureStarted(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, long); @@ -17676,9 +17681,16 @@ package android.hardware.camera2 { field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5 field public static final int REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING = 17; // 0x11 field public static final int REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13; // 0xd + field public static final int REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE = 19; // 0x13 field public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14; // 0xe field public static final int REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR = 16; // 0x10 field public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7; // 0x7 + field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT = 0; // 0x0 + field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW = 1; // 0x1 + field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL = 4; // 0x4 + field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE = 2; // 0x2 + field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL = 5; // 0x5 + field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD = 3; // 0x3 field public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; // 0x0 field public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; // 0x1 field public static final int SCALER_ROTATE_AND_CROP_180 = 2; // 0x2 @@ -18086,6 +18098,7 @@ package android.hardware.camera2.params { method public int get10BitFormat(); method @NonNull public java.util.List<android.util.Size> getAvailableSizes(); method public int getFormat(); + method public int getStreamUseCase(); method public boolean is10BitCapable(); method public boolean isInput(); method public boolean isMaximumSize(); @@ -18142,6 +18155,7 @@ package android.hardware.camera2.params { method public void enableSurfaceSharing(); method public int getDynamicRangeProfile(); method public int getMaxSharedSurfaceCount(); + method public int getStreamUseCase(); method @Nullable public android.view.Surface getSurface(); method public int getSurfaceGroupId(); method @NonNull public java.util.List<android.view.Surface> getSurfaces(); @@ -18149,6 +18163,7 @@ package android.hardware.camera2.params { method public void removeSurface(@NonNull android.view.Surface); method public void setDynamicRangeProfile(int); method public void setPhysicalCameraId(@Nullable String); + method public void setStreamUseCase(int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR; field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff @@ -43049,7 +43064,8 @@ package android.telephony { method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long); method public void setSubscriptionOverrideUnmetered(int, boolean, long); method public void setSubscriptionOverrideUnmetered(int, boolean, @NonNull int[], long); - method public void setSubscriptionPlans(int, @NonNull java.util.List<android.telephony.SubscriptionPlan>); + method @Deprecated public void setSubscriptionPlans(int, @NonNull java.util.List<android.telephony.SubscriptionPlan>); + method public void setSubscriptionPlans(int, @NonNull java.util.List<android.telephony.SubscriptionPlan>, long); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, @NonNull android.app.PendingIntent); field public static final String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED"; field public static final String ACTION_DEFAULT_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED"; @@ -48934,10 +48950,9 @@ package android.view { method public default void onBackInvoked(); } - public abstract class OnBackInvokedDispatcher { - ctor public OnBackInvokedDispatcher(); - method public abstract void registerOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback, int); - method public abstract void unregisterOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback); + public interface OnBackInvokedDispatcher { + method public void registerOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback, @IntRange(from=0) int); + method public void unregisterOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback); field public static final int PRIORITY_DEFAULT = 0; // 0x0 field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240 } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ef55d50b7677..3dad1a987494 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11143,6 +11143,7 @@ package android.service.euicc { field public static final String EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED = "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED"; field public static final String EXTRA_RESOLUTION_CONSENT = "android.service.euicc.extra.RESOLUTION_CONSENT"; field public static final String EXTRA_RESOLUTION_PORT_INDEX = "android.service.euicc.extra.RESOLUTION_PORT_INDEX"; + field public static final String EXTRA_RESOLUTION_SUBSCRIPTION_ID = "android.service.euicc.extra.RESOLUTION_SUBSCRIPTION_ID"; field public static final String EXTRA_RESOLUTION_USE_PORT_INDEX = "android.service.euicc.extra.RESOLUTION_USE_PORT_INDEX"; field public static final String EXTRA_RESOLVABLE_ERRORS = "android.service.euicc.extra.RESOLVABLE_ERRORS"; field public static final int RESOLVABLE_ERROR_CONFIRMATION_CODE = 1; // 0x1 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e27fc3480276..7b72374f2dce 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -670,6 +670,7 @@ package android.content { ctor public AttributionSource(int, @Nullable String, @Nullable String); ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder); ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource); + method public void enforceCallingPid(); } public final class AutofillOptions implements android.os.Parcelable { @@ -2801,6 +2802,10 @@ package android.view { field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800 } + public interface OnBackInvokedDispatcher { + field public static final long DISPATCH_BACK_INVOCATION_AHEAD_OF_TIME = 195946584L; // 0xbade858L + } + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface RemotableViewMethod { method public abstract String asyncImpl() default ""; } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 8935022639e4..db865ced6a67 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -115,6 +115,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnBackInvokedCallback; import android.view.OnBackInvokedDispatcher; import android.view.OnBackInvokedDispatcherOwner; import android.view.RemoteAnimationDefinition; @@ -144,6 +145,7 @@ import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toolbar; import android.window.SplashScreen; +import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -791,6 +793,7 @@ public class Activity extends ContextThemeWrapper private static final int LOG_AM_ON_ACTIVITY_RESULT_CALLED = 30062; private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064; private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065; + private OnBackInvokedCallback mDefaultBackCallback; /** * After {@link Build.VERSION_CODES#TIRAMISU}, @@ -1617,7 +1620,16 @@ public class Activity extends ContextThemeWrapper } mRestoredFromBundle = savedInstanceState != null; mCalled = true; - + if (!WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) { + // Add onBackPressed as default back behavior. + mDefaultBackCallback = new OnBackInvokedCallback() { + @Override + public void onBackInvoked() { + navigateBack(); + } + }; + getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); + } } /** @@ -2653,6 +2665,10 @@ public class Activity extends ContextThemeWrapper if (mUiTranslationController != null) { mUiTranslationController.onActivityDestroyed(); } + + if (mDefaultBackCallback != null) { + getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback); + } } /** @@ -3773,10 +3789,13 @@ public class Activity extends ContextThemeWrapper * @see KeyEvent */ public boolean onKeyUp(int keyCode, KeyEvent event) { - if (getApplicationInfo().targetSdkVersion - >= Build.VERSION_CODES.ECLAIR) { - if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() - && !event.isCanceled()) { + int sdkVersion = getApplicationInfo().targetSdkVersion; + if (sdkVersion >= Build.VERSION_CODES.ECLAIR) { + if (keyCode == KeyEvent.KEYCODE_BACK + && event.isTracking() + && !event.isCanceled() + && mDefaultBackCallback == null) { + // Using legacy back handling. onBackPressed(); return true; } @@ -3841,6 +3860,10 @@ public class Activity extends ContextThemeWrapper if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) { return; } + navigateBack(); + } + + private void navigateBack() { if (!isTaskRoot()) { // If the activity is not the root of the task, allow finish to proceed normally. finishAfterTransition(); diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 4b42ddc383b2..208477588885 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -52,6 +52,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnBackInvokedCallback; import android.view.OnBackInvokedDispatcher; import android.view.OnBackInvokedDispatcherOwner; import android.view.SearchEvent; @@ -62,6 +63,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; +import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; import com.android.internal.app.WindowDecorActionBar; @@ -156,6 +158,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** A {@link Runnable} to run instead of dismissing when {@link #dismiss()} is called. */ private Runnable mDismissOverride; + private OnBackInvokedCallback mDefaultBackCallback; /** * Creates a dialog window that uses the default dialog theme. @@ -453,6 +456,16 @@ public class Dialog implements DialogInterface, Window.Callback, */ protected void onStart() { if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true); + if (mContext != null && !WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) { + // Add onBackPressed as default back behavior. + mDefaultBackCallback = new OnBackInvokedCallback() { + @Override + public void onBackInvoked() { + onBackPressed(); + } + }; + getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); + } } /** @@ -460,6 +473,9 @@ public class Dialog implements DialogInterface, Window.Callback, */ protected void onStop() { if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); + if (mDefaultBackCallback != null) { + getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback); + } } private static final String DIALOG_SHOWING_TAG = "android:dialogShowing"; @@ -685,7 +701,8 @@ public class Dialog implements DialogInterface, Window.Callback, public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) && event.isTracking() - && !event.isCanceled()) { + && !event.isCanceled() + && WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) { onBackPressed(); return true; } diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 8fcb07f578e8..da1ba5265c4a 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -180,6 +180,8 @@ public class StatusBarManager { public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0; /** @hide */ public static final int NAVIGATION_HINT_IME_SHOWN = 1 << 1; + /** @hide */ + public static final int NAVIGATION_HINT_IME_SWITCHER_SHOWN = 1 << 2; /** @hide */ public static final int WINDOW_STATUS_BAR = 1; diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 157e709a67f0..3f2fa2188d24 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -155,8 +155,8 @@ public final class AttributionSource implements Parcelable { this(AttributionSourceState.CREATOR.createFromParcel(in)); // Since we just unpacked this object as part of it transiting a Binder - // call, this is the perfect time to enforce that its UID can be trusted - enforceCallingUid(); + // call, this is the perfect time to enforce that its UID and PID can be trusted + enforceCallingUidAndPid(); } /** @hide */ @@ -259,13 +259,24 @@ public final class AttributionSource implements Parcelable { } /** + * If you are handling an IPC and you don't trust the caller you need to validate whether the + * attribution source is one for the calling app to prevent the caller to pass you a source from + * another app without including themselves in the attribution chain. + * + * @throws SecurityException if the attribution source cannot be trusted to be from the caller. + */ + private void enforceCallingUidAndPid() { + enforceCallingUid(); + enforceCallingPid(); + } + + /** * If you are handling an IPC and you don't trust the caller you need to validate * whether the attribution source is one for the calling app to prevent the caller * to pass you a source from another app without including themselves in the * attribution chain. * - * @throws SecurityException if the attribution source cannot be trusted to be - * from the caller. + * @throws SecurityException if the attribution source cannot be trusted to be from the caller. */ public void enforceCallingUid() { if (!checkCallingUid()) { @@ -294,6 +305,33 @@ public final class AttributionSource implements Parcelable { return true; } + /** + * Validate that the pid being claimed for the calling app is not spoofed + * + * @throws SecurityException if the attribution source cannot be trusted to be from the caller. + * @hide + */ + @TestApi + public void enforceCallingPid() { + if (!checkCallingPid()) { + throw new SecurityException("Calling pid: " + Binder.getCallingPid() + + " doesn't match source pid: " + mAttributionSourceState.pid); + } + } + + /** + * Validate that the pid being claimed for the calling app is not spoofed + * + * @return if the attribution source cannot be trusted to be from the caller. + */ + private boolean checkCallingPid() { + final int callingPid = Binder.getCallingPid(); + if (mAttributionSourceState.pid != -1 && callingPid != mAttributionSourceState.pid) { + return false; + } + return true; + } + @Override public String toString() { if (Build.IS_DEBUGGABLE) { diff --git a/core/java/android/hardware/CameraStreamStats.java b/core/java/android/hardware/CameraStreamStats.java index ed22de8dd594..85890c1912c5 100644 --- a/core/java/android/hardware/CameraStreamStats.java +++ b/core/java/android/hardware/CameraStreamStats.java @@ -16,6 +16,7 @@ package android.hardware; import android.hardware.camera2.params.DynamicRangeProfiles; +import android.hardware.camera2.CameraMetadata; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -47,6 +48,7 @@ public class CameraStreamStats implements Parcelable { private float[] mHistogramBins; private long[] mHistogramCounts; private int mDynamicRangeProfile; + private int mStreamUseCase; private static final String TAG = "CameraStreamStats"; @@ -63,11 +65,13 @@ public class CameraStreamStats implements Parcelable { mMaxAppBuffers = 0; mHistogramType = HISTOGRAM_TYPE_UNKNOWN; mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; + mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT; } public CameraStreamStats(int width, int height, int format, int dataSpace, long usage, long requestCount, long errorCount, - int startLatencyMs, int maxHalBuffers, int maxAppBuffers, int dynamicRangeProfile) { + int startLatencyMs, int maxHalBuffers, int maxAppBuffers, int dynamicRangeProfile, + int streamUseCase) { mWidth = width; mHeight = height; mFormat = format; @@ -80,6 +84,7 @@ public class CameraStreamStats implements Parcelable { mMaxAppBuffers = maxAppBuffers; mHistogramType = HISTOGRAM_TYPE_UNKNOWN; mDynamicRangeProfile = dynamicRangeProfile; + mStreamUseCase = streamUseCase; } public static final @android.annotation.NonNull Parcelable.Creator<CameraStreamStats> CREATOR = @@ -126,6 +131,7 @@ public class CameraStreamStats implements Parcelable { dest.writeFloatArray(mHistogramBins); dest.writeLongArray(mHistogramCounts); dest.writeInt(mDynamicRangeProfile); + dest.writeInt(mStreamUseCase); } public void readFromParcel(Parcel in) { @@ -143,6 +149,7 @@ public class CameraStreamStats implements Parcelable { mHistogramBins = in.createFloatArray(); mHistogramCounts = in.createLongArray(); mDynamicRangeProfile = in.readInt(); + mStreamUseCase = in.readInt(); } public int getWidth() { @@ -200,4 +207,8 @@ public class CameraStreamStats implements Parcelable { public int getDynamicRangeProfile() { return mDynamicRangeProfile; } + + public int getStreamUseCase() { + return mStreamUseCase; + } } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index d2dc314585d6..a29bffe6ea93 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -657,7 +657,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * * @throws IllegalArgumentException if metadataClass is not a subclass of CameraMetadata */ - private <TKey> List<TKey> + <TKey> List<TKey> getAvailableKeyList(Class<?> metadataClass, Class<TKey> keyClass, int[] filterTags, boolean includeSynthetic) { @@ -2214,6 +2214,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR ULTRA_HIGH_RESOLUTION_SENSOR}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING REMOSAIC_REPROCESSING}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT DYNAMIC_RANGE_TEN_BIT}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE STREAM_USE_CASE}</li> * </ul> * * <p>This key is available on all devices.</p> @@ -2238,6 +2239,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR * @see #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT + * @see #REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE */ @PublicKey @NonNull @@ -3475,6 +3477,90 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<Boolean>("android.scaler.multiResolutionStreamSupported", boolean.class); /** + * <p>The stream use cases supported by this camera device.</p> + * <p>The stream use case indicates the purpose of a particular camera stream from + * the end-user perspective. Some examples of camera use cases are: preview stream for + * live viewfinder shown to the user, still capture for generating high quality photo + * capture, video record for encoding the camera output for the purpose of future playback, + * and video call for live realtime video conferencing.</p> + * <p>With this flag, the camera device can optimize the image processing pipeline + * parameters, such as tuning, sensor mode, and ISP settings, indepedent of + * the properties of the immediate camera output surface. For example, if the output + * surface is a SurfaceTexture, the stream use case flag can be used to indicate whether + * the camera frames eventually go to display, video encoder, + * still image capture, or all of them combined.</p> + * <p>The application sets the use case of a camera stream by calling + * {@link android.hardware.camera2.params.OutputConfiguration#setStreamUseCase }.</p> + * <p>A camera device with + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } + * capability must support the following stream use cases:</p> + * <ul> + * <li>DEFAULT</li> + * <li>PREVIEW</li> + * <li>STILL_CAPTURE</li> + * <li>VIDEO_RECORD</li> + * <li>PREVIEW_VIDEO_STILL</li> + * <li>VIDEO_CALL</li> + * </ul> + * <p>The guaranteed stream combinations related to stream use case for a camera device with + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } + * capability is documented in the camera device + * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline}. The + * application is strongly recommended to use one of the guaranteed stream combintations. + * If the application creates a session with a stream combination not in the guaranteed + * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session, + * the camera device may ignore some stream use cases due to hardware constraints + * and implementation details.</p> + * <p>For stream combinations not covered by the stream use case mandatory lists, such as + * reprocessable session, constrained high speed session, or RAW stream combinations, the + * application should leave stream use cases within the session as DEFAULT.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT DEFAULT}</li> + * <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW PREVIEW}</li> + * <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE STILL_CAPTURE}</li> + * <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD VIDEO_RECORD}</li> + * <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL PREVIEW_VIDEO_STILL}</li> + * <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL VIDEO_CALL}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT + * @see #SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW + * @see #SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE + * @see #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD + * @see #SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL + * @see #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL + */ + @PublicKey + @NonNull + public static final Key<int[]> SCALER_AVAILABLE_STREAM_USE_CASES = + new Key<int[]>("android.scaler.availableStreamUseCases", int[].class); + + /** + * <p>An array of mandatory stream combinations with stream use cases. + * This is an app-readable conversion of the mandatory stream combination + * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables} with + * each stream's use case being set.</p> + * <p>The array of + * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is + * generated according to the documented + * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} for a + * camera device with + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } + * capability. + * The mandatory stream combination array will be {@code null} in case the device doesn't + * have {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } + * capability.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS = + new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryUseCaseStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class); + + /** * <p>The area of the image sensor which corresponds to active pixels after any geometric * distortion correction has been applied.</p> * <p>This is the rectangle representing the size of the active region of the sensor (i.e. diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 47eb79d07469..1a42eaf541ca 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -404,7 +404,10 @@ public abstract class CameraDevice implements AutoCloseable { * (output format)/(surface type), or if the extension is not * supported, or if any of the output configurations select * a dynamic range different from - * {@link android.hardware.camera2.params.DynamicRangeProfiles#STANDARD} + * {@link android.hardware.camera2.params.DynamicRangeProfiles#STANDARD}, + * or if any of the output configurations sets a stream use + * case different from {@link + * android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT}. * @see CameraExtensionCharacteristics#getSupportedExtensions * @see CameraExtensionCharacteristics#getExtensionSupportedSizes */ @@ -855,6 +858,31 @@ public abstract class CameraDevice implements AutoCloseable { * will cause a capture session initialization failure. * </p> * + * <p>Devices with the STREAM_USE_CASE capability ({@link + * CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes {@link + * CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE}) support below additional + * stream combinations: + * + * <table> + * <tr><th colspan="10">STREAM_USE_CASE capability additional guaranteed configurations</th></tr> + * <tr><th colspan="3" id="rb">Target 1</th><th colspan="3" id="rb">Target 2</th><th colspan="3" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Usecase</th><th>Type</th><th id="rb">Max size</th><th>Usecase</th><th>Type</th><th id="rb">Max size</th><th>Usecase</th> </tr> + * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td colspan="3" id="rb"></td> <td colspan="3" id="rb"></td> <td>Simple preview or in-app image processing</td> </tr> + * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td colspan="3" id="rb"></td> <td colspan="3" id="rb"></td> <td>Simple video recording or in-app video processing</td> </tr> + * <tr> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td colspan="3" id="rb"></td> <td>Simple JPEG or YUV still image capture</td> </tr> + * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td colspan="3" id="rb"></td> <td colspan="3" id="rb"></td> <td>Multi-purpose stream for preview, video and still image capture</td> </tr> + * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code VIDEO_CALL}</td> <td colspan="3" id="rb"></td> <td colspan="3" id="rb"></td> <td>Simple video call</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>Preview with JPEG or YUV still image capture</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td colspan="3" id="rb"></td> <td>Preview with video recording or in-app video processing</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td colspan="3" id="rb"></td> <td>Preview with in-application image processing</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code VIDEO_CALL}</td> <td colspan="3" id="rb"></td> <td>Preview with video call</td> </tr> + * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>Multi-purpose stream with JPEG or YUV still capture</td> </tr> + * <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>YUV and JPEG concurrent still image capture (for testing)</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG or YUV video snapshot</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG or YUV still image capture</td> </tr> + * </table><br> + * </p> + * * <p>Since the capabilities of camera devices vary greatly, a given camera device may support * target combinations with sizes outside of these guarantees, but this can only be tested for * by calling {@link #isSessionConfigurationSupported} or attempting to create a session with diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 5c636c7fe2f0..aa98f1fdf9bc 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -30,6 +30,7 @@ import android.hardware.camera2.extension.IInitializeSessionCallback; import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.SizeList; +import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.ConditionVariable; @@ -49,6 +50,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -785,8 +787,8 @@ public final class CameraExtensionCharacteristics { if (latencyRange != null) { return new Range(latencyRange.min, latencyRange.max); } - } - } catch (RemoteException e) { + } + } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension capture latency! Extension service does" + " not respond!"); } finally { @@ -795,4 +797,142 @@ public final class CameraExtensionCharacteristics { return null; } + + /** + * Returns the set of keys supported by a {@link CaptureRequest} submitted in a + * {@link CameraExtensionSession} with a given extension type. + * + * <p>The set returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.</p> + * + * @param extension the extension type + * + * @return non-modifiable set of capture keys supported by camera extension session initialized + * with the given extension type. + * @throws IllegalArgumentException in case of unsupported extension. + */ + @NonNull + public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) { + long clientId = registerClient(mContext); + if (clientId < 0) { + throw new IllegalArgumentException("Unsupported extensions"); + } + + HashSet<CaptureRequest.Key> ret = new HashSet<>(); + + try { + if (!isExtensionSupported(mCameraId, extension, mChars)) { + throw new IllegalArgumentException("Unsupported extension"); + } + Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = + initializeExtension(extension); + extenders.second.onInit(mCameraId, mChars.getNativeMetadata()); + extenders.second.init(mCameraId, mChars.getNativeMetadata()); + CameraMetadataNative captureRequestMeta = + extenders.second.getAvailableCaptureRequestKeys(); + + if (captureRequestMeta != null) { + int[] requestKeys = captureRequestMeta.get( + CameraCharacteristics.REQUEST_AVAILABLE_REQUEST_KEYS); + if (requestKeys == null) { + throw new AssertionError("android.request.availableRequestKeys must be non-null" + + " in the characteristics"); + } + CameraCharacteristics requestChars = new CameraCharacteristics(captureRequestMeta); + + Object crKey = CaptureRequest.Key.class; + Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey; + + ret.addAll(requestChars.getAvailableKeyList(CaptureRequest.class, crKeyTyped, + requestKeys, /*includeSynthetic*/ false)); + } + + // Jpeg quality and orientation must always be supported + if (!ret.contains(CaptureRequest.JPEG_QUALITY)) { + ret.add(CaptureRequest.JPEG_QUALITY); + } + if (!ret.contains(CaptureRequest.JPEG_ORIENTATION)) { + ret.add(CaptureRequest.JPEG_ORIENTATION); + } + extenders.second.onDeInit(); + } catch (RemoteException e) { + throw new IllegalStateException("Failed to query the available capture request keys!"); + } finally { + unregisterClient(clientId); + } + + return Collections.unmodifiableSet(ret); + } + + /** + * Returns the set of keys supported by a {@link CaptureResult} passed as an argument to + * {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable}. + * + * <p>The set returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.</p> + * + * <p>In case the set is empty, then the extension is not able to support any capture results + * and the {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable} + * callback will not be fired.</p> + * + * @param extension the extension type + * + * @return non-modifiable set of capture result keys supported by camera extension session + * initialized with the given extension type. + * @throws IllegalArgumentException in case of unsupported extension. + */ + @NonNull + public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) { + long clientId = registerClient(mContext); + if (clientId < 0) { + throw new IllegalArgumentException("Unsupported extensions"); + } + + HashSet<CaptureResult.Key> ret = new HashSet<>(); + try { + if (!isExtensionSupported(mCameraId, extension, mChars)) { + throw new IllegalArgumentException("Unsupported extension"); + } + + Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = + initializeExtension(extension); + extenders.second.onInit(mCameraId, mChars.getNativeMetadata()); + extenders.second.init(mCameraId, mChars.getNativeMetadata()); + CameraMetadataNative captureResultMeta = + extenders.second.getAvailableCaptureResultKeys(); + + if (captureResultMeta != null) { + int[] resultKeys = captureResultMeta.get( + CameraCharacteristics.REQUEST_AVAILABLE_RESULT_KEYS); + if (resultKeys == null) { + throw new AssertionError("android.request.availableResultKeys must be non-null " + + "in the characteristics"); + } + CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta); + Object crKey = CaptureResult.Key.class; + Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey; + + ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped, + resultKeys, /*includeSynthetic*/ false)); + + // Jpeg quality, orientation and sensor timestamp must always be supported + if (!ret.contains(CaptureResult.JPEG_QUALITY)) { + ret.add(CaptureResult.JPEG_QUALITY); + } + if (!ret.contains(CaptureResult.JPEG_ORIENTATION)) { + ret.add(CaptureResult.JPEG_ORIENTATION); + } + if (!ret.contains(CaptureResult.SENSOR_TIMESTAMP)) { + ret.add(CaptureResult.SENSOR_TIMESTAMP); + } + } + extenders.second.onDeInit(); + } catch (RemoteException e) { + throw new IllegalStateException("Failed to query the available capture result keys!"); + } finally { + unregisterClient(clientId); + } + + return Collections.unmodifiableSet(ret); + } } diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java index 5892f682dd49..ee3441fc16f1 100644 --- a/core/java/android/hardware/camera2/CameraExtensionSession.java +++ b/core/java/android/hardware/camera2/CameraExtensionSession.java @@ -172,6 +172,32 @@ public abstract class CameraExtensionSession implements AutoCloseable { int sequenceId) { // default empty implementation } + + /** + * This method is called when an image capture has fully completed and all the + * result metadata is available. + * + * <p>This callback will only be called in case + * {@link CameraExtensionCharacteristics#getAvailableCaptureResultKeys} returns a valid + * non-empty list.</p> + * + * <p>The default implementation of this method does nothing.</p> + * + * @param session The session received during + * {@link StateCallback#onConfigured(CameraExtensionSession)} + * @param request The request that was given to the CameraDevice + * @param result The total output metadata from the capture, which only includes the + * capture result keys advertised as supported in + * {@link CameraExtensionCharacteristics#getAvailableCaptureResultKeys}. + * + * @see #capture + * @see #setRepeatingRequest + * @see CameraExtensionCharacteristics#getAvailableCaptureResultKeys + */ + public void onCaptureResultAvailable(@NonNull CameraExtensionSession session, + @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + // default empty implementation + } } /** diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 803684da6ddb..95238ee4e6a6 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1210,6 +1210,36 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT = 18; + /** + * <p>The camera device supports selecting a per-stream use case via + * {@link android.hardware.camera2.params.OutputConfiguration#setStreamUseCase } + * so that the device can optimize camera pipeline parameters such as tuning, sensor + * mode, or ISP settings for a specific user scenario. + * Some sample usages of this capability are: + * * Distinguish high quality YUV captures from a regular YUV stream where + * the image quality may not be as good as the JPEG stream, or + * * Use one stream to serve multiple purposes: viewfinder, video recording and + * still capture. This is common with applications that wish to apply edits equally + * to preview, saved images, and saved videos.</p> + * <p>This capability requires the camera device to support the following + * stream use cases: + * * DEFAULT for backward compatibility where the application doesn't set + * a stream use case + * * PREVIEW for live viewfinder and in-app image analysis + * * STILL_CAPTURE for still photo capture + * * VIDEO_RECORD for recording video clips + * * PREVIEW_VIDEO_STILL for one single stream used for viewfinder, video + * recording, and still capture. + * * VIDEO_CALL for long running video calls</p> + * <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES } + * lists all of the supported stream use cases.</p> + * <p>Refer to {@link android.hardware.camera2.CameraDevice#createCaptureSession } for the + * mandatory stream combinations involving stream use cases, which can also be queried + * via {@link android.hardware.camera2.params.MandatoryStreamCombination }.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE = 19; + // // Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP // @@ -1336,6 +1366,89 @@ public abstract class CameraMetadata<TKey> { public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; // + // Enumeration values for CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES + // + + /** + * <p>Default stream use case.</p> + * <p>This use case is the same as when the application doesn't set any use case for + * the stream. The camera device uses the properties of the output target, such as + * format, dataSpace, or surface class type, to optimize the image processing pipeline.</p> + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES + */ + public static final int SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT = 0x0; + + /** + * <p>Live stream shown to the user.</p> + * <p>Optimized for performance and usability as a viewfinder, but not necessarily for + * image quality. The output is not meant to be persisted as saved images or video.</p> + * <p>No stall if android.control.<em> are set to FAST; may have stall if android.control.</em> + * are set to HIGH_QUALITY. This use case has the same behavior as the default + * SurfaceView and SurfaceTexture targets. Additionally, this use case can be used for + * in-app image analysis.</p> + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES + */ + public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW = 0x1; + + /** + * <p>Still photo capture.</p> + * <p>Optimized for high-quality high-resolution capture, and not expected to maintain + * preview-like frame rates.</p> + * <p>The stream may have stalls regardless of whether android.control.* is HIGH_QUALITY. + * This use case has the same behavior as the default JPEG and RAW related formats.</p> + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES + */ + public static final int SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE = 0x2; + + /** + * <p>Recording video clips.</p> + * <p>Optimized for high-quality video capture, including high-quality image stabilization + * if supported by the device and enabled by the application. As a result, may produce + * output frames with a substantial lag from real time, to allow for highest-quality + * stabilization or other processing. As such, such an output is not suitable for drawing + * to screen directly, and is expected to be persisted to disk or similar for later + * playback or processing. Only streams that set the VIDEO_RECORD use case are guaranteed + * to have video stabilization applied when the video stabilization control is set + * to ON, as opposed to PREVIEW_STABILIZATION.</p> + * <p>This use case has the same behavior as the default MediaRecorder and MediaCodec + * targets.</p> + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES + */ + public static final int SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD = 0x3; + + /** + * <p>One single stream used for combined purposes of preview, video, and still capture.</p> + * <p>For such multi-purpose streams, the camera device aims to make the best tradeoff + * between the individual use cases. For example, the STILL_CAPTURE use case by itself + * may have stalls for achieving best image quality. But if combined with PREVIEW and + * VIDEO_RECORD, the camera device needs to trade off the additional image processing + * for speed so that preview and video recording aren't slowed down.</p> + * <p>Similarly, VIDEO_RECORD may produce frames with a substantial lag, but + * PREVIEW_VIDEO_STILL must have minimal output delay. This means that to enable video + * stabilization with this use case, the device must support and the app must select the + * PREVIEW_STABILIZATION mode for video stabilization.</p> + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES + */ + public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL = 0x4; + + /** + * <p>Long-running video call optimized for both power efficienty and video quality.</p> + * <p>The camera sensor may run in a lower-resolution mode to reduce power consumption + * at the cost of some image and digital zoom quality. Unlike VIDEO_RECORD, VIDEO_CALL + * outputs are expected to work in dark conditions, so are usually accompanied with + * variable frame rate settings to allow sufficient exposure time in low light.</p> + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES + */ + public static final int SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL = 0x5; + + /** + * <p>Vendor defined use cases. These depend on the vendor implementation.</p> + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES + * @hide + */ + public static final int SCALER_AVAILABLE_STREAM_USE_CASES_VENDOR_START = 0x10000; + + // // Enumeration values for CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT // diff --git a/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl index 022b084f613b..3c5f5ff63a34 100644 --- a/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl +++ b/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl @@ -17,6 +17,7 @@ package android.hardware.camera2.extension; import android.view.Surface; import android.hardware.camera2.extension.CaptureBundle; +import android.hardware.camera2.extension.IProcessResultImpl; import android.hardware.camera2.extension.Size; /** @hide */ @@ -25,5 +26,5 @@ interface ICaptureProcessorImpl void onOutputSurface(in Surface surface, int imageFormat); void onResolutionUpdate(in Size size); void onImageFormatUpdate(int imageFormat); - void process(in List<CaptureBundle> capturelist); + void process(in List<CaptureBundle> capturelist, in IProcessResultImpl resultCallback); } diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl index 3ebf63793b79..a8a7866e5ca4 100644 --- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl +++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl @@ -39,4 +39,6 @@ interface IImageCaptureExtenderImpl int getMaxCaptureStage(); @nullable List<SizeList> getSupportedResolutions(); LatencyRange getEstimatedCaptureLatencyRange(in Size outputSize); + CameraMetadataNative getAvailableCaptureRequestKeys(); + CameraMetadataNative getAvailableCaptureResultKeys(); } diff --git a/core/java/android/hardware/camera2/extension/IPreviewImageProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewImageProcessorImpl.aidl index f7e4023c5067..ecd098be9d3b 100644 --- a/core/java/android/hardware/camera2/extension/IPreviewImageProcessorImpl.aidl +++ b/core/java/android/hardware/camera2/extension/IPreviewImageProcessorImpl.aidl @@ -17,6 +17,7 @@ package android.hardware.camera2.extension; import android.hardware.camera2.impl.CameraMetadataNative; import android.view.Surface; +import android.hardware.camera2.extension.IProcessResultImpl; import android.hardware.camera2.extension.ParcelImage; import android.hardware.camera2.extension.Size; @@ -26,5 +27,6 @@ interface IPreviewImageProcessorImpl void onOutputSurface(in Surface surface, int imageFormat); void onResolutionUpdate(in Size size); void onImageFormatUpdate(int imageFormat); - void process(in ParcelImage image, in CameraMetadataNative result, int sequenceId); + void process(in ParcelImage image, in CameraMetadataNative result, int sequenceId, + in IProcessResultImpl resultCallback); } diff --git a/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl b/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl new file mode 100644 index 000000000000..4114edb37a8c --- /dev/null +++ b/core/java/android/hardware/camera2/extension/IProcessResultImpl.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.camera2.extension; + +import android.hardware.camera2.impl.CameraMetadataNative; + +/** @hide */ +interface IProcessResultImpl +{ + void onCaptureCompleted(long shutterTimestamp, in CameraMetadataNative results); +} diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 9d2c901ed049..cd392ced2081 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -105,7 +105,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @RequiresPermission(android.Manifest.permission.CAMERA) public static CameraAdvancedExtensionSessionImpl createCameraAdvancedExtensionSession( @NonNull CameraDevice cameraDevice, @NonNull Context ctx, - @NonNull ExtensionSessionConfiguration config) + @NonNull ExtensionSessionConfiguration config, int sessionId) throws CameraAccessException, RemoteException { long clientId = CameraExtensionCharacteristics.registerClient(ctx); if (clientId < 0) { @@ -135,6 +135,11 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes throw new IllegalArgumentException("Unsupported dynamic range profile: " + c.getDynamicRangeProfile()); } + if (c.getStreamUseCase() != + CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT) { + throw new IllegalArgumentException("Unsupported stream use case: " + + c.getStreamUseCase()); + } } int suitableSurfaceCount = 0; diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 9b19fc4d3ef2..3cb0c93d8409 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -2495,10 +2495,10 @@ public class CameraDeviceImpl extends CameraDevice if (CameraExtensionCharacteristics.areAdvancedExtensionsSupported()) { mCurrentAdvancedExtensionSession = CameraAdvancedExtensionSessionImpl.createCameraAdvancedExtensionSession( - this, mContext, extensionConfiguration); + this, mContext, extensionConfiguration, mNextSessionId++); } else { mCurrentExtensionSession = CameraExtensionSessionImpl.createCameraExtensionSession( - this, mContext, extensionConfiguration); + this, mContext, extensionConfiguration, mNextSessionId++); } } catch (RemoteException e) { throw new CameraAccessException(CameraAccessException.CAMERA_ERROR); diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionForwardProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionForwardProcessor.java index bf4593260a70..d148d87fc9d8 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionForwardProcessor.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionForwardProcessor.java @@ -19,6 +19,7 @@ package android.hardware.camera2.impl; import android.annotation.SuppressLint; import android.hardware.camera2.CameraExtensionCharacteristics; import android.hardware.camera2.extension.IPreviewImageProcessorImpl; +import android.hardware.camera2.extension.IProcessResultImpl; import android.hardware.camera2.extension.ParcelImage; import android.hardware.camera2.TotalCaptureResult; import android.media.Image; @@ -114,12 +115,12 @@ public class CameraExtensionForwardProcessor { } } - public void process(ParcelImage image, TotalCaptureResult totalCaptureResult) - throws RemoteException { + public void process(ParcelImage image, TotalCaptureResult totalCaptureResult, + IProcessResultImpl resultCallback) throws RemoteException { if ((mIntermediateSurface != null) && (mIntermediateSurface.isValid()) && !mOutputAbandoned) { mProcessor.process(image, totalCaptureResult.getNativeMetadata(), - totalCaptureResult.getSequenceId()); + totalCaptureResult.getSequenceId(), resultCallback); } } diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java index 425f22c31306..1514a2be5de8 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java @@ -24,6 +24,7 @@ import android.graphics.ImageFormat; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.extension.CaptureBundle; import android.hardware.camera2.extension.ICaptureProcessorImpl; +import android.hardware.camera2.extension.IProcessResultImpl; import android.media.Image; import android.media.Image.Plane; import android.media.ImageReader; @@ -183,11 +184,13 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl { int cropLeft, int cropTop, int cropRight, int cropBottom, int rot90); - public void process(List<CaptureBundle> captureBundle) throws RemoteException { + @Override + public void process(List<CaptureBundle> captureBundle, IProcessResultImpl captureCallback) + throws RemoteException { JpegParameters jpegParams = getJpegParameters(captureBundle); try { mJpegParameters.add(jpegParams); - mProcessor.process(captureBundle); + mProcessor.process(captureBundle, captureCallback); } catch (Exception e) { mJpegParameters.remove(jpegParams); throw e; diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index c8ecfd0bdea9..a50db57e48cd 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -36,6 +36,7 @@ import android.hardware.camera2.extension.ICaptureProcessorImpl; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; import android.hardware.camera2.extension.IInitializeSessionCallback; import android.hardware.camera2.extension.IPreviewExtenderImpl; +import android.hardware.camera2.extension.IProcessResultImpl; import android.hardware.camera2.extension.IRequestUpdateProcessorImpl; import android.hardware.camera2.extension.ParcelImage; import android.hardware.camera2.TotalCaptureResult; @@ -67,6 +68,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; public final class CameraExtensionSessionImpl extends CameraExtensionSession { @@ -83,6 +85,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private final StateCallback mCallbacks; private final List<Size> mSupportedPreviewSizes; private final InitializeSessionHandler mInitializeHandler; + private final int mSessionId; + private final Set<CaptureRequest.Key> mSupportedRequestKeys; + private final Set<CaptureResult.Key> mSupportedResultKeys; + private boolean mCaptureResultsSupported; private CameraCaptureSession mCaptureSession = null; private Surface mCameraRepeatingSurface, mClientRepeatingRequestSurface; @@ -121,7 +127,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { public static CameraExtensionSessionImpl createCameraExtensionSession( @NonNull CameraDevice cameraDevice, @NonNull Context ctx, - @NonNull ExtensionSessionConfiguration config) + @NonNull ExtensionSessionConfiguration config, + int sessionId) throws CameraAccessException, RemoteException { long clientId = CameraExtensionCharacteristics.registerClient(ctx); if (clientId < 0) { @@ -151,6 +158,11 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { throw new IllegalArgumentException("Unsupported dynamic range profile: " + c.getDynamicRangeProfile()); } + if (c.getStreamUseCase() != + CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT) { + throw new IllegalArgumentException("Unsupported stream use case: " + + c.getStreamUseCase()); + } } Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = @@ -197,7 +209,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { repeatingRequestSurface, burstCaptureSurface, config.getStateCallback(), - config.getExecutor()); + config.getExecutor(), + sessionId, + extensionChars.getAvailableCaptureRequestKeys(config.getExtension()), + extensionChars.getAvailableCaptureResultKeys(config.getExtension())); session.initialize(); @@ -212,7 +227,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface, @NonNull StateCallback callback, - @NonNull Executor executor) { + @NonNull Executor executor, + int sessionId, + @NonNull Set<CaptureRequest.Key> requestKeys, + @Nullable Set<CaptureResult.Key> resultKeys) { mExtensionClientId = extensionClientId; mImageExtender = imageExtender; mPreviewExtender = previewExtender; @@ -227,6 +245,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mHandler = new Handler(mHandlerThread.getLooper()); mInitialized = false; mInitializeHandler = new InitializeSessionHandler(); + mSessionId = sessionId; + mSupportedRequestKeys = requestKeys; + mSupportedResultKeys = resultKeys; + mCaptureResultsSupported = !resultKeys.isEmpty(); } private void initializeRepeatingRequestPipeline() throws RemoteException { @@ -483,7 +505,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { return captureStageList; } - private static List<CaptureRequest> createBurstRequest(CameraDevice cameraDevice, + private List<CaptureRequest> createBurstRequest(CameraDevice cameraDevice, List<CaptureStageImpl> captureStageList, CaptureRequest clientRequest, Surface target, int captureTemplate, Map<CaptureRequest, Integer> captureMap) { CaptureRequest.Builder requestBuilder; @@ -495,16 +517,13 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { return null; } - // This will override the extension capture stage jpeg parameters with the user set - // jpeg quality and rotation. This will guarantee that client configured jpeg - // parameters always have highest priority. - Integer jpegRotation = clientRequest.get(CaptureRequest.JPEG_ORIENTATION); - if (jpegRotation != null) { - captureStage.parameters.set(CaptureRequest.JPEG_ORIENTATION, jpegRotation); - } - Byte jpegQuality = clientRequest.get(CaptureRequest.JPEG_QUALITY); - if (jpegQuality != null) { - captureStage.parameters.set(CaptureRequest.JPEG_QUALITY, jpegQuality); + // This will guarantee that client configured + // parameters always have the highest priority. + for (CaptureRequest.Key requestKey : mSupportedRequestKeys){ + Object value = clientRequest.get(requestKey); + if (value != null) { + captureStage.parameters.set(requestKey, value); + } } requestBuilder.addTarget(target); @@ -517,10 +536,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { return ret; } - private static CaptureRequest createRequest(CameraDevice cameraDevice, - List<CaptureStageImpl> captureStageList, - Surface target, - int captureTemplate) throws CameraAccessException { + private CaptureRequest createRequest(CameraDevice cameraDevice, + List<CaptureStageImpl> captureStageList, Surface target, int captureTemplate, + CaptureRequest clientRequest) throws CameraAccessException { CaptureRequest.Builder requestBuilder; requestBuilder = cameraDevice.createCaptureRequest(captureTemplate); if (target != null) { @@ -528,14 +546,35 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } CaptureRequest ret = requestBuilder.build(); + CameraMetadataNative nativeMeta = ret.getNativeMetadata(); for (CaptureStageImpl captureStage : captureStageList) { if (captureStage != null) { - CameraMetadataNative.update(ret.getNativeMetadata(), captureStage.parameters); + CameraMetadataNative.update(nativeMeta, captureStage.parameters); + } + } + + if (clientRequest != null) { + // This will guarantee that client configured + // parameters always have the highest priority. + for (CaptureRequest.Key requestKey : mSupportedRequestKeys) { + Object value = clientRequest.get(requestKey); + if (value != null) { + nativeMeta.set(requestKey, value); + } } } + return ret; } + private CaptureRequest createRequest(CameraDevice cameraDevice, + List<CaptureStageImpl> captureStageList, + Surface target, + int captureTemplate) throws CameraAccessException { + return createRequest(cameraDevice, captureStageList, target, captureTemplate, + /*clientRequest*/ null); + } + @Override public int capture(@NonNull CaptureRequest request, @NonNull Executor executor, @@ -629,12 +668,17 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } private int setRepeatingRequest(CaptureStageImpl captureStage, - CameraCaptureSession.CaptureCallback requestHandler) + CameraCaptureSession.CaptureCallback requestHandler) throws CameraAccessException { + return setRepeatingRequest(captureStage, requestHandler, /*clientRequest*/ null); + } + + private int setRepeatingRequest(CaptureStageImpl captureStage, + CameraCaptureSession.CaptureCallback requestHandler, CaptureRequest clientRequest) throws CameraAccessException { ArrayList<CaptureStageImpl> captureStageList = new ArrayList<>(); captureStageList.add(captureStage); - CaptureRequest repeatingRequest = createRequest(mCameraDevice, - captureStageList, mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW); + CaptureRequest repeatingRequest = createRequest(mCameraDevice, captureStageList, + mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW, clientRequest); return mCaptureSession.setSingleRepeatingRequest(repeatingRequest, new CameraExtensionUtils.HandlerExecutor(mHandler), requestHandler); } @@ -843,6 +887,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private ImageCallback mImageCallback = null; private boolean mCaptureFailed = false; + private CaptureResultHandler mCaptureResultHandler = null; public BurstRequestHandler(@NonNull CaptureRequest request, @NonNull Executor executor, @NonNull ExtensionCaptureCallback callbacks, @@ -963,20 +1008,18 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP); if (timestamp != null) { + if (mCaptureResultsSupported && (mCaptureResultHandler == null)) { + mCaptureResultHandler = new CaptureResultHandler(mClientRequest, mExecutor, + mCallbacks, result.getSessionId()); + } if (mImageProcessor != null) { if (mCapturePendingMap.indexOfKey(timestamp) >= 0) { Image img = mCapturePendingMap.get(timestamp).first; - mCaptureStageMap.put(stageId, - new Pair<>(img, - result)); + mCaptureStageMap.put(stageId, new Pair<>(img, result)); checkAndFireBurstProcessing(); } else { - mCapturePendingMap.put(timestamp, - new Pair<>(null, - stageId)); - mCaptureStageMap.put(stageId, - new Pair<>(null, - result)); + mCapturePendingMap.put(timestamp, new Pair<>(null, stageId)); + mCaptureStageMap.put(stageId, new Pair<>(null, result)); } } else { mCaptureRequestMap.clear(); @@ -986,6 +1029,18 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { () -> mCallbacks .onCaptureProcessStarted(CameraExtensionSessionImpl.this, mClientRequest)); + + if (mCaptureResultHandler != null) { + CameraMetadataNative captureResults = new CameraMetadataNative(); + for (CaptureResult.Key key : mSupportedResultKeys) { + Object value = result.get(key); + if (value != null) { + captureResults.set(key, value); + } + } + mCaptureResultHandler.onCaptureCompleted(timestamp, + captureResults); + } } finally { Binder.restoreCallingIdentity(ident); } @@ -1013,7 +1068,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { List<CaptureBundle> captureList = initializeParcelable(mCaptureStageMap, jpegOrientation, jpegQuality); try { - mImageProcessor.process(captureList); + mImageProcessor.process(captureList, mCaptureResultHandler); } catch (RemoteException e) { Log.e(TAG, "Failed to process multi-frame request! Extension service " + "does not respond!"); @@ -1228,6 +1283,43 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } } + private class CaptureResultHandler extends IProcessResultImpl.Stub { + private final Executor mExecutor; + private final ExtensionCaptureCallback mCallbacks; + private final CaptureRequest mClientRequest; + private final int mRequestId; + + public CaptureResultHandler(@NonNull CaptureRequest clientRequest, + @NonNull Executor executor, @NonNull ExtensionCaptureCallback listener, + int requestId) { + mClientRequest = clientRequest; + mExecutor = executor; + mCallbacks = listener; + mRequestId = requestId; + } + + @Override + public void onCaptureCompleted(long shutterTimestamp, CameraMetadataNative result) { + if (result == null) { + Log.e(TAG,"Invalid capture result!"); + return; + } + + result.set(CaptureResult.SENSOR_TIMESTAMP, shutterTimestamp); + TotalCaptureResult totalResult = new TotalCaptureResult(mCameraDevice.getId(), result, + mClientRequest, mRequestId, shutterTimestamp, new ArrayList<CaptureResult>(), + mSessionId, new PhysicalCaptureResultInfo[0]); + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute( + () -> mCallbacks.onCaptureResultAvailable(CameraExtensionSessionImpl.this, + mClientRequest, totalResult)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + // This handler can operate in two modes: // 1) Using valid client callbacks, which means camera buffers will be propagated the // registered output surfaces and clients will be notified accordingly. @@ -1242,6 +1334,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private OnImageAvailableListener mImageCallback = null; private LongSparseArray<Pair<Image, TotalCaptureResult>> mPendingResultMap = new LongSparseArray<>(); + private CaptureResultHandler mCaptureResultHandler = null; private boolean mRequestUpdatedNeeded = false; @@ -1375,6 +1468,11 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { synchronized (mInterfaceLock) { final Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP); if (timestamp != null) { + if (mCaptureResultsSupported && mClientNotificationsEnabled && + (mCaptureResultHandler == null)) { + mCaptureResultHandler = new CaptureResultHandler(mClientRequest, mExecutor, + mCallbacks, result.getSessionId()); + } if (mPreviewProcessorType == IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY) { CaptureStageImpl captureStage = null; @@ -1387,7 +1485,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } if (captureStage != null) { try { - setRepeatingRequest(captureStage, this); + setRepeatingRequest(captureStage, this, request); mRequestUpdatedNeeded = true; } catch (IllegalStateException e) { // This is possible in case the camera device closes and the @@ -1406,7 +1504,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { ParcelImage parcelImage = initializeParcelImage( mPendingResultMap.get(timestamp).first); try { - mPreviewImageProcessor.process(parcelImage, result); + mPreviewImageProcessor.process(parcelImage, result, + mCaptureResultHandler); } catch (RemoteException e) { processStatus = false; Log.e(TAG, "Extension service does not respond during " + @@ -1444,6 +1543,19 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { .onCaptureProcessStarted( CameraExtensionSessionImpl.this, mClientRequest)); + if ((mCaptureResultHandler != null) && (mPreviewProcessorType != + IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR)) { + CameraMetadataNative captureResults = + new CameraMetadataNative(); + for (CaptureResult.Key key : mSupportedResultKeys) { + Object value = result.get(key); + if (value != null) { + captureResults.set(key, value); + } + } + mCaptureResultHandler.onCaptureCompleted(timestamp, + captureResults); + } } else { mExecutor.execute( () -> mCallbacks @@ -1587,7 +1699,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { ParcelImage parcelImage = initializeParcelImage(img); try { mPreviewImageProcessor.process(parcelImage, - mPendingResultMap.get(timestamp).second); + mPendingResultMap.get(timestamp).second, mCaptureResultHandler); } catch (RemoteException e) { processStatus = false; Log.e(TAG, "Extension service does not respond during " + diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 0f8bdf64e132..9b67633fe72b 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -333,6 +333,7 @@ public class CameraMetadataNative implements Parcelable { private static final int MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION = 1; private static final int MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT = 2; private static final int MANDATORY_STREAM_CONFIGURATIONS_10BIT = 3; + private static final int MANDATORY_STREAM_CONFIGURATIONS_USE_CASE = 4; private static String translateLocationProviderToProcess(final String provider) { if (provider == null) { @@ -700,6 +701,16 @@ public class CameraMetadataNative implements Parcelable { }); sGetCommandMap.put( + CameraCharacteristics.SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getMandatoryUseCaseStreamCombinations(); + } + }); + + sGetCommandMap.put( CameraCharacteristics.CONTROL_MAX_REGIONS_AE.getNativeKey(), new GetCommand() { @Override @SuppressWarnings("unchecked") @@ -1413,6 +1424,9 @@ public class CameraMetadataNative implements Parcelable { case MANDATORY_STREAM_CONFIGURATIONS_10BIT: combs = build.getAvailableMandatory10BitStreamCombinations(); break; + case MANDATORY_STREAM_CONFIGURATIONS_USE_CASE: + combs = build.getAvailableMandatoryStreamUseCaseCombinations(); + break; default: combs = build.getAvailableMandatoryStreamCombinations(); } @@ -1446,6 +1460,10 @@ public class CameraMetadataNative implements Parcelable { return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_DEFAULT); } + private MandatoryStreamCombination[] getMandatoryUseCaseStreamCombinations() { + return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_USE_CASE); + } + private StreamConfigurationMap getStreamConfigurationMap() { StreamConfiguration[] configurations = getBase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS); diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index 32c15da4a909..0d93c985b793 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -26,6 +26,9 @@ import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.OutputConfiguration.StreamUseCase; +import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.HashCodeHelpers; import android.media.CamcorderProfile; import android.util.Log; @@ -63,6 +66,7 @@ public final class MandatoryStreamCombination { private final boolean mIsUltraHighResolution; private final boolean mIsMaximumSize; private final boolean mIs10BitCapable; + private final int mStreamUseCase; /** * Create a new {@link MandatoryStreamInformation}. @@ -141,6 +145,30 @@ public final class MandatoryStreamCombination { public MandatoryStreamInformation(@NonNull List<Size> availableSizes, @Format int format, boolean isMaximumSize, boolean isInput, boolean isUltraHighResolution, boolean is10BitCapable) { + this(availableSizes, format, isMaximumSize, isInput, isUltraHighResolution, + is10BitCapable, CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT); + } + + /** + * Create a new {@link MandatoryStreamInformation}. + * + * @param availableSizes List of possible stream sizes. + * @param format Image format. + * @param isMaximumSize Whether this is a maximum size stream. + * @param isInput Flag indicating whether this stream is input. + * @param isUltraHighResolution Flag indicating whether this is a ultra-high resolution + * stream. + * @param is10BitCapable Flag indicating whether this stream is able to support 10-bit + * @param streamUseCase The stream use case. + * + * @throws IllegalArgumentException + * if sizes is empty or if the format was not user-defined in + * ImageFormat/PixelFormat. + * @hide + */ + public MandatoryStreamInformation(@NonNull List<Size> availableSizes, @Format int format, + boolean isMaximumSize, boolean isInput, boolean isUltraHighResolution, + boolean is10BitCapable, @StreamUseCase int streamUseCase) { if (availableSizes.isEmpty()) { throw new IllegalArgumentException("No available sizes"); } @@ -150,6 +178,7 @@ public final class MandatoryStreamCombination { mIsInput = isInput; mIsUltraHighResolution = isUltraHighResolution; mIs10BitCapable = is10BitCapable; + mStreamUseCase = streamUseCase; } /** @@ -272,6 +301,20 @@ public final class MandatoryStreamCombination { } /** + * Retrieve the mandatory stream use case. + * + * <p>If this {@link MandatoryStreamInformation} is part of a mandatory stream + * combination for stream use cases, the return value will be a non-DEFAULT value. + * For {@link MandatoryStreamInformation} belonging to other mandatory stream + * combinations, the return value will be DEFAULT. </p> + * + * @return the integer stream use case. + */ + public @StreamUseCase int getStreamUseCase() { + return mStreamUseCase; + } + + /** * Check if this {@link MandatoryStreamInformation} is equal to another * {@link MandatoryStreamInformation}. * @@ -292,6 +335,7 @@ public final class MandatoryStreamCombination { final MandatoryStreamInformation other = (MandatoryStreamInformation) obj; if ((mFormat != other.mFormat) || (mIsInput != other.mIsInput) || (mIsUltraHighResolution != other.mIsUltraHighResolution) || + (mStreamUseCase != other.mStreamUseCase) || (mAvailableSizes.size() != other.mAvailableSizes.size())) { return false; } @@ -308,7 +352,8 @@ public final class MandatoryStreamCombination { @Override public int hashCode() { return HashCodeHelpers.hashCode(mFormat, Boolean.hashCode(mIsInput), - Boolean.hashCode(mIsUltraHighResolution), mAvailableSizes.hashCode()); + Boolean.hashCode(mIsUltraHighResolution), mAvailableSizes.hashCode(), + mStreamUseCase); } } @@ -316,6 +361,21 @@ public final class MandatoryStreamCombination { private final boolean mIsReprocessable; private final ArrayList<MandatoryStreamInformation> mStreamsInformation = new ArrayList<MandatoryStreamInformation>(); + + /** + * Short hand for stream use cases + */ + private static final int STREAM_USE_CASE_PREVIEW = + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW; + private static final int STREAM_USE_CASE_STILL_CAPTURE = + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE; + private static final int STREAM_USE_CASE_RECORD = + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD; + private static final int STREAM_USE_CASE_PREVIEW_VIDEO_STILL = + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL; + private static final int STREAM_USE_CASE_VIDEO_CALL = + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL; + /** * Create a new {@link MandatoryStreamCombination}. * @@ -411,15 +471,15 @@ public final class MandatoryStreamCombination { private static final class StreamTemplate { public int mFormat; public SizeThreshold mSizeThreshold; - public boolean mIsInput; + public int mStreamUseCase; public StreamTemplate(int format, SizeThreshold sizeThreshold) { - this(format, sizeThreshold, /*isInput*/false); + this(format, sizeThreshold, CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT); } public StreamTemplate(@Format int format, @NonNull SizeThreshold sizeThreshold, - boolean isInput) { + @StreamUseCase int streamUseCase) { mFormat = format; mSizeThreshold = sizeThreshold; - mIsInput = isInput; + mStreamUseCase = streamUseCase; } } @@ -1034,6 +1094,174 @@ public final class MandatoryStreamCombination { "High-resolution recording with video snapshot"), }; + private static StreamCombinationTemplate sStreamUseCaseCombinations[] = { + // Single stream combinations + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW) }, + "Simple preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW) }, + "Simple in-application image processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD, + STREAM_USE_CASE_RECORD) }, + "Simple video recording"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, + STREAM_USE_CASE_RECORD) }, + "Simple in-application video processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE) }, + "Simple JPEG still capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE) }, + "Simple YUV still capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p, + STREAM_USE_CASE_PREVIEW_VIDEO_STILL) }, + "Multi-purpose stream for preview, video and still capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p, + STREAM_USE_CASE_PREVIEW_VIDEO_STILL) }, + "Multi-purpose YUV stream for preview, video and still capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p, + STREAM_USE_CASE_VIDEO_CALL) }, + "Simple video call"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p, + STREAM_USE_CASE_VIDEO_CALL) }, + "Simple YUV video call"), + + // 2-stream combinations + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Preview with JPEG still image capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Preview with YUV still image capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD, + STREAM_USE_CASE_RECORD)}, + "Preview with video recording"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, + STREAM_USE_CASE_RECORD)}, + "Preview with in-application video processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW)}, + "Preview with in-application image processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p, + STREAM_USE_CASE_VIDEO_CALL)}, + "Preview with video call"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p, + STREAM_USE_CASE_VIDEO_CALL)}, + "Preview with YUV video call"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p, + STREAM_USE_CASE_PREVIEW_VIDEO_STILL), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Multi-purpose stream with JPEG still capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s1440p, + STREAM_USE_CASE_PREVIEW_VIDEO_STILL), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Multi-purpose stream with YUV still capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p, + STREAM_USE_CASE_PREVIEW_VIDEO_STILL), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Multi-purpose YUV stream with JPEG still capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s1440p, + STREAM_USE_CASE_PREVIEW_VIDEO_STILL), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Multi-purpose YUV stream with YUV still capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW, + STREAM_USE_CASE_STILL_CAPTURE), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE)}, + "YUV and JPEG concurrent still image capture (for testing)"), + + // 3-stream combinations + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD, + STREAM_USE_CASE_RECORD), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Preview, video record and JPEG video snapshot"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD, + STREAM_USE_CASE_RECORD), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Preview, video record and YUV video snapshot"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, + STREAM_USE_CASE_RECORD), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Preview, in-application video processing and JPEG video snapshot"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, + STREAM_USE_CASE_RECORD), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Preview, in-application video processing and YUV video snapshot"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Preview, in-application image processing, and JPEG still image capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW, + STREAM_USE_CASE_PREVIEW), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM, + STREAM_USE_CASE_STILL_CAPTURE)}, + "Preview, in-application image processing, and YUV still image capture"), + }; + /** * Helper builder class to generate a list of available mandatory stream combinations. * @hide @@ -1153,6 +1381,76 @@ public final class MandatoryStreamCombination { } /** + * Retrieve a list of all available mandatory stream combinations with stream use cases. + * when the camera device has {@link + * CameraMetdata.REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE} capability. + * + * @return a non-modifiable list of supported mandatory stream combinations with stream + * use cases. Null in case the device doesn't have {@link + * CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE} + * capability. + */ + public @NonNull List<MandatoryStreamCombination> + getAvailableMandatoryStreamUseCaseCombinations() { + if (!isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE)) { + return null; + } + + HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes = + enumerateAvailableSizes(); + if (availableSizes == null) { + Log.e(TAG, "Available size enumeration failed!"); + return null; + } + + ArrayList<MandatoryStreamCombination> availableStreamCombinations = new ArrayList<>(); + availableStreamCombinations.ensureCapacity(sStreamUseCaseCombinations.length); + for (StreamCombinationTemplate combTemplate : sStreamUseCaseCombinations) { + ArrayList<MandatoryStreamInformation> streamsInfo = + new ArrayList<MandatoryStreamInformation>(); + streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length); + + for (StreamTemplate template : combTemplate.mStreamTemplates) { + List<Size> sizes = null; + Pair<SizeThreshold, Integer> pair; + pair = new Pair<SizeThreshold, Integer>(template.mSizeThreshold, + new Integer(template.mFormat)); + sizes = availableSizes.get(pair); + + MandatoryStreamInformation streamInfo; + boolean isMaximumSize = + (template.mSizeThreshold == SizeThreshold.MAXIMUM); + try { + streamInfo = new MandatoryStreamInformation(sizes, template.mFormat, + isMaximumSize, /*isInput*/false, /*isUltraHighResolution*/false, + /*is10BitCapable*/ false, template.mStreamUseCase); + } catch (IllegalArgumentException e) { + Log.e(TAG, "No available sizes found for format: " + template.mFormat + + " size threshold: " + template.mSizeThreshold + " combination: " + + combTemplate.mDescription); + return null; + } + streamsInfo.add(streamInfo); + } + + MandatoryStreamCombination streamCombination; + try { + streamCombination = new MandatoryStreamCombination(streamsInfo, + combTemplate.mDescription, /*isReprocessable*/ false); + } catch (IllegalArgumentException e) { + Log.e(TAG, "No stream information for mandatory combination: " + + combTemplate.mDescription); + return null; + } + + availableStreamCombinations.add(streamCombination); + } + + return Collections.unmodifiableList(availableStreamCombinations); + } + + /** * Retrieve a list of all available mandatory concurrent stream combinations. * This method should only be called for devices which are listed in combinations returned * by CameraManager.getConcurrentCameraIds. @@ -1632,6 +1930,8 @@ public final class MandatoryStreamCombination { Size recordingMaxSize = new Size(0, 0); Size previewMaxSize = new Size(0, 0); Size vgaSize = new Size(640, 480); + Size s720pSize = new Size(1280, 720); + Size s1440pSize = new Size(1920, 1440); // For external camera, or hidden physical camera, CamcorderProfile may not be // available, so get maximum recording size using stream configuration map. if (isExternalCamera() || mIsHiddenPhysicalCamera) { @@ -1682,6 +1982,12 @@ public final class MandatoryStreamCombination { pair = new Pair<SizeThreshold, Integer>(SizeThreshold.MAXIMUM, intFormat); availableSizes.put(pair, Arrays.asList(sizes)); + + pair = new Pair<SizeThreshold, Integer>(SizeThreshold.s720p, intFormat); + availableSizes.put(pair, getSizesWithinBound(sizes, s720pSize)); + + pair = new Pair<SizeThreshold, Integer>(SizeThreshold.s1440p, intFormat); + availableSizes.put(pair, getSizesWithinBound(sizes, s1440pSize)); } return availableSizes; diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index f2b881ba7758..d8295c962154 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -159,6 +159,17 @@ public final class OutputConfiguration implements Parcelable { CameraMetadata.SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION}) public @interface SensorPixelMode {}; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"STREAM_USE_CASE_"}, value = + {CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT, + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW, + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE, + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD, + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL, + CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL}) + public @interface StreamUseCase {}; + /** * Create a new {@link OutputConfiguration} instance with a {@link Surface}. * @@ -355,6 +366,7 @@ public final class OutputConfiguration implements Parcelable { mIsMultiResolution = false; mSensorPixelModesUsed = new ArrayList<Integer>(); mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; + mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT; } /** @@ -453,6 +465,7 @@ public final class OutputConfiguration implements Parcelable { mIsMultiResolution = false; mSensorPixelModesUsed = new ArrayList<Integer>(); mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; + mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT; } /** @@ -730,6 +743,73 @@ public final class OutputConfiguration implements Parcelable { } /** + * Set stream use case for this OutputConfiguration + * + * <p>Stream use case is used to describe the purpose of the stream, whether it's for live + * preview, still image capture, video recording, or their combinations. This flag is useful + * for scenarios where the immediate consumer target isn't sufficient to indicate the stream's + * usage.</p> + * + * <p>The main difference beteween stream use case and capture intent is that the former + * enables the camera device to optimize camera hardware and software pipelines based on user + * scenarios for each stream, whereas the latter is mainly a hint to camera to decide + * optimal 3A strategy that's applicable to the whole session. The camera device carries out + * configurations such as selecting tuning parameters, choosing camera sensor mode, and + * constructing image processing pipeline based on the streams's use cases. Capture intents are + * then used to fine tune 3A behaviors such as adjusting AE/AF convergence speed, and capture + * intents may change during the lifetime of a session. For example, for a session with a + * PREVIEW_VIDEO_STILL use case stream and a STILL_CAPTURE use case stream, the capture intents + * may be PREVIEW with fast 3A convergence speed and flash metering with automatic control for + * live preview, STILL_CAPTURE with best 3A parameters for still photo capture, or VIDEO_RECORD + * with slower 3A convergence speed for better video playback experience.</p> + * + * <p>The supported stream use cases supported by a camera device can be queried by + * {@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES}.</p> + * + * <p>The mandatory stream combinations involving stream use cases can be found at {@link + * android.hardware.camera2.CameraDevice#createCaptureSession}, as well as queried via + * {@link android.hardware.camera2.params.MandatoryStreamCombination}. The application is + * strongly recommended to select one of the guaranteed stream combinations where all streams' + * use cases are set to non-DEFAULT values. If the application chooses a stream combination + * not in the mandatory list, the camera device may ignore some use case flags due to + * hardware constraints or implementation details.</p> + * + * <p>This function must be called before {@link CameraDevice#createCaptureSession} or {@link + * CameraDevice#createCaptureSessionByOutputConfigurations}. Calling this function after + * {@link CameraDevice#createCaptureSession} or + * {@link CameraDevice#createCaptureSessionByOutputConfigurations} has no effect to the camera + * session.</p> + * + * @param streamUseCase The stream use case to be set. + * + * @throws IllegalArgumentException If the streamUseCase isn't within the range of valid + * values. + */ + public void setStreamUseCase(@StreamUseCase int streamUseCase) { + // Verify that the value is in range + int maxUseCaseValue = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL; + if (streamUseCase > maxUseCaseValue && + streamUseCase < CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VENDOR_START) { + throw new IllegalArgumentException("Not a valid stream use case value " + + streamUseCase); + } + + mStreamUseCase = streamUseCase; + } + + /** + * Get the current stream use case + * + * <p>If no {@link #setStreamUseCase} is called first, this function returns + * {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT DEFAULT}.</p> + * + * @return the currently set stream use case + */ + public int getStreamUseCase() { + return mStreamUseCase; + } + + /** * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration} * instance. * @@ -756,6 +836,7 @@ public final class OutputConfiguration implements Parcelable { this.mIsMultiResolution = other.mIsMultiResolution; this.mSensorPixelModesUsed = other.mSensorPixelModesUsed; this.mDynamicRangeProfile = other.mDynamicRangeProfile; + this.mStreamUseCase = other.mStreamUseCase; } /** @@ -774,6 +855,8 @@ public final class OutputConfiguration implements Parcelable { String physicalCameraId = source.readString(); boolean isMultiResolutionOutput = source.readInt() == 1; int[] sensorPixelModesUsed = source.createIntArray(); + int streamUseCase = source.readInt(); + checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); int dynamicRangeProfile = source.readInt(); DynamicRangeProfiles.checkProfileValue(dynamicRangeProfile); @@ -801,6 +884,7 @@ public final class OutputConfiguration implements Parcelable { mIsMultiResolution = isMultiResolutionOutput; mSensorPixelModesUsed = convertIntArrayToIntegerList(sensorPixelModesUsed); mDynamicRangeProfile = dynamicRangeProfile; + mStreamUseCase = streamUseCase; } /** @@ -917,6 +1001,7 @@ public final class OutputConfiguration implements Parcelable { // writeList doesn't seem to work well with Integer list. dest.writeIntArray(convertIntegerToIntList(mSensorPixelModesUsed)); dest.writeInt(mDynamicRangeProfile); + dest.writeInt(mStreamUseCase); } /** @@ -947,7 +1032,8 @@ public final class OutputConfiguration implements Parcelable { mConfiguredDataspace != other.mConfiguredDataspace || mConfiguredGenerationId != other.mConfiguredGenerationId || !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) || - mIsMultiResolution != other.mIsMultiResolution) + mIsMultiResolution != other.mIsMultiResolution || + mStreamUseCase != other.mStreamUseCase) return false; if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) { return false; @@ -985,7 +1071,7 @@ public final class OutputConfiguration implements Parcelable { mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), - mDynamicRangeProfile); + mDynamicRangeProfile, mStreamUseCase); } return HashCodeHelpers.hashCode( @@ -994,7 +1080,7 @@ public final class OutputConfiguration implements Parcelable { mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), - mDynamicRangeProfile); + mDynamicRangeProfile, mStreamUseCase); } private static final String TAG = "OutputConfiguration"; @@ -1028,4 +1114,6 @@ public final class OutputConfiguration implements Parcelable { private ArrayList<Integer> mSensorPixelModesUsed; // Dynamic range profile private int mDynamicRangeProfile; + // Stream use case + private int mStreamUseCase; } diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java index dbe7a418b0fc..c3113799ad45 100644 --- a/core/java/android/hardware/lights/Light.java +++ b/core/java/android/hardware/lights/Light.java @@ -38,6 +38,12 @@ public final class Light implements Parcelable { /** Type for lights that indicate microphone usage */ public static final int LIGHT_TYPE_MICROPHONE = 8; + /** Type for lights that indicate camera usage + * + * @hide + */ + public static final int LIGHT_TYPE_CAMERA = 9; + // These enum values start from 10001 to avoid collision with expanding of HAL light types. /** * Type for lights that indicate a monochrome color LED light. diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index 83fc7276eabc..c3bb381bb740 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -219,7 +219,7 @@ final class NavigationBarController { // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary. final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT | (mShouldShowImeSwitcherWhenImeIsShown - ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN + ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0); navigationBarView.setNavigationIconHints(hints); } @@ -470,7 +470,7 @@ final class NavigationBarController { } final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT | (shouldShowImeSwitcherWhenImeIsShown - ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN : 0); + ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0); navigationBarView.setNavigationIconHints(hints); } diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java index 42847784dd2b..a2d71054c65d 100644 --- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java @@ -270,7 +270,7 @@ public final class NavigationBarView extends FrameLayout { // Update IME button visibility, a11y and rotate button always overrides the appearance final boolean imeSwitcherVisible = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0; + (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0; getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE); getBackButton().setVisibility(View.VISIBLE); diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 6284f56c8258..dc241066a60a 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -72,9 +72,9 @@ interface INetworkPolicyManager { SubscriptionPlan getSubscriptionPlan(in NetworkTemplate template); void notifyStatsProviderWarningOrLimitReached(); SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage); - void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage); + void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, long expirationDurationMillis, String callingPackage); String getSubscriptionPlansOwner(int subId); - void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, in int[] networkTypes, long timeoutMillis, String callingPackage); + void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, in int[] networkTypes, long expirationDurationMillis, String callingPackage); void factoryReset(String subscriber); diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 9122adfece53..d8f098eb880b 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -482,8 +482,8 @@ public class NetworkPolicyManager { * @param networkTypes the network types this override applies to. If no * network types are specified, override values will be ignored. * {@see TelephonyManager#getAllNetworkTypes()} - * @param timeoutMillis the timeout after which the requested override will - * be automatically cleared, or {@code 0} to leave in the + * @param expirationDurationMillis the duration after which the requested override + * will be automatically cleared, or {@code 0} to leave in the * requested state until explicitly cleared, or the next reboot, * whichever happens first * @param callingPackage the name of the package making the call. @@ -491,11 +491,11 @@ public class NetworkPolicyManager { */ public void setSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, @SubscriptionOverrideMask int overrideValue, - @NonNull @Annotation.NetworkType int[] networkTypes, long timeoutMillis, + @NonNull @Annotation.NetworkType int[] networkTypes, long expirationDurationMillis, @NonNull String callingPackage) { try { mService.setSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes, - timeoutMillis, callingPackage); + expirationDurationMillis, callingPackage); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -506,13 +506,16 @@ public class NetworkPolicyManager { * * @param subId the subscriber this relationship applies to. * @param plans the list of plans. + * @param expirationDurationMillis the duration after which the subscription plans + * will be automatically cleared, or {@code 0} to leave the plans until + * explicitly cleared, or the next reboot, whichever happens first * @param callingPackage the name of the package making the call * @hide */ public void setSubscriptionPlans(int subId, @NonNull SubscriptionPlan[] plans, - @NonNull String callingPackage) { + long expirationDurationMillis, @NonNull String callingPackage) { try { - mService.setSubscriptionPlans(subId, plans, callingPackage); + mService.setSubscriptionPlans(subId, plans, expirationDurationMillis, callingPackage); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c4fe1a44d161..8538b7d6be95 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -17322,6 +17322,12 @@ public final class Settings { "clockwork_long_press_to_assistant_enabled"; /* + * Whether the device has Cooldown Mode enabled. + * @hide + */ + public static final String COOLDOWN_MODE_ON = "cooldown_mode_on"; + + /* * Whether the device has Wet Mode/ Touch Lock Mode enabled. * @hide */ diff --git a/core/java/android/service/games/IGameSessionController.aidl b/core/java/android/service/games/IGameSessionController.aidl index 84311dc0aedf..fd994044775f 100644 --- a/core/java/android/service/games/IGameSessionController.aidl +++ b/core/java/android/service/games/IGameSessionController.aidl @@ -24,6 +24,6 @@ import com.android.internal.infra.AndroidFuture; */ oneway interface IGameSessionController { void takeScreenshot(int taskId, in AndroidFuture gameScreenshotResultFuture); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY)") void restartGame(in int taskId); } diff --git a/core/java/android/view/OnBackInvokedDispatcher.java b/core/java/android/view/OnBackInvokedDispatcher.java index f3ca531f2a42..5c4ee89bfbd3 100644 --- a/core/java/android/view/OnBackInvokedDispatcher.java +++ b/core/java/android/view/OnBackInvokedDispatcher.java @@ -17,8 +17,13 @@ package android.view; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.annotation.TestApi; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.os.Build; import java.lang.annotation.Retention; @@ -32,13 +37,29 @@ import java.lang.annotation.RetentionPolicy; * Attribute updates are proactively pushed to the window manager if they change the dispatch * target (a.k.a. the callback to be invoked next), or its behavior. */ -public abstract class OnBackInvokedDispatcher { +public interface OnBackInvokedDispatcher { + /** + * Enables dispatching the "back" action via {@link android.view.OnBackInvokedDispatcher}. + * + * When enabled, the following APIs are no longer invoked: + * <ul> + * <li> {@link android.app.Activity#onBackPressed} + * <li> {@link android.app.Dialog#onBackPressed} + * <li> {@link android.view.KeyEvent#KEYCODE_BACK} is no longer dispatched. + * </ul> + * + * @hide + */ + @TestApi + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + long DISPATCH_BACK_INVOCATION_AHEAD_OF_TIME = 195946584L; /** @hide */ - public static final String TAG = "OnBackInvokedDispatcher"; + String TAG = "OnBackInvokedDispatcher"; /** @hide */ - public static final boolean DEBUG = Build.isDebuggable(); + boolean DEBUG = Build.isDebuggable(); /** @hide */ @IntDef({ @@ -46,18 +67,26 @@ public abstract class OnBackInvokedDispatcher { PRIORITY_OVERLAY, }) @Retention(RetentionPolicy.SOURCE) - public @interface Priority{} + @interface Priority{} /** * Priority level of {@link OnBackInvokedCallback}s for overlays such as menus and * navigation drawers that should receive back dispatch before non-overlays. */ - public static final int PRIORITY_OVERLAY = 1000000; + int PRIORITY_OVERLAY = 1000000; /** * Default priority level of {@link OnBackInvokedCallback}s. */ - public static final int PRIORITY_DEFAULT = 0; + int PRIORITY_DEFAULT = 0; + + /** + * Priority level of {@link OnBackInvokedCallback}s registered by the system. + * + * System back animation will play when the callback to receive dispatch has this priority. + * @hide + */ + int PRIORITY_SYSTEM = -1; /** * Registers a {@link OnBackInvokedCallback}. @@ -69,10 +98,11 @@ public abstract class OnBackInvokedDispatcher { * registered, the existing instance (no matter its priority) will be * unregistered and registered again. * @param priority The priority of the callback. + * @throws {@link IllegalArgumentException} if the priority is negative. */ @SuppressLint("SamShouldBeLast") - public abstract void registerOnBackInvokedCallback( - @NonNull OnBackInvokedCallback callback, @Priority int priority); + void registerOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback, @Priority @IntRange(from = 0) int priority); /** * Unregisters a {@link OnBackInvokedCallback}. @@ -80,5 +110,20 @@ public abstract class OnBackInvokedDispatcher { * @param callback The callback to be unregistered. Does nothing if the callback has not been * registered. */ - public abstract void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback); + void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback); + + /** + * Returns the most prioritized callback to receive back dispatch next. + * @hide + */ + @Nullable + default OnBackInvokedCallback getTopCallback() { + return null; + } + + /** + * Registers a {@link OnBackInvokedCallback} with system priority. + * @hide + */ + default void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3d8653554efd..ee4a934482db 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -317,6 +317,11 @@ public final class ViewRootImpl implements ViewParent, */ private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher(); + /** + * Compatibility {@link OnBackInvokedCallback} that dispatches KEYCODE_BACK events + * to view root for apps using legacy back behavior. + */ + private OnBackInvokedCallback mCompatOnBackInvokedCallback; /** * Callback for notifying about global configuration changes. @@ -1190,6 +1195,14 @@ public final class ViewRootImpl implements ViewParent, mTmpFrames.displayFrame, mTempRect2, mTmpFrames.frame); setFrame(mTmpFrames.frame); registerBackCallbackOnWindow(); + if (WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) { + // For apps requesting legacy back behavior, we add a compat callback that + // dispatches {@link KeyEvent#KEYCODE_BACK} to their root views. + // This way from system point of view, these apps are providing custom + // {@link OnBackInvokedCallback}s, and will not play system back animations + // for them. + registerCompatOnBackInvokedCallback(); + } if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; @@ -5931,7 +5944,7 @@ public final class ViewRootImpl implements ViewParent, } } - private boolean isBack(InputEvent event) { + boolean isBack(InputEvent event) { if (event instanceof KeyEvent) { return ((KeyEvent) event).getKeyCode() == KeyEvent.KEYCODE_BACK; } else { @@ -6471,6 +6484,19 @@ public final class ViewRootImpl implements ViewParent, return FINISH_NOT_HANDLED; } + if (isBack(event) + && mContext != null + && !WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) { + // Invoke the appropriate {@link OnBackInvokedCallback} if the new back + // navigation should be used, and the key event is not handled by anything else. + OnBackInvokedCallback topCallback = + getOnBackInvokedDispatcher().getTopCallback(); + if (topCallback != null) { + topCallback.onBackInvoked(); + return FINISH_HANDLED; + } + } + // This dispatch is for windows that don't have a Window.Callback. Otherwise, // the Window.Callback usually will have already called this (see // DecorView.superDispatchKeyEvent) leaving this call a no-op. @@ -8414,6 +8440,7 @@ public final class ViewRootImpl implements ViewParent, mAdded = false; } + unregisterCompatOnBackInvokedCallback(); mOnBackInvokedDispatcher.detachFromWindow(); WindowManagerGlobal.getInstance().doRemoveView(this); } @@ -10782,6 +10809,38 @@ public final class ViewRootImpl implements ViewParent, mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow); } + private void sendBackKeyEvent(int action) { + long when = SystemClock.uptimeMillis(); + final KeyEvent ev = new KeyEvent(when, when, action, + KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, + KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + InputDevice.SOURCE_KEYBOARD); + + ev.setDisplayId(mContext.getDisplay().getDisplayId()); + if (mView != null) { + mView.dispatchKeyEvent(ev); + } + } + + private void registerCompatOnBackInvokedCallback() { + mCompatOnBackInvokedCallback = new OnBackInvokedCallback() { + @Override + public void onBackInvoked() { + sendBackKeyEvent(KeyEvent.ACTION_DOWN); + sendBackKeyEvent(KeyEvent.ACTION_UP); + } + }; + mOnBackInvokedDispatcher.registerOnBackInvokedCallback( + mCompatOnBackInvokedCallback, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + } + + private void unregisterCompatOnBackInvokedCallback() { + if (mCompatOnBackInvokedCallback != null) { + mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mCompatOnBackInvokedCallback); + } + } + @Override public void setTouchableRegion(Region r) { if (r != null) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0fe2ed51beb6..9efa583a894a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -10821,8 +10821,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener && mSavedMarqueeModeLayout.getLineWidth(0) > width)); } + /** + * @hide + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private void startMarquee() { + protected void startMarquee() { // Do not ellipsize EditText if (getKeyListener() != null) return; @@ -10848,7 +10851,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private void stopMarquee() { + /** + * @hide + */ + protected void stopMarquee() { if (mMarquee != null && !mMarquee.isStopped()) { mMarquee.stop(); } diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java index 509bbd4de389..4de977a91d05 100644 --- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java +++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java @@ -42,7 +42,7 @@ import java.util.List; * * @hide */ -public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher { +public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher { /** * List of pair representing an {@link OnBackInvokedCallback} and its associated priority. @@ -60,14 +60,16 @@ public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher { Log.v(TAG, String.format("Pending register %s. Actual=%s", callback, mActualDispatcherOwner)); } - synchronized (mLock) { - mCallbacks.add(Pair.create(callback, priority)); - if (mActualDispatcherOwner != null) { - mActualDispatcherOwner.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( - callback, priority); - } - + if (priority < 0) { + throw new IllegalArgumentException("Application registered OnBackInvokedCallback " + + "cannot have negative priority. Priority: " + priority); } + registerOnBackInvokedCallbackUnchecked(callback, priority); + } + + @Override + public void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { + registerOnBackInvokedCallbackUnchecked(callback, PRIORITY_SYSTEM); } @Override @@ -82,6 +84,17 @@ public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher { } } + private void registerOnBackInvokedCallbackUnchecked( + @NonNull OnBackInvokedCallback callback, int priority) { + synchronized (mLock) { + mCallbacks.add(Pair.create(callback, priority)); + if (mActualDispatcherOwner != null) { + mActualDispatcherOwner.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + callback, priority); + } + } + } + /** * Transfers all the pending callbacks to the provided dispatcher. * <p> @@ -99,8 +112,12 @@ public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher { dispatcher)); } for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) { - dispatcher.registerOnBackInvokedCallback(callbackPair.first, - callbackPair.second); + int priority = callbackPair.second; + if (priority >= 0) { + dispatcher.registerOnBackInvokedCallback(callbackPair.first, priority); + } else { + dispatcher.registerSystemOnBackInvokedCallback(callbackPair.first); + } } mCallbacks.clear(); } diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 29786046e49c..d37d3b42872f 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -18,8 +18,10 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.compat.CompatChanges; import android.os.Handler; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.Log; import android.view.IWindow; import android.view.IWindowSession; @@ -31,7 +33,7 @@ import java.util.HashMap; import java.util.TreeMap; /** - * Provides window based implementation of {@link OnBackInvokedDispatcher}. + * Provides window based implementation of {@link android.view.OnBackInvokedDispatcher}. * * Callbacks with higher priorities receive back dispatching first. * Within the same priority, callbacks receive back dispatching in the reverse order @@ -39,16 +41,19 @@ import java.util.TreeMap; * * When the top priority callback is updated, the new callback is propagated to the Window Manager * if the window the instance is associated with has been attached. It is allowed to register / - * unregister {@link OnBackInvokedCallback}s before the window is attached, although callbacks - * will not receive dispatches until window attachment. + * unregister {@link android.view.OnBackInvokedCallback}s before the window is attached, although + * callbacks will not receive dispatches until window attachment. * * @hide */ -public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher { +public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private IWindowSession mWindowSession; private IWindow mWindow; private static final String TAG = "WindowOnBackDispatcher"; private static final boolean DEBUG = false; + private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; + private static final boolean IS_BACK_PREDICTABILITY_ENABLED = SystemProperties + .getInt(BACK_PREDICTABILITY_PROP, 0) > 0; /** The currently most prioritized callback. */ @Nullable @@ -82,6 +87,15 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher { @Override public void registerOnBackInvokedCallback( @NonNull OnBackInvokedCallback callback, @Priority int priority) { + if (priority < 0) { + throw new IllegalArgumentException("Application registered OnBackInvokedCallback " + + "cannot have negative priority. Priority: " + priority); + } + registerOnBackInvokedCallbackUnchecked(callback, priority); + } + + private void registerOnBackInvokedCallbackUnchecked( + @NonNull OnBackInvokedCallback callback, @Priority int priority) { if (!mOnBackInvokedCallbacks.containsKey(priority)) { mOnBackInvokedCallbacks.put(priority, new ArrayList<>()); } @@ -120,6 +134,11 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher { } } + @Override + public void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { + registerOnBackInvokedCallbackUnchecked(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM); + } + /** Clears all registered callbacks on the instance. */ public void clear() { mAllCallbacks.clear(); @@ -198,4 +217,21 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher { Handler.getMain().post(() -> mCallback.onBackInvoked()); } } + + @Override + public OnBackInvokedCallback getTopCallback() { + return mTopCallback == null ? null : mTopCallback.getCallback(); + } + + /** + * Returns if the legacy back behavior should be used. + * + * Legacy back behavior dispatches KEYCODE_BACK instead of invoking the application registered + * {@link android.view.OnBackInvokedCallback}. + * + */ + public static boolean shouldUseLegacyBack() { + return !CompatChanges.isChangeEnabled(DISPATCH_BACK_INVOCATION_AHEAD_OF_TIME) + || !IS_BACK_PREDICTABILITY_ENABLED; + } } diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 59d6005ba193..54325e590347 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -448,4 +448,7 @@ <color name="accessibility_magnification_background">#F50D60</color> <color name="accessibility_daltonizer_background">#00BCD4</color> <color name="accessibility_color_inversion_background">#546E7A</color> + + <!-- Color of camera light when camera is in use --> + <color name="camera_privacy_light">#FFFFFF</color> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 33efbce15eea..2b25c3eb099b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4722,4 +4722,6 @@ <java-symbol type="bool" name="config_lowPowerStandbySupported" /> <java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" /> <java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" /> + + <java-symbol type="color" name="camera_privacy_light"/> </resources> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 3badfe4ae15b..83c4024e7867 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -401,6 +401,7 @@ applications that come with the platform <!-- Permission required for CTS test - TrustTestCases --> <permission name="android.permission.PROVIDE_TRUST_AGENT" /> <permission name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <permission name="android.permission.TRUST_LISTENER" /> <!-- Permissions required for Incremental CTS tests --> <permission name="com.android.permission.USE_INSTALLER_V2"/> <permission name="android.permission.LOADER_USAGE_STATS"/> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index b310dd638e6c..9a6df23ca971 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -18,9 +18,12 @@ package com.android.wm.shell.back; import android.view.MotionEvent; +import com.android.wm.shell.common.annotations.ExternalThread; + /** - * Interface for SysUI to get access to the Back animation related methods. + * Interface for external process to get access to the Back animation related methods. */ +@ExternalThread public interface BackAnimation { /** @@ -32,4 +35,11 @@ public interface BackAnimation { * Sets whether the back gesture is past the trigger threshold or not. */ void setTriggerBack(boolean triggerBack); + + /** + * Returns a binder that can be passed to an external process to update back animations. + */ + default IBackAnimation createExternalInterface() { + return null; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 229e8ee04982..a5140c3aafff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.back; +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import android.animation.Animator; @@ -26,6 +27,7 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.WindowConfiguration; +import android.content.Context; import android.graphics.Point; import android.graphics.PointF; import android.hardware.HardwareBuffer; @@ -35,16 +37,18 @@ import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceControl; import android.window.BackNavigationInfo; +import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ShellMainThread; /** * Controls the window animation run when a user initiates a back gesture. */ -public class BackAnimationController { +public class BackAnimationController implements RemoteCallable<BackAnimationController> { private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; public static final boolean IS_ENABLED = SystemProperties @@ -72,18 +76,26 @@ public class BackAnimationController { private BackNavigationInfo mBackNavigationInfo; private final SurfaceControl.Transaction mTransaction; private final IActivityTaskManager mActivityTaskManager; + private final Context mContext; + @Nullable + private IOnBackInvokedCallback mBackToLauncherCallback; - public BackAnimationController(@ShellMainThread ShellExecutor shellExecutor) { - this(shellExecutor, new SurfaceControl.Transaction(), ActivityTaskManager.getService()); + public BackAnimationController( + @ShellMainThread ShellExecutor shellExecutor, + Context context) { + this(shellExecutor, new SurfaceControl.Transaction(), ActivityTaskManager.getService(), + context); } @VisibleForTesting BackAnimationController(@NonNull ShellExecutor shellExecutor, @NonNull SurfaceControl.Transaction transaction, - @NonNull IActivityTaskManager activityTaskManager) { + @NonNull IActivityTaskManager activityTaskManager, + Context context) { mShellExecutor = shellExecutor; mTransaction = transaction; mActivityTaskManager = activityTaskManager; + mContext = context; } public BackAnimation getBackAnimationImpl() { @@ -92,7 +104,27 @@ public class BackAnimationController { private final BackAnimation mBackAnimation = new BackAnimationImpl(); + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mShellExecutor; + } + private class BackAnimationImpl implements BackAnimation { + private IBackAnimationImpl mBackAnimation; + + @Override + public IBackAnimation createExternalInterface() { + if (mBackAnimation != null) { + mBackAnimation.invalidate(); + } + mBackAnimation = new IBackAnimationImpl(BackAnimationController.this); + return mBackAnimation; + } @Override public void onBackMotion(MotionEvent event) { @@ -105,6 +137,48 @@ public class BackAnimationController { } } + private static class IBackAnimationImpl extends IBackAnimation.Stub { + private BackAnimationController mController; + + IBackAnimationImpl(BackAnimationController controller) { + mController = controller; + } + + @Override + public void setBackToLauncherCallback(IOnBackInvokedCallback callback) { + executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback", + (controller) -> mController.setBackToLauncherCallback(callback)); + } + + @Override + public void clearBackToLauncherCallback() { + executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback", + (controller) -> mController.clearBackToLauncherCallback()); + } + + @Override + public void onBackToLauncherAnimationFinished() { + executeRemoteCallWithTaskPermission(mController, "onBackToLauncherAnimationFinished", + (controller) -> mController.onBackToLauncherAnimationFinished()); + } + + void invalidate() { + mController = null; + } + } + + private void setBackToLauncherCallback(IOnBackInvokedCallback callback) { + mBackToLauncherCallback = callback; + } + + private void clearBackToLauncherCallback() { + mBackToLauncherCallback = null; + } + + private void onBackToLauncherAnimationFinished() { + finishAnimation(); + } + /** * Called when a new motion event needs to be transferred to this * {@link BackAnimationController} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl new file mode 100644 index 000000000000..6311f879fd45 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.back; + +import android.window.IOnBackInvokedCallback; + +/** + * Interface for Launcher process to register back invocation callbacks. + */ +interface IBackAnimation { + + /** + * Sets a {@link IOnBackInvokedCallback} to be invoked when + * back navigation has type {@link BackNavigationInfo#TYPE_RETURN_TO_HOME}. + */ + void setBackToLauncherCallback(in IOnBackInvokedCallback callback); + + /** + * Clears the previously registered {@link IOnBackInvokedCallback}. + */ + void clearBackToLauncherCallback(); + + /** + * Notifies Shell that the back to launcher animation has fully finished + * (including the transition animation that runs after the finger is lifted). + * + * At this point the top window leash (if one was created) should be ready to be released. + * //TODO: Remove once we play the transition animation through shell transitions. + */ + void onBackToLauncherAnimationFinished(); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 8f4cfb0a49a4..4d279bc4e927 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -49,7 +49,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; /** - * Controls to show/update restart-activity buttons on Tasks based on whether the foreground + * Controller to show/update compat UI components on Tasks based on whether the foreground * activities are in compatibility mode. */ public class CompatUIController implements OnDisplaysChangedListener, @@ -228,8 +228,7 @@ public class CompatUIController implements OnDisplaysChangedListener, final CompatUIWindowManager compatUIWindowManager = createLayout(context, taskInfo, taskListener); mActiveLayouts.put(taskInfo.taskId, compatUIWindowManager); - compatUIWindowManager.createLayout(showOnDisplay(taskInfo.displayId), - taskInfo.topActivityInSizeCompat, taskInfo.cameraCompatControlState); + compatUIWindowManager.createLayout(showOnDisplay(taskInfo.displayId), taskInfo); } @VisibleForTesting @@ -254,9 +253,7 @@ public class CompatUIController implements OnDisplaysChangedListener, if (layout == null) { return; } - layout.updateCompatInfo(taskInfo.configuration, taskListener, - showOnDisplay(layout.getDisplayId()), taskInfo.topActivityInSizeCompat, - taskInfo.cameraCompatControlState); + layout.updateCompatInfo(taskInfo, taskListener, showOnDisplay(layout.getDisplayId())); } private void removeLayout(int taskId) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index 44526b00bf0d..9c001a37e4b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -20,28 +20,16 @@ import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import android.annotation.Nullable; +import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; import android.content.Context; import android.content.res.Configuration; -import android.graphics.PixelFormat; import android.graphics.Rect; -import android.os.Binder; import android.util.Log; -import android.view.IWindow; import android.view.LayoutInflater; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.SurfaceSession; import android.view.View; -import android.view.WindowManager; -import android.view.WindowlessWindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; @@ -50,26 +38,19 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; /** - * Holds view hierarchy of a root surface and helps to inflate and manage layout for compat - * controls. + * Window manager for the Size Compat restart button and Camera Compat control. */ -class CompatUIWindowManager extends WindowlessWindowManager { +class CompatUIWindowManager extends CompatUIWindowManagerAbstract { - private static final String TAG = "CompatUIWindowManager"; + /** + * The Compat UI should be the topmost child of the Task in case there can be more than one + * child. + */ + private static final int Z_ORDER = Integer.MAX_VALUE; - private final SyncTransactionQueue mSyncQueue; private final CompatUIController.CompatUICallback mCallback; - private final int mDisplayId; - private final int mTaskId; - private final Rect mStableBounds; - private Context mContext; - private Configuration mTaskConfig; - private ShellTaskOrganizer.TaskListener mTaskListener; - private DisplayLayout mDisplayLayout; - - // Remember the last reported states in case visibility changes due to keyguard or - // IME updates. + // Remember the last reported states in case visibility changes due to keyguard or IME updates. @VisibleForTesting boolean mHasSizeCompat; @CameraCompatControlState @@ -82,147 +63,83 @@ class CompatUIWindowManager extends WindowlessWindowManager { @Nullable @VisibleForTesting - CompatUILayout mCompatUILayout; - - @Nullable - private SurfaceControlViewHost mViewHost; - @Nullable - private SurfaceControl mLeash; + CompatUILayout mLayout; CompatUIWindowManager(Context context, Configuration taskConfig, SyncTransactionQueue syncQueue, CompatUIController.CompatUICallback callback, int taskId, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, - boolean hasShownSizeCompatHint, boolean hasShownCameraCompatHint) { - super(taskConfig, null /* rootSurface */, null /* hostInputToken */); - mContext = context; - mSyncQueue = syncQueue; + boolean hasShownSizeCompatHint, boolean hasShownCameraCompatHint) { + super(context, taskConfig, syncQueue, taskId, taskListener, displayLayout); mCallback = callback; - mTaskConfig = taskConfig; - mDisplayId = mContext.getDisplayId(); - mTaskId = taskId; - mTaskListener = taskListener; - mDisplayLayout = displayLayout; mShouldShowSizeCompatHint = !hasShownSizeCompatHint; mShouldShowCameraCompatHint = !hasShownCameraCompatHint; - mStableBounds = new Rect(); - mDisplayLayout.getStableBounds(mStableBounds); } @Override - public void setConfiguration(Configuration configuration) { - super.setConfiguration(configuration); - mContext = mContext.createConfigurationContext(configuration); + protected int getZOrder() { + return Z_ORDER; + } + + + @Override + protected @Nullable View getLayout() { + return mLayout; } @Override - protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { - // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later. - final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) - .setContainerLayer() - .setName("CompatUILeash") - .setHidden(false) - .setCallsite("CompatUIWindowManager#attachToParentSurface"); - attachToParentSurface(builder); - mLeash = builder.build(); - b.setParent(mLeash); + protected void removeLayout() { + mLayout = null; } - /** Creates the layout for compat controls. */ - void createLayout(boolean show, boolean hasSizeCompat, - @CameraCompatControlState int cameraCompatControlState) { - mHasSizeCompat = hasSizeCompat; - mCameraCompatControlState = cameraCompatControlState; - if (!show || mCompatUILayout != null) { - // Wait until compat controls should be visible. - return; - } + @Override + protected boolean eligibleToShowLayout() { + return mHasSizeCompat || shouldShowCameraControl(); + } + + /** + * Updates the internal state with respect to {@code taskInfo} and calls {@link + * #createLayout(boolean)}. + */ + void createLayout(boolean canShow, TaskInfo taskInfo) { + mHasSizeCompat = taskInfo.topActivityInSizeCompat; + mCameraCompatControlState = taskInfo.cameraCompatControlState; + createLayout(canShow); + } + + @Override + protected View createLayout() { + mLayout = inflateLayout(); + mLayout.inject(this); - initCompatUi(); - updateSurfacePosition(); + updateVisibilityOfViews(); - if (hasSizeCompat) { + if (mHasSizeCompat) { mCallback.onSizeCompatRestartButtonAppeared(mTaskId); } + + return mLayout; } - private void createLayout(boolean show) { - createLayout(show, mHasSizeCompat, mCameraCompatControlState); + @VisibleForTesting + CompatUILayout inflateLayout() { + return (CompatUILayout) LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, + null); } - /** Called when compat info changed. */ - void updateCompatInfo(Configuration taskConfig, - ShellTaskOrganizer.TaskListener taskListener, boolean show, boolean hasSizeCompat, - @CameraCompatControlState int cameraCompatControlState) { - final Configuration prevTaskConfig = mTaskConfig; - final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener; - mTaskConfig = taskConfig; - mTaskListener = taskListener; + @Override + public void updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, + boolean canShow) { final boolean prevHasSizeCompat = mHasSizeCompat; final int prevCameraCompatControlState = mCameraCompatControlState; - mHasSizeCompat = hasSizeCompat; - mCameraCompatControlState = cameraCompatControlState; + mHasSizeCompat = taskInfo.topActivityInSizeCompat; + mCameraCompatControlState = taskInfo.cameraCompatControlState; - // Update configuration. - mContext = mContext.createConfigurationContext(taskConfig); - setConfiguration(taskConfig); - - if (mCompatUILayout == null || prevTaskListener != taskListener) { - // TaskListener changed, recreate the layout for new surface parent. - release(); - createLayout(show); - return; - } + super.updateCompatInfo(taskInfo, taskListener, canShow); if (prevHasSizeCompat != mHasSizeCompat || prevCameraCompatControlState != mCameraCompatControlState) { updateVisibilityOfViews(); } - - if (!taskConfig.windowConfiguration.getBounds() - .equals(prevTaskConfig.windowConfiguration.getBounds())) { - // Reposition the UI surfaces. - updateSurfacePosition(); - } - - if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) { - // Update layout for RTL. - mCompatUILayout.setLayoutDirection(taskConfig.getLayoutDirection()); - updateSurfacePosition(); - } - - } - - /** Called when the visibility of the UI should change. */ - void updateVisibility(boolean show) { - if (mCompatUILayout == null) { - // Layout may not have been created because it was hidden previously. - createLayout(show); - return; - } - - // Hide compat UIs when IME is showing. - final int newVisibility = show ? View.VISIBLE : View.GONE; - if (mCompatUILayout.getVisibility() != newVisibility) { - mCompatUILayout.setVisibility(newVisibility); - } - } - - /** Called when display layout changed. */ - void updateDisplayLayout(DisplayLayout displayLayout) { - final Rect prevStableBounds = mStableBounds; - final Rect curStableBounds = new Rect(); - displayLayout.getStableBounds(curStableBounds); - mDisplayLayout = displayLayout; - if (!prevStableBounds.equals(curStableBounds)) { - // Stable bounds changed, update UI surface positions. - updateSurfacePosition(); - mStableBounds.set(curStableBounds); - } - } - - /** Called when it is ready to be placed compat UI surface. */ - void attachToParentSurface(SurfaceControl.Builder b) { - mTaskListener.attachChildSurfaceToTask(mTaskId, b); } /** Called when the restart button is clicked. */ @@ -233,7 +150,7 @@ class CompatUIWindowManager extends WindowlessWindowManager { /** Called when the camera treatment button is clicked. */ void onCameraTreatmentButtonClicked() { if (!shouldShowCameraControl()) { - Log.w(TAG, "Camera compat shouldn't receive clicks in the hidden state."); + Log.w(getTag(), "Camera compat shouldn't receive clicks in the hidden state."); return; } // When a camera control is shown, only two states are allowed: "treament applied" and @@ -244,141 +161,72 @@ class CompatUIWindowManager extends WindowlessWindowManager { ? CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED : CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; mCallback.onCameraControlStateUpdated(mTaskId, mCameraCompatControlState); - mCompatUILayout.updateCameraTreatmentButton(mCameraCompatControlState); + mLayout.updateCameraTreatmentButton(mCameraCompatControlState); } /** Called when the camera dismiss button is clicked. */ void onCameraDismissButtonClicked() { if (!shouldShowCameraControl()) { - Log.w(TAG, "Camera compat shouldn't receive clicks in the hidden state."); + Log.w(getTag(), "Camera compat shouldn't receive clicks in the hidden state."); return; } mCameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED; mCallback.onCameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED); - mCompatUILayout.setCameraControlVisibility(/* show= */ false); + mLayout.setCameraControlVisibility(/* show= */ false); } /** Called when the restart button is long clicked. */ void onRestartButtonLongClicked() { - if (mCompatUILayout == null) { + if (mLayout == null) { return; } - mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true); + mLayout.setSizeCompatHintVisibility(/* show= */ true); } /** Called when either dismiss or treatment camera buttons is long clicked. */ void onCameraButtonLongClicked() { - if (mCompatUILayout == null) { + if (mLayout == null) { return; } - mCompatUILayout.setCameraCompatHintVisibility(/* show= */ true); + mLayout.setCameraCompatHintVisibility(/* show= */ true); } - int getDisplayId() { - return mDisplayId; - } - - int getTaskId() { - return mTaskId; - } - - /** Releases the surface control and tears down the view hierarchy. */ - void release() { - // Hiding before releasing to avoid flickering when transitioning to the Home screen. - mCompatUILayout.setVisibility(View.GONE); - mCompatUILayout = null; - - if (mViewHost != null) { - mViewHost.release(); - mViewHost = null; - } - - if (mLeash != null) { - final SurfaceControl leash = mLeash; - mSyncQueue.runInSync(t -> t.remove(leash)); - mLeash = null; - } - } - - void relayout() { - mViewHost.relayout(getWindowLayoutParams()); - updateSurfacePosition(); - } - - @VisibleForTesting - void updateSurfacePosition() { - if (mCompatUILayout == null || mLeash == null) { + @Override + protected void updateSurfacePosition(Rect taskBounds, Rect stableBounds) { + if (mLayout == null) { return; } - - // Use stable bounds to prevent controls from overlapping with system bars. - final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds(); - final Rect stableBounds = new Rect(); - mDisplayLayout.getStableBounds(stableBounds); - stableBounds.intersect(taskBounds); - // Position of the button in the container coordinate. final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? stableBounds.left - taskBounds.left - : stableBounds.right - taskBounds.left - mCompatUILayout.getMeasuredWidth(); + : stableBounds.right - taskBounds.left - mLayout.getMeasuredWidth(); final int positionY = stableBounds.bottom - taskBounds.top - - mCompatUILayout.getMeasuredHeight(); + - mLayout.getMeasuredHeight(); updateSurfacePosition(positionX, positionY); } - private int getLayoutDirection() { - return mContext.getResources().getConfiguration().getLayoutDirection(); - } - - private void updateSurfacePosition(int positionX, int positionY) { - mSyncQueue.runInSync(t -> { - if (mLeash == null || !mLeash.isValid()) { - Log.w(TAG, "The leash has been released."); - return; - } - t.setPosition(mLeash, positionX, positionY); - // The compat UI should be the topmost child of the Task in case there can be more - // than one children. - t.setLayer(mLeash, Integer.MAX_VALUE); - }); - } - - /** Inflates {@link CompatUILayout} on to the root surface. */ - private void initCompatUi() { - if (mViewHost != null) { - throw new IllegalStateException( - "A UI has already been created with this window manager."); - } - - // Construction extracted into the separate methods to allow injection for tests. - mViewHost = createSurfaceViewHost(); - mCompatUILayout = inflateCompatUILayout(); - mCompatUILayout.inject(this); - - updateVisibilityOfViews(); - - mViewHost.setView(mCompatUILayout, getWindowLayoutParams()); - } - private void updateVisibilityOfViews() { + if (mLayout == null) { + return; + } // Size Compat mode restart button. - mCompatUILayout.setRestartButtonVisibility(mHasSizeCompat); + mLayout.setRestartButtonVisibility(mHasSizeCompat); if (mHasSizeCompat && mShouldShowSizeCompatHint) { - mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true); + mLayout.setSizeCompatHintVisibility(/* show= */ true); // Only show by default for the first time. mShouldShowSizeCompatHint = false; } // Camera control for stretched issues. - mCompatUILayout.setCameraControlVisibility(shouldShowCameraControl()); + mLayout.setCameraControlVisibility(shouldShowCameraControl()); if (shouldShowCameraControl() && mShouldShowCameraCompatHint) { - mCompatUILayout.setCameraCompatHintVisibility(/* show= */ true); + mLayout.setCameraCompatHintVisibility(/* show= */ true); // Only show by default for the first time. mShouldShowCameraCompatHint = false; } if (shouldShowCameraControl()) { - mCompatUILayout.updateCameraTreatmentButton(mCameraCompatControlState); + mLayout.updateCameraTreatmentButton(mCameraCompatControlState); } } @@ -386,32 +234,4 @@ class CompatUIWindowManager extends WindowlessWindowManager { return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN && mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED; } - - @VisibleForTesting - CompatUILayout inflateCompatUILayout() { - return (CompatUILayout) LayoutInflater.from(mContext) - .inflate(R.layout.compat_ui_layout, null); - } - - @VisibleForTesting - SurfaceControlViewHost createSurfaceViewHost() { - return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); - } - - /** Gets the layout params. */ - private WindowManager.LayoutParams getWindowLayoutParams() { - // Measure how big the hint is since its size depends on the text size. - mCompatUILayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( - // Cannot be wrap_content as this determines the actual window size - mCompatUILayout.getMeasuredWidth(), mCompatUILayout.getMeasuredHeight(), - TYPE_APPLICATION_OVERLAY, - FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, - PixelFormat.TRANSLUCENT); - winParams.token = new Binder(); - winParams.setTitle(CompatUILayout.class.getSimpleName() + mTaskId); - winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; - return winParams; - } - } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java new file mode 100644 index 000000000000..b9a9db1ee800 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui; + +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import android.annotation.Nullable; +import android.app.TaskInfo; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Binder; +import android.util.Log; +import android.view.IWindow; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.SurfaceSession; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +/** + * A superclass for all Compat UI {@link WindowlessWindowManager}s that holds shared logic and + * exposes general API for {@link CompatUIController}. + * + * <p>Holds view hierarchy of a root surface and helps to inflate and manage layout. + */ +abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager { + + protected final SyncTransactionQueue mSyncQueue; + protected final int mDisplayId; + protected final int mTaskId; + + protected Context mContext; + protected Configuration mTaskConfig; + protected ShellTaskOrganizer.TaskListener mTaskListener; + protected DisplayLayout mDisplayLayout; + protected final Rect mStableBounds; + + /** + * Utility class for adding and releasing a View hierarchy for this {@link + * WindowlessWindowManager} to {@code mLeash}. + */ + @Nullable + protected SurfaceControlViewHost mViewHost; + + /** + * A surface leash to position the layout relative to the task, since we can't set position for + * the {@code mViewHost} directly. + */ + @Nullable + protected SurfaceControl mLeash; + + protected CompatUIWindowManagerAbstract(Context context, Configuration taskConfig, + SyncTransactionQueue syncQueue, int taskId, + ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout) { + super(taskConfig, null /* rootSurface */, null /* hostInputToken */); + mContext = context; + mSyncQueue = syncQueue; + mTaskConfig = taskConfig; + mDisplayId = mContext.getDisplayId(); + mTaskId = taskId; + mTaskListener = taskListener; + mDisplayLayout = displayLayout; + mStableBounds = new Rect(); + mDisplayLayout.getStableBounds(mStableBounds); + } + + /** + * Returns the z-order of this window which will be passed to the {@link SurfaceControl} once + * {@link #attachToParentSurface} is called. + * + * <p>See {@link SurfaceControl.Transaction#setLayer}. + */ + protected abstract int getZOrder(); + + /** Returns the layout of this window manager. */ + protected abstract @Nullable View getLayout(); + + /** + * Inflates and inits the layout of this window manager on to the root surface if both {@code + * canShow} and {@link #eligibleToShowLayout} are true. + * + * @param canShow whether the layout is allowed to be shown by the parent controller. + */ + void createLayout(boolean canShow) { + if (!canShow || !eligibleToShowLayout() || getLayout() != null) { + // Wait until layout should be visible. + return; + } + + if (mViewHost != null) { + throw new IllegalStateException( + "A UI has already been created with this window manager."); + } + + // Construction extracted into separate methods to allow injection for tests. + mViewHost = createSurfaceViewHost(); + mViewHost.setView(createLayout(), getWindowLayoutParams()); + + updateSurfacePosition(); + } + + /** Inflates and inits the layout of this window manager. */ + protected abstract View createLayout(); + + protected abstract void removeLayout(); + + /** + * Whether the layout is eligible to be shown according to the internal state of the subclass. + * Returns true by default if subclass doesn't override this method. + */ + protected boolean eligibleToShowLayout() { + return true; + } + + @Override + public void setConfiguration(Configuration configuration) { + super.setConfiguration(configuration); + mContext = mContext.createConfigurationContext(configuration); + } + + @Override + protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + String className = getClass().getSimpleName(); + final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) + .setContainerLayer() + .setName(className + "Leash") + .setHidden(false) + .setCallsite(className + "#attachToParentSurface"); + attachToParentSurface(builder); + mLeash = builder.build(); + b.setParent(mLeash); + + initSurface(mLeash); + } + + /** Inits the z-order of the surface. */ + private void initSurface(SurfaceControl leash) { + final int z = getZOrder(); + mSyncQueue.runInSync(t -> { + if (leash == null || !leash.isValid()) { + Log.w(getTag(), "The leash has been released."); + return; + } + t.setLayer(leash, z); + }); + } + + /** + * Called when compat info changed. + * + * @param canShow whether the layout is allowed to be shown by the parent controller. + */ + void updateCompatInfo(TaskInfo taskInfo, + ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { + final Configuration prevTaskConfig = mTaskConfig; + final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener; + mTaskConfig = taskInfo.configuration; + mTaskListener = taskListener; + + // Update configuration. + setConfiguration(mTaskConfig); + + View layout = getLayout(); + if (layout == null || prevTaskListener != taskListener) { + // TaskListener changed, recreate the layout for new surface parent. + release(); + createLayout(canShow); + return; + } + + boolean boundsUpdated = !mTaskConfig.windowConfiguration.getBounds().equals( + prevTaskConfig.windowConfiguration.getBounds()); + boolean layoutDirectionUpdated = + mTaskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection(); + if (boundsUpdated || layoutDirectionUpdated) { + // Reposition the UI surfaces. + updateSurfacePosition(); + } + + if (layout != null && layoutDirectionUpdated) { + // Update layout for RTL. + layout.setLayoutDirection(mTaskConfig.getLayoutDirection()); + } + } + + + /** + * Updates the visibility of the layout. + * + * @param canShow whether the layout is allowed to be shown by the parent controller. + */ + void updateVisibility(boolean canShow) { + View layout = getLayout(); + if (layout == null) { + // Layout may not have been created because it was hidden previously. + createLayout(canShow); + return; + } + + final int newVisibility = canShow && eligibleToShowLayout() ? View.VISIBLE : View.GONE; + if (layout.getVisibility() != newVisibility) { + layout.setVisibility(newVisibility); + } + } + + /** Called when display layout changed. */ + void updateDisplayLayout(DisplayLayout displayLayout) { + final Rect prevStableBounds = mStableBounds; + final Rect curStableBounds = new Rect(); + displayLayout.getStableBounds(curStableBounds); + mDisplayLayout = displayLayout; + if (!prevStableBounds.equals(curStableBounds)) { + // Stable bounds changed, update UI surface positions. + updateSurfacePosition(); + mStableBounds.set(curStableBounds); + } + } + + /** Called when the surface is ready to be placed under the task surface. */ + @VisibleForTesting + void attachToParentSurface(SurfaceControl.Builder b) { + mTaskListener.attachChildSurfaceToTask(mTaskId, b); + } + + int getDisplayId() { + return mDisplayId; + } + + int getTaskId() { + return mTaskId; + } + + /** Releases the surface control and tears down the view hierarchy. */ + void release() { + // Hiding before releasing to avoid flickering when transitioning to the Home screen. + View layout = getLayout(); + if (layout != null) { + layout.setVisibility(View.GONE); + } + removeLayout(); + + if (mViewHost != null) { + mViewHost.release(); + mViewHost = null; + } + + if (mLeash != null) { + final SurfaceControl leash = mLeash; + mSyncQueue.runInSync(t -> t.remove(leash)); + mLeash = null; + } + } + + /** Re-layouts the view host and updates the surface position. */ + void relayout() { + if (mViewHost == null) { + return; + } + mViewHost.relayout(getWindowLayoutParams()); + updateSurfacePosition(); + } + + /** + * Updates the position of the surface with respect to the task bounds and display layout + * stable bounds. + */ + @VisibleForTesting + void updateSurfacePosition() { + if (mLeash == null) { + return; + } + // Use stable bounds to prevent controls from overlapping with system bars. + final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds(); + final Rect stableBounds = new Rect(); + mDisplayLayout.getStableBounds(stableBounds); + stableBounds.intersect(taskBounds); + + updateSurfacePosition(taskBounds, stableBounds); + } + + /** + * Updates the position of the surface with respect to the given {@code taskBounds} and {@code + * stableBounds}. + */ + protected abstract void updateSurfacePosition(Rect taskBounds, Rect stableBounds); + + /** + * Updates the position of the surface with respect to the given {@code positionX} and {@code + * positionY}. + */ + protected void updateSurfacePosition(int positionX, int positionY) { + mSyncQueue.runInSync(t -> { + if (mLeash == null || !mLeash.isValid()) { + Log.w(getTag(), "The leash has been released."); + return; + } + t.setPosition(mLeash, positionX, positionY); + }); + } + + protected int getLayoutDirection() { + return mContext.getResources().getConfiguration().getLayoutDirection(); + } + + @VisibleForTesting + SurfaceControlViewHost createSurfaceViewHost() { + return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + } + + /** Gets the layout params. */ + private WindowManager.LayoutParams getWindowLayoutParams() { + View layout = getLayout(); + if (layout == null) { + return new WindowManager.LayoutParams(); + } + // Measure how big the hint is since its size depends on the text size. + layout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + return getWindowLayoutParams(layout.getMeasuredWidth(), layout.getMeasuredHeight()); + } + + /** Gets the layout params given the width and height of the layout. */ + private WindowManager.LayoutParams getWindowLayoutParams(int width, int height) { + final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( + // Cannot be wrap_content as this determines the actual window size + width, height, + TYPE_APPLICATION_OVERLAY, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, + PixelFormat.TRANSLUCENT); + winParams.token = new Binder(); + winParams.setTitle(getClass().getSimpleName() + mTaskId); + winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + return winParams; + } + + protected final String getTag() { + return getClass().getSimpleName(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 9f4ff7c8dc06..2e54c792ed57 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -696,11 +696,12 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Optional<BackAnimationController> provideBackAnimationController( + Context context, @ShellMainThread ShellExecutor shellExecutor ) { if (BackAnimationController.IS_ENABLED) { return Optional.of( - new BackAnimationController(shellExecutor)); + new BackAnimationController(shellExecutor, context)); } return Optional.empty(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index b738c47ef6ff..21ced0dc5981 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.verify; import android.app.IActivityTaskManager; import android.app.WindowConfiguration; +import android.content.Context; import android.hardware.HardwareBuffer; import android.os.RemoteCallback; import android.os.RemoteException; @@ -52,6 +53,9 @@ public class BackAnimationControllerTest { private final ShellExecutor mShellExecutor = new TestShellExecutor(); @Mock + private Context mContext; + + @Mock private SurfaceControl.Transaction mTransaction; @Mock @@ -63,7 +67,7 @@ public class BackAnimationControllerTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mController = new BackAnimationController( - mShellExecutor, mTransaction, mActivityTaskManager); + mShellExecutor, mTransaction, mActivityTaskManager, mContext); } private void createNavigationInfo(SurfaceControl topWindowLeash, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 4352fd3d2c27..741da3fe9f58 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -116,35 +116,17 @@ public class CompatUIControllerTest extends ShellTestCase { TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); - // Verify that the restart button is added with non-null size compat info. + // Verify that the compat controls are added with non-null size compat info. mController.onCompatInfoChanged(taskInfo, mMockTaskListener); verify(mController).createLayout(any(), eq(taskInfo), eq(mMockTaskListener)); - // Verify that the restart button is updated with non-null new size compat info. - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), - mMockTaskListener); - - verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener, - true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); - - // Verify that the restart button is updated with new camera state. - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED), - mMockTaskListener); - - verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener, - true /* show */, true /* hasSizeCompat */, + // Verify that the compat controls are updated with non-null new size compat info. + taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); + mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED), - mMockTaskListener); - - verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener, - true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, true /* canShow */); // Verify that compat controls are removed with null compat info. mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, @@ -155,7 +137,7 @@ public class CompatUIControllerTest extends ShellTestCase { clearInvocations(mMockLayout); clearInvocations(mController); - // Verify that compat controls are removed with dismissed camera state. + // Verify that compat controls are removed with no size compat and dismissed camera state. taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); @@ -245,11 +227,11 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockLayout).updateVisibility(false); // Verify button remains hidden while IME is showing. - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); + TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_HIDDEN); + mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener, - false /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, false /* canShow */); // Verify button is shown after IME is hidden. mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */); @@ -268,11 +250,11 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockLayout).updateVisibility(false); // Verify button remains hidden while keyguard is occluded. - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); + TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_HIDDEN); + mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener, - false /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, false /* canShow */); // Verify button is shown after keyguard becomes not occluded. mController.onKeyguardOccludedChanged(false); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index 353d8fe8bc52..211781798af7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -27,6 +27,9 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.app.TaskInfo.CameraCompatControlState; import android.content.res.Configuration; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; @@ -83,7 +86,7 @@ public class CompatUILayoutTest extends ShellTestCase { spyOn(mWindowManager); spyOn(mCompatUILayout); doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); - doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout(); + doReturn(mCompatUILayout).when(mWindowManager).inflateLayout(); } @Test @@ -107,8 +110,8 @@ public class CompatUILayoutTest extends ShellTestCase { @Test public void testOnClickForSizeCompatHint() { - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.createLayout(true /* show */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_HIDDEN)); final LinearLayout sizeCompatHint = mCompatUILayout.findViewById(R.id.size_compat_hint); sizeCompatHint.performClick(); @@ -117,8 +120,8 @@ public class CompatUILayoutTest extends ShellTestCase { @Test public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() { - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); + mWindowManager.createLayout(true /* show */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED)); final ImageButton button = mCompatUILayout.findViewById(R.id.camera_compat_treatment_button); button.performClick(); @@ -135,8 +138,8 @@ public class CompatUILayoutTest extends ShellTestCase { @Test public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() { - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.createLayout(true /* show */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED)); final ImageButton button = mCompatUILayout.findViewById(R.id.camera_compat_treatment_button); button.performClick(); @@ -153,8 +156,8 @@ public class CompatUILayoutTest extends ShellTestCase { @Test public void testOnCameraDismissButtonClicked() { - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.createLayout(true /* show */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED)); final ImageButton button = mCompatUILayout.findViewById(R.id.camera_compat_dismiss_button); button.performClick(); @@ -188,11 +191,19 @@ public class CompatUILayoutTest extends ShellTestCase { @Test public void testOnClickForCameraCompatHint() { - mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.createLayout(true /* show */, createTaskInfo(false /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED)); final LinearLayout hint = mCompatUILayout.findViewById(R.id.camera_compat_hint); hint.performClick(); verify(mCompatUILayout).setCameraCompatHintVisibility(/* show= */ false); } + + private static TaskInfo createTaskInfo(boolean hasSizeCompat, + @CameraCompatControlState int cameraCompatControlState) { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.topActivityInSizeCompat = hasSizeCompat; + taskInfo.cameraCompatControlState = cameraCompatControlState; + return taskInfo; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index 11c797363819..de882eae1503 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -26,8 +26,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -35,6 +35,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.app.ActivityManager; +import android.app.TaskInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.testing.AndroidTestingRunner; @@ -75,47 +77,45 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock private CompatUILayout mCompatUILayout; @Mock private SurfaceControlViewHost mViewHost; - private Configuration mTaskConfig; private CompatUIWindowManager mWindowManager; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mTaskConfig = new Configuration(); mWindowManager = new CompatUIWindowManager(mContext, new Configuration(), mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(), false /* hasShownSizeCompatHint */, false /* hasShownSizeCompatHint */); spyOn(mWindowManager); - doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout(); + doReturn(mCompatUILayout).when(mWindowManager).inflateLayout(); doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); } @Test public void testCreateSizeCompatButton() { // Not create layout if show is false. - mWindowManager.createLayout(false /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.createLayout(false /* canShow */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_HIDDEN)); - verify(mWindowManager, never()).inflateCompatUILayout(); + verify(mWindowManager, never()).inflateLayout(); // Not create hint popup. mWindowManager.mShouldShowSizeCompatHint = false; - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_HIDDEN)); - verify(mWindowManager).inflateCompatUILayout(); + verify(mWindowManager).inflateLayout(); verify(mCompatUILayout, never()).setSizeCompatHintVisibility(true /* show */); // Create hint popup. mWindowManager.release(); mWindowManager.mShouldShowSizeCompatHint = true; - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_HIDDEN)); - verify(mWindowManager, times(2)).inflateCompatUILayout(); + verify(mWindowManager, times(2)).inflateLayout(); assertNotNull(mCompatUILayout); verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */); assertFalse(mWindowManager.mShouldShowSizeCompatHint); @@ -123,10 +123,10 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Test public void testRelease() { - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_HIDDEN)); - verify(mWindowManager).inflateCompatUILayout(); + verify(mWindowManager).inflateLayout(); mWindowManager.release(); @@ -135,65 +135,104 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Test public void testUpdateCompatInfo() { - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_HIDDEN); + TaskInfo taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.createLayout(true /* canShow */, taskInfo); // No diff clearInvocations(mWindowManager); - mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */, - true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, true /* canShow */); verify(mWindowManager, never()).updateSurfacePosition(); verify(mWindowManager, never()).release(); - verify(mWindowManager, never()).createLayout(anyBoolean(), anyBoolean(), anyInt()); + verify(mWindowManager, never()).createLayout(anyBoolean()); // Change task listener, recreate button. clearInvocations(mWindowManager); final ShellTaskOrganizer.TaskListener newTaskListener = mock( ShellTaskOrganizer.TaskListener.class); - mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, - true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */); verify(mWindowManager).release(); - verify(mWindowManager).createLayout(anyBoolean(), anyBoolean(), anyInt()); + verify(mWindowManager).createLayout(true); + + // Change in Size Compat to false, hides restart button. + clearInvocations(mWindowManager); + taskInfo = createTaskInfo(false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */); + + verify(mCompatUILayout).setRestartButtonVisibility(/* show */ false); + + // Change in Size Compat to true, shows restart button. + clearInvocations(mWindowManager); + clearInvocations(mCompatUILayout); + taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */); + + verify(mCompatUILayout).setRestartButtonVisibility(/* show */ true); // Change Camera Compat state, show a control. - mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, true /* show */, - true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); + clearInvocations(mWindowManager); + clearInvocations(mCompatUILayout); + taskInfo = createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */); verify(mCompatUILayout).setCameraControlVisibility(/* show */ true); verify(mCompatUILayout).updateCameraTreatmentButton( CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); + // Change Camera Compat state, update a control. clearInvocations(mWindowManager); clearInvocations(mCompatUILayout); - // Change Camera Compat state, update a control. - mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, true /* show */, - true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + taskInfo = createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */); verify(mCompatUILayout).setCameraControlVisibility(/* show */ true); verify(mCompatUILayout).updateCameraTreatmentButton( CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + // Change Camera Compat state to hidden, hide a control. clearInvocations(mWindowManager); clearInvocations(mCompatUILayout); - // Change Camera Compat state to hidden, hide a control. - mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, - true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */); verify(mCompatUILayout).setCameraControlVisibility(/* show */ false); // Change task bounds, update position. clearInvocations(mWindowManager); - final Configuration newTaskConfiguration = new Configuration(); - newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000)); - mWindowManager.updateCompatInfo(newTaskConfiguration, newTaskListener, - true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000)); + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */); verify(mWindowManager).updateSurfacePosition(); } @Test + public void testUpdateCompatInfoLayoutNotInflatedYet() { + TaskInfo taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.createLayout(false /* canShow */, taskInfo); + + verify(mWindowManager, never()).inflateLayout(); + + // Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be + // inflated + clearInvocations(mWindowManager); + taskInfo = createTaskInfo(false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, true /* canShow */); + + verify(mWindowManager, never()).inflateLayout(); + + // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated. + clearInvocations(mWindowManager); + taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, true /* canShow */); + + verify(mWindowManager).inflateLayout(); + } + + @Test public void testUpdateDisplayLayout() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 1000; @@ -237,26 +276,25 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Test public void testUpdateVisibility() { // Create button if it is not created. - mWindowManager.mCompatUILayout = null; + mWindowManager.mLayout = null; mWindowManager.mHasSizeCompat = true; - mWindowManager.updateVisibility(true /* show */); + mWindowManager.updateVisibility(true /* canShow */); - verify(mWindowManager).createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_HIDDEN); + verify(mWindowManager).createLayout(true /* canShow */); // Hide button. clearInvocations(mWindowManager); doReturn(View.VISIBLE).when(mCompatUILayout).getVisibility(); - mWindowManager.updateVisibility(false /* show */); + mWindowManager.updateVisibility(false /* canShow */); - verify(mWindowManager, never()).createLayout(anyBoolean(), anyBoolean(), anyInt()); + verify(mWindowManager, never()).createLayout(anyBoolean(), any()); verify(mCompatUILayout).setVisibility(View.GONE); // Show button. doReturn(View.GONE).when(mCompatUILayout).getVisibility(); - mWindowManager.updateVisibility(true /* show */); + mWindowManager.updateVisibility(true /* canShow */); - verify(mWindowManager, never()).createLayout(anyBoolean(), anyBoolean(), anyInt()); + verify(mWindowManager, never()).createLayout(anyBoolean(), any()); verify(mCompatUILayout).setVisibility(View.VISIBLE); } @@ -270,8 +308,8 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Test public void testOnCameraDismissButtonClicked() { - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED)); clearInvocations(mCompatUILayout); mWindowManager.onCameraDismissButtonClicked(); @@ -281,8 +319,8 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Test public void testOnCameraTreatmentButtonClicked() { - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED)); clearInvocations(mCompatUILayout); mWindowManager.onCameraTreatmentButtonClicked(); @@ -310,10 +348,10 @@ public class CompatUIWindowManagerTest extends ShellTestCase { public void testOnRestartButtonLongClicked_showHint() { // Not create hint popup. mWindowManager.mShouldShowSizeCompatHint = false; - mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_HIDDEN)); - verify(mWindowManager).inflateCompatUILayout(); + verify(mWindowManager).inflateLayout(); verify(mCompatUILayout, never()).setSizeCompatHintVisibility(true /* show */); mWindowManager.onRestartButtonLongClicked(); @@ -325,10 +363,10 @@ public class CompatUIWindowManagerTest extends ShellTestCase { public void testOnCamerControlLongClicked_showHint() { // Not create hint popup. mWindowManager.mShouldShowCameraCompatHint = false; - mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.createLayout(true /* canShow */, createTaskInfo(false /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED)); - verify(mWindowManager).inflateCompatUILayout(); + verify(mWindowManager).inflateLayout(); verify(mCompatUILayout, never()).setCameraCompatHintVisibility(true /* show */); mWindowManager.onCameraButtonLongClicked(); @@ -339,30 +377,37 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Test public void testCreateCameraCompatControl() { // Not create layout if show is false. - mWindowManager.createLayout(false /* show */, false /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.createLayout(false /* canShow */, createTaskInfo(false /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED)); - verify(mWindowManager, never()).inflateCompatUILayout(); + verify(mWindowManager, never()).inflateLayout(); // Not create hint popup. mWindowManager.mShouldShowCameraCompatHint = false; - mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.createLayout(true /* canShow */, createTaskInfo(false /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED)); - verify(mWindowManager).inflateCompatUILayout(); + verify(mWindowManager).inflateLayout(); verify(mCompatUILayout, never()).setCameraCompatHintVisibility(true /* show */); verify(mCompatUILayout).setCameraControlVisibility(true /* show */); // Create hint popup. mWindowManager.release(); mWindowManager.mShouldShowCameraCompatHint = true; - mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */, - CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + mWindowManager.createLayout(true /* canShow */, createTaskInfo(false /* hasSizeCompat */, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED)); - verify(mWindowManager, times(2)).inflateCompatUILayout(); + verify(mWindowManager, times(2)).inflateLayout(); assertNotNull(mCompatUILayout); verify(mCompatUILayout, times(2)).setCameraControlVisibility(true /* show */); assertFalse(mWindowManager.mShouldShowCameraCompatHint); } + private static TaskInfo createTaskInfo(boolean hasSizeCompat, + @TaskInfo.CameraCompatControlState int cameraCompatControlState) { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.topActivityInSizeCompat = hasSizeCompat; + taskInfo.cameraCompatControlState = cameraCompatControlState; + return taskInfo; + } } diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index 5f02a430f384..e2e48d35a672 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -43,7 +43,6 @@ import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.StampedLock; /** * <p>The ImageReader class allows direct application access to image data @@ -676,8 +675,7 @@ public class ImageReader implements AutoCloseable { * If no handler specified and the calling thread has no looper. */ public void setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler) { - long writeStamp = mListenerLock.writeLock(); - try { + synchronized (mListenerLock) { if (listener != null) { Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); if (looper == null) { @@ -693,8 +691,6 @@ public class ImageReader implements AutoCloseable { mListenerExecutor = null; } mListener = listener; - } finally { - mListenerLock.unlockWrite(writeStamp); } } @@ -717,12 +713,9 @@ public class ImageReader implements AutoCloseable { throw new IllegalArgumentException("executor must not be null"); } - long writeStamp = mListenerLock.writeLock(); - try { + synchronized (mListenerLock) { mListenerExecutor = executor; mListener = listener; - } finally { - mListenerLock.unlockWrite(writeStamp); } } @@ -738,8 +731,6 @@ public class ImageReader implements AutoCloseable { /** * Callback that is called when a new image is available from ImageReader. * - * This callback must not modify or close the passed {@code reader}. - * * @param reader the ImageReader the callback is associated with. * @see ImageReader * @see Image @@ -898,41 +889,28 @@ public class ImageReader implements AutoCloseable { return; } - synchronized (ir.mCloseLock) { - if (!ir.mIsReaderValid) { - // It's dangerous to fire onImageAvailable() callback when the ImageReader - // is being closed, as application could acquire next image in the - // onImageAvailable() callback. - return; - } - } - final Executor executor; - final long readStamp = ir.mListenerLock.readLock(); - try { + final OnImageAvailableListener listener; + synchronized (ir.mListenerLock) { executor = ir.mListenerExecutor; - if (executor == null) { - return; - } - } finally { - ir.mListenerLock.unlockRead(readStamp); + listener = ir.mListener; + } + final boolean isReaderValid; + synchronized (ir.mCloseLock) { + isReaderValid = ir.mIsReaderValid; } - executor.execute(() -> { - // Acquire readlock to ensure that the ImageReader does not change its - // state while a listener is actively processing. - final long rStamp = ir.mListenerLock.readLock(); - try { - // Fire onImageAvailable of the latest non-null listener - // This ensures that if the listener changes while messages are in queue, the - // in-flight messages will call onImageAvailable of the new listener instead - if (ir.mListener != null) { - ir.mListener.onImageAvailable(ir); + // It's dangerous to fire onImageAvailable() callback when the ImageReader + // is being closed, as application could acquire next image in the + // onImageAvailable() callback. + if (executor != null && listener != null && isReaderValid) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.onImageAvailable(ir); } - } finally { - ir.mListenerLock.unlockRead(rStamp); - } - }); + }); + } } /** @@ -1092,7 +1070,7 @@ public class ImageReader implements AutoCloseable { private Surface mSurface; private int mEstimatedNativeAllocBytes; - private final StampedLock mListenerLock = new StampedLock(); + private final Object mListenerLock = new Object(); private final Object mCloseLock = new Object(); private boolean mIsReaderValid = false; private OnImageAvailableListener mListener; diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp index d2d6ef0cbbb1..c3049dabefc2 100644 --- a/packages/ConnectivityT/service/Android.bp +++ b/packages/ConnectivityT/service/Android.bp @@ -28,6 +28,11 @@ filegroup { "src/com/android/server/net/NetworkStats*.java", "src/com/android/server/net/BpfInterfaceMapUpdater.java", "src/com/android/server/net/InterfaceMapValue.java", + "src/com/android/server/net/CookieTagMapKey.java", + "src/com/android/server/net/CookieTagMapValue.java", + "src/com/android/server/net/StatsMapKey.java", + "src/com/android/server/net/StatsMapValue.java", + "src/com/android/server/net/UidStatsMapKey.java", ], path: "src", visibility: [ diff --git a/packages/ConnectivityT/service/src/com/android/server/net/CookieTagMapKey.java b/packages/ConnectivityT/service/src/com/android/server/net/CookieTagMapKey.java new file mode 100644 index 000000000000..443e5b38475e --- /dev/null +++ b/packages/ConnectivityT/service/src/com/android/server/net/CookieTagMapKey.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * Key for cookie tag map. + */ +public class CookieTagMapKey extends Struct { + @Field(order = 0, type = Type.S64) + public final long socketCookie; + + public CookieTagMapKey(final long socketCookie) { + this.socketCookie = socketCookie; + } +} diff --git a/packages/ConnectivityT/service/src/com/android/server/net/CookieTagMapValue.java b/packages/ConnectivityT/service/src/com/android/server/net/CookieTagMapValue.java new file mode 100644 index 000000000000..93b9195f92da --- /dev/null +++ b/packages/ConnectivityT/service/src/com/android/server/net/CookieTagMapValue.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * Value for cookie tag map. + */ +public class CookieTagMapValue extends Struct { + @Field(order = 0, type = Type.U32) + public final long uid; + + @Field(order = 1, type = Type.U32) + public final long tag; + + public CookieTagMapValue(final long uid, final long tag) { + this.uid = uid; + this.tag = tag; + } +} diff --git a/packages/ConnectivityT/service/src/com/android/server/net/StatsMapKey.java b/packages/ConnectivityT/service/src/com/android/server/net/StatsMapKey.java new file mode 100644 index 000000000000..ea8d83638347 --- /dev/null +++ b/packages/ConnectivityT/service/src/com/android/server/net/StatsMapKey.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * Key for both stats maps. + */ +public class StatsMapKey extends Struct { + @Field(order = 0, type = Type.U32) + public final long uid; + + @Field(order = 1, type = Type.U32) + public final long tag; + + @Field(order = 2, type = Type.U32) + public final long counterSet; + + @Field(order = 3, type = Type.U32) + public final long ifaceIndex; + + public StatsMapKey(final long uid, final long tag, final long counterSet, + final long ifaceIndex) { + this.uid = uid; + this.tag = tag; + this.counterSet = counterSet; + this.ifaceIndex = ifaceIndex; + } +} diff --git a/packages/ConnectivityT/service/src/com/android/server/net/StatsMapValue.java b/packages/ConnectivityT/service/src/com/android/server/net/StatsMapValue.java new file mode 100644 index 000000000000..48f26ce686e2 --- /dev/null +++ b/packages/ConnectivityT/service/src/com/android/server/net/StatsMapValue.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * Value used for both stats maps and uid stats map. + */ +public class StatsMapValue extends Struct { + @Field(order = 0, type = Type.U63) + public final long rxPackets; + + @Field(order = 1, type = Type.U63) + public final long rxBytes; + + @Field(order = 2, type = Type.U63) + public final long txPackets; + + @Field(order = 3, type = Type.U63) + public final long txBytes; + + public StatsMapValue(final long rxPackets, final long rxBytes, final long txPackets, + final long txBytes) { + this.rxPackets = rxPackets; + this.rxBytes = rxBytes; + this.txPackets = txPackets; + this.txBytes = txBytes; + } +} diff --git a/packages/ConnectivityT/service/src/com/android/server/net/UidStatsMapKey.java b/packages/ConnectivityT/service/src/com/android/server/net/UidStatsMapKey.java new file mode 100644 index 000000000000..2849f94c9383 --- /dev/null +++ b/packages/ConnectivityT/service/src/com/android/server/net/UidStatsMapKey.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * Key for uid stats map. + */ +public class UidStatsMapKey extends Struct { + @Field(order = 0, type = Type.U32) + public final long uid; + + public UidStatsMapKey(final long uid) { + this.uid = uid; + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 246466e16c74..bbfab0bfa792 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -314,6 +314,7 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_HEIGHT, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_WIDTH, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.Wearable.WET_MODE_ON, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.COOLDOWN_MODE_ON, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 51870e2e958e..077337cdc8c3 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3624,7 +3624,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 209; + private static final int SETTINGS_VERSION = 210; private final int mUserId; @@ -5498,17 +5498,21 @@ public class SettingsProvider extends ContentProvider { } if (currentVersion == 208) { - // Version 208: Enable enforcement of + // Unused + currentVersion = 209; + } + if (currentVersion == 209) { + // Version 209: Enable enforcement of // android.Manifest.permission#POST_NOTIFICATIONS in order for applications // to post notifications. final SettingsState secureSettings = getSecureSettingsLocked(userId); secureSettings.insertSettingLocked( Secure.NOTIFICATION_PERMISSION_ENABLED, - /* enabled= */" 1", + /* enabled= */ "1", /* tag= */ null, /* makeDefault= */ false, SettingsState.SYSTEM_PACKAGE_NAME); - currentVersion = 209; + currentVersion = 210; } // vXXX: Add new settings above this point. diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 13ae87015fa4..3de1fb2b4ba4 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -659,7 +659,8 @@ public class SettingsBackupTest { Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY, Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED, Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER, - Settings.Global.Wearable.WET_MODE_ON); + Settings.Global.Wearable.WET_MODE_ON, + Settings.Global.Wearable.COOLDOWN_MODE_ON); private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = newHashSet( diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 35eeac822d3c..ef5849c73b72 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -562,6 +562,7 @@ <!-- Permissions required for CTS test - TrustTestCases --> <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" /> <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <uses-permission android:name="android.permission.TRUST_LISTENER" /> <!-- Permission required for CTS test - CtsGameManagerTestCases --> <uses-permission android:name="android.permission.MANAGE_GAME_MODE" /> diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index 536b0423ce16..02c58e468c30 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -26,9 +26,9 @@ android:layout_marginEnd="0dp" android:layout_gravity="center_vertical | start"> - <TextView + <com.android.systemui.util.SafeMarqueeTextView android:id="@+id/tile_label" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="start" android:textDirection="locale" @@ -37,9 +37,9 @@ android:singleLine="true" android:textAppearance="@style/TextAppearance.QS.TileLabel"/> - <TextView + <com.android.systemui.util.SafeMarqueeTextView android:id="@+id/app_label" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="start" android:textDirection="locale" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 1ae3d362cd6e..47822b77a93f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -686,7 +686,7 @@ <integer name="config_connectionMinDuration">1000</integer> <!-- Flag to activate notification to contents feature --> - <bool name="config_notificationToContents">false</bool> + <bool name="config_notificationToContents">true</bool> <!-- Respect drawable/rounded_secondary.xml intrinsic size for multiple radius corner path customization for secondary display--> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 8d98a7540a05..56326e36ff5e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -18,6 +18,7 @@ package com.android.systemui.shared.recents.utilities; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import android.annotation.TargetApi; import android.content.Context; @@ -110,11 +111,16 @@ public class Utilities { hints &= ~NAVIGATION_HINT_BACK_ALT; break; } - if (showImeSwitcher) { + if (imeShown) { hints |= NAVIGATION_HINT_IME_SHOWN; } else { hints &= ~NAVIGATION_HINT_IME_SHOWN; } + if (showImeSwitcher) { + hints |= NAVIGATION_HINT_IME_SWITCHER_SHOWN; + } else { + hints &= ~NAVIGATION_HINT_IME_SWITCHER_SHOWN; + } return hints; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index eebc7918c72c..08b4d3f68a87 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -59,6 +59,7 @@ public class QuickStepContract { public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation"; // See IRecentTasks.aidl public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks"; + public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation"; public static final String NAV_BAR_MODE_3BUTTON_OVERLAY = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 30429fbf8cfa..2a737970907a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -49,7 +49,6 @@ import androidx.slice.builders.SliceAction; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUIAppComponentFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -139,6 +138,8 @@ public class KeyguardSliceProvider extends SliceProvider implements public StatusBarStateController mStatusBarStateController; @Inject public KeyguardBypassController mKeyguardBypassController; + @Inject + public KeyguardUpdateMonitor mKeyguardUpdateMonitor; private CharSequence mMediaTitle; private CharSequence mMediaArtist; protected boolean mDozing; @@ -333,7 +334,7 @@ public class KeyguardSliceProvider extends SliceProvider implements mAlarmManager.cancel(mUpdateNextAlarm); if (mRegistered) { mRegistered = false; - getKeyguardUpdateMonitor().removeCallback(mKeyguardUpdateMonitorCallback); + mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); getContext().unregisterReceiver(mIntentReceiver); } KeyguardSliceProvider.sInstance = null; @@ -389,7 +390,7 @@ public class KeyguardSliceProvider extends SliceProvider implements filter.addAction(Intent.ACTION_LOCALE_CHANGED); getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/, null /* scheduler */); - getKeyguardUpdateMonitor().registerCallback(mKeyguardUpdateMonitorCallback); + mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); mRegistered = true; } } @@ -441,10 +442,6 @@ public class KeyguardSliceProvider extends SliceProvider implements updateNextAlarm(); } - private KeyguardUpdateMonitor getKeyguardUpdateMonitor() { - return Dependency.get(KeyguardUpdateMonitor.class); - } - /** * Called whenever new media metadata is available. * @param metadata New metadata. diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 1bef32ad8caf..4b550f2fb875 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -17,7 +17,7 @@ package com.android.systemui.navigationbar; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; @@ -1410,7 +1410,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, .setFlag(SYSUI_STATE_IME_SHOWING, (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0) .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0) + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0) .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, allowSystemGestureIgnoringBarVisibility()) .commitUpdate(mDisplayId); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 2dd89f3c4dcd..102669397283 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -778,7 +778,7 @@ public class NavigationBarView extends FrameLayout implements // Update IME button visibility, a11y and rotate button always overrides the appearance boolean disableImeSwitcher = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0 + (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) == 0 || isImeRenderingNavButtons; mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, !disableImeSwitcher); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index ec15b2469358..75a3df7cad0c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -17,7 +17,7 @@ package com.android.systemui.navigationbar; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.containsType; @@ -293,7 +293,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, .setFlag(SYSUI_STATE_IME_SHOWING, (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0) .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0) + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0) .setFlag(SYSUI_STATE_OVERVIEW_DISABLED, (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0) .setFlag(SYSUI_STATE_HOME_DISABLED, diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index eb3415639db6..58ebe89f199a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -109,7 +109,7 @@ class FgsManagerController @Inject constructor( } isAvailable = deviceConfigProxy - .getBoolean(NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, false) + .getBoolean(NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, true) initialized = true } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 821dfa5fc902..a712ce2e6394 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -25,6 +25,7 @@ import android.content.res.Configuration import android.content.res.Resources.ID_NULL import android.graphics.drawable.Drawable import android.graphics.drawable.RippleDrawable +import android.os.Trace import android.service.quicksettings.Tile import android.text.TextUtils import android.util.Log @@ -163,6 +164,12 @@ open class QSTileViewImpl @JvmOverloads constructor( updateResources() } + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + Trace.traceBegin(Trace.TRACE_TAG_APP, "QSTileViewImpl#onMeasure") + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + Trace.endSection() + } + override fun resetOverride() { heightOverride = HeightOverrideable.NO_OVERRIDE updateHeight() diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 00a314943f7a..1218fd307931 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -26,6 +26,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; @@ -104,6 +105,7 @@ import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; @@ -163,6 +165,7 @@ public class OverviewProxyService extends CurrentUserTracker implements private final Optional<StartingSurface> mStartingSurface; private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController; private final Optional<RecentTasks> mRecentTasks; + private final Optional<BackAnimation> mBackAnimation; private final UiEventLogger mUiEventLogger; private Region mActiveNavBarRegion; @@ -508,6 +511,9 @@ public class OverviewProxyService extends CurrentUserTracker implements mRecentTasks.ifPresent(recentTasks -> params.putBinder( KEY_EXTRA_RECENT_TASKS, recentTasks.createExternalInterface().asBinder())); + mBackAnimation.ifPresent((backAnimation) -> params.putBinder( + KEY_EXTRA_SHELL_BACK_ANIMATION, + backAnimation.createExternalInterface().asBinder())); try { mOverviewProxy.onInitialize(params); @@ -566,6 +572,7 @@ public class OverviewProxyService extends CurrentUserTracker implements Optional<SplitScreen> splitScreenOptional, Optional<OneHanded> oneHandedOptional, Optional<RecentTasks> recentTasks, + Optional<BackAnimation> backAnimation, Optional<StartingSurface> startingSurface, BroadcastDispatcher broadcastDispatcher, ShellTransitions shellTransitions, @@ -593,6 +600,7 @@ public class OverviewProxyService extends CurrentUserTracker implements mOneHandedOptional = oneHandedOptional; mShellTransitions = shellTransitions; mRecentTasks = recentTasks; + mBackAnimation = backAnimation; mUiEventLogger = uiEventLogger; // Assumes device always starts with back button until launcher tells it that it does not diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index 0b6d7594ce50..7d035a78450a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -35,6 +35,10 @@ public class FooterView extends StackScrollerDecorView { private FooterViewButton mClearAllButton; private FooterViewButton mManageButton; private boolean mShowHistory; + // String cache, for performance reasons. + // Reading them from a Resources object can be quite slow sometimes. + private String mManageNotificationText; + private String mManageNotificationHistoryText; public FooterView(Context context, AttributeSet attrs) { super(context, attrs); @@ -68,6 +72,8 @@ public class FooterView extends StackScrollerDecorView { super.onFinishInflate(); mClearAllButton = (FooterViewButton) findSecondaryView(); mManageButton = findViewById(R.id.manage_text); + updateResources(); + updateText(); } public void setManageButtonClickListener(OnClickListener listener) { @@ -86,15 +92,20 @@ public class FooterView extends StackScrollerDecorView { } public void showHistory(boolean showHistory) { + if (mShowHistory == showHistory) { + return; + } mShowHistory = showHistory; + updateText(); + } + + private void updateText() { if (mShowHistory) { - mManageButton.setText(R.string.manage_notifications_history_text); - mManageButton.setContentDescription( - mContext.getString(R.string.manage_notifications_history_text)); + mManageButton.setText(mManageNotificationHistoryText); + mManageButton.setContentDescription(mManageNotificationHistoryText); } else { - mManageButton.setText(R.string.manage_notifications_text); - mManageButton.setContentDescription( - mContext.getString(R.string.manage_notifications_text)); + mManageButton.setText(mManageNotificationText); + mManageButton.setContentDescription(mManageNotificationText); } } @@ -109,7 +120,8 @@ public class FooterView extends StackScrollerDecorView { mClearAllButton.setText(R.string.clear_all_notifications_text); mClearAllButton.setContentDescription( mContext.getString(R.string.accessibility_clear_all)); - showHistory(mShowHistory); + updateResources(); + updateText(); } /** @@ -124,6 +136,12 @@ public class FooterView extends StackScrollerDecorView { mManageButton.setTextColor(textColor); } + private void updateResources() { + mManageNotificationText = getContext().getString(R.string.manage_notifications_text); + mManageNotificationHistoryText = getContext() + .getString(R.string.manage_notifications_history_text); + } + @Override public ExpandableViewState createExpandableViewState() { return new FooterViewState(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java index 329293409dc2..d464acb7fe76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java @@ -217,8 +217,10 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { * frameworks/base/core/res/res/values/config.xml */ public void addIgnoredSlot(String slotName) { - addIgnoredSlotInternal(slotName); - requestLayout(); + boolean added = addIgnoredSlotInternal(slotName); + if (added) { + requestLayout(); + } } /** @@ -226,17 +228,27 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { * @param slots names of the icons to ignore */ public void addIgnoredSlots(List<String> slots) { + boolean willAddAny = false; for (String slot : slots) { - addIgnoredSlotInternal(slot); + willAddAny |= addIgnoredSlotInternal(slot); } - requestLayout(); + if (willAddAny) { + requestLayout(); + } } - private void addIgnoredSlotInternal(String slotName) { - if (!mIgnoredSlots.contains(slotName)) { - mIgnoredSlots.add(slotName); + /** + * + * @param slotName + * @return + */ + private boolean addIgnoredSlotInternal(String slotName) { + if (mIgnoredSlots.contains(slotName)) { + return false; } + mIgnoredSlots.add(slotName); + return true; } /** @@ -245,9 +257,10 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { * @param slotName name of the icon slot to remove from the ignored list */ public void removeIgnoredSlot(String slotName) { - mIgnoredSlots.remove(slotName); - - requestLayout(); + boolean removed = mIgnoredSlots.remove(slotName); + if (removed) { + requestLayout(); + } } /** @@ -256,11 +269,14 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { * @param slots name of the icon slots to remove from the ignored list */ public void removeIgnoredSlots(List<String> slots) { + boolean removedAny = false; for (String slot : slots) { - mIgnoredSlots.remove(slot); + removedAny |= mIgnoredSlots.remove(slot); } - requestLayout(); + if (removedAny) { + requestLayout(); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java b/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java index 09dbfee5e0a4..fa4f314ff0a2 100644 --- a/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java +++ b/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java @@ -19,7 +19,6 @@ package com.android.systemui.util; import android.content.Context; import android.text.TextUtils; import android.util.AttributeSet; -import android.widget.TextView; /** * TextView that changes its ellipsize value with its visibility. @@ -27,7 +26,7 @@ import android.widget.TextView; * The View responds to changes in user-visibility to change its ellipsize from MARQUEE to END * and back. Useful for TextView that need to marquee forever. */ -public class AutoMarqueeTextView extends TextView { +public class AutoMarqueeTextView extends SafeMarqueeTextView { private boolean mAggregatedVisible = false; diff --git a/packages/SystemUI/src/com/android/systemui/util/SafeMarqueeTextView.kt b/packages/SystemUI/src/com/android/systemui/util/SafeMarqueeTextView.kt new file mode 100644 index 000000000000..1c1a990e6018 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/SafeMarqueeTextView.kt @@ -0,0 +1,44 @@ +package com.android.systemui.util + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.ViewGroup +import android.widget.TextView + +/** + * A TextField that doesn't relayout when changing from marquee to ellipsis. + */ +@SuppressLint("AppCompatCustomView") +open class SafeMarqueeTextView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : TextView(context, attrs, defStyleAttr, defStyleRes) { + + private var safelyIgnoreLayout = false + private val hasStableWidth + get() = layoutParams.width != ViewGroup.LayoutParams.WRAP_CONTENT + + override fun requestLayout() { + if (safelyIgnoreLayout) { + return + } + super.requestLayout() + } + + override fun startMarquee() { + val wasIgnoring = safelyIgnoreLayout + safelyIgnoreLayout = hasStableWidth + super.startMarquee() + safelyIgnoreLayout = wasIgnoring + } + + override fun stopMarquee() { + val wasIgnoring = safelyIgnoreLayout + safelyIgnoreLayout = hasStableWidth + super.stopMarquee() + safelyIgnoreLayout = wasIgnoring + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java index d1f505baa9e3..51c258055465 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java @@ -90,6 +90,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { private DozeParameters mDozeParameters; @Mock private NextAlarmController mNextAlarmController; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; private TestableKeyguardSliceProvider mProvider; private boolean mIsZenMode; @@ -97,7 +98,6 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - mKeyguardUpdateMonitor = mDependency.injectMockDependency(KeyguardUpdateMonitor.class); mIsZenMode = false; mProvider = new TestableKeyguardSliceProvider(); mProvider.setContextAvailableCallback(context -> { }); @@ -265,6 +265,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { mStatusBarStateController = KeyguardSliceProviderTest.this.mStatusBarStateController; mKeyguardBypassController = KeyguardSliceProviderTest.this.mKeyguardBypassController; mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager; + mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor; } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 9ca898b9dea9..612bad8483d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -18,6 +18,7 @@ package com.android.systemui.navigationbar; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.inputmethodservice.InputMethodService.IME_VISIBLE; @@ -285,20 +286,26 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, true); // Verify IME window state will be updated in default NavBar & external NavBar state reset. - assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN, + assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN + | NAVIGATION_HINT_IME_SWITCHER_SHOWN, defaultNavBar.getNavigationIconHints()); assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) + != 0); externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); defaultNavBar.setImeWindowStatus( DEFAULT_DISPLAY, null, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false); // Verify IME window state will be updated in external NavBar & default NavBar state reset. - assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN, + assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN + | NAVIGATION_HINT_IME_SWITCHER_SHOWN, externalNavBar.getNavigationIconHints()); assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) + != 0); } @Test @@ -316,6 +323,8 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, true); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) + != 0); // Verify navbar didn't alter and showing back icon when the keyguard is showing without // requesting IME insets visible. @@ -324,6 +333,8 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, true); assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) + != 0); // Verify navbar altered and showing back icon when the keyguard is showing and // requesting IME insets visible. @@ -333,6 +344,8 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, true); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) + != 0); } @Test diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 6938e25ea9af..c9903ea19868 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -43,6 +43,7 @@ import android.hardware.camera2.extension.IImageProcessorImpl; import android.hardware.camera2.extension.IInitializeSessionCallback; import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.IPreviewImageProcessorImpl; +import android.hardware.camera2.extension.IProcessResultImpl; import android.hardware.camera2.extension.IRequestCallback; import android.hardware.camera2.extension.IRequestProcessorImpl; import android.hardware.camera2.extension.IRequestUpdateProcessorImpl; @@ -90,6 +91,7 @@ import androidx.camera.extensions.impl.NightPreviewExtenderImpl; import androidx.camera.extensions.impl.PreviewExtenderImpl; import androidx.camera.extensions.impl.PreviewExtenderImpl.ProcessorType; import androidx.camera.extensions.impl.PreviewImageProcessorImpl; +import androidx.camera.extensions.impl.ProcessResultImpl; import androidx.camera.extensions.impl.RequestUpdateProcessorImpl; import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.AutoAdvancedExtenderImpl; @@ -124,14 +126,17 @@ public class CameraExtensionsProxyService extends Service { private static final String LATEST_VERSION = "1.2.0"; private static final String NON_INIT_VERSION_PREFIX = "1.0"; private static final String ADVANCED_VERSION_PREFIX = "1.2"; - private static final String[] SUPPORTED_VERSION_PREFIXES = {ADVANCED_VERSION_PREFIX, - "1.1", NON_INIT_VERSION_PREFIX}; + private static final String RESULTS_VERSION_PREFIX = "1.3"; + private static final String[] SUPPORTED_VERSION_PREFIXES = {RESULTS_VERSION_PREFIX, + ADVANCED_VERSION_PREFIX, "1.1", NON_INIT_VERSION_PREFIX}; private static final boolean EXTENSIONS_PRESENT = checkForExtensions(); private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ? (new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null; private static final boolean ADVANCED_API_SUPPORTED = checkForAdvancedAPI(); private static final boolean INIT_API_SUPPORTED = EXTENSIONS_PRESENT && (!EXTENSIONS_VERSION.startsWith(NON_INIT_VERSION_PREFIX)); + private static final boolean RESULT_API_SUPPORTED = EXTENSIONS_PRESENT && + (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX)); private HashMap<String, CameraCharacteristics> mCharacteristicsHashMap = new HashMap<>(); private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>(); @@ -1242,7 +1247,7 @@ public class CameraExtensionsProxyService extends Service { } if (processor != null) { - return new PreviewImageProcessorImplStub(processor); + return new PreviewImageProcessorImplStub(processor, mCameraId); } return null; @@ -1332,7 +1337,7 @@ public class CameraExtensionsProxyService extends Service { public ICaptureProcessorImpl getCaptureProcessor() { CaptureProcessorImpl captureProcessor = mImageExtender.getCaptureProcessor(); if (captureProcessor != null) { - return new CaptureProcessorImplStub(captureProcessor); + return new CaptureProcessorImplStub(captureProcessor, mCameraId); } return null; @@ -1390,13 +1395,97 @@ public class CameraExtensionsProxyService extends Service { return null; } + + @Override + public CameraMetadataNative getAvailableCaptureRequestKeys() { + if (RESULT_API_SUPPORTED) { + List<CaptureRequest.Key> supportedCaptureKeys = + mImageExtender.getAvailableCaptureRequestKeys(); + + if ((supportedCaptureKeys != null) && !supportedCaptureKeys.isEmpty()) { + CameraMetadataNative ret = new CameraMetadataNative(); + long vendorId = mMetadataVendorIdMap.containsKey(mCameraId) ? + mMetadataVendorIdMap.get(mCameraId) : Long.MAX_VALUE; + ret.setVendorId(vendorId); + int requestKeyTags [] = new int[supportedCaptureKeys.size()]; + int i = 0; + for (CaptureRequest.Key key : supportedCaptureKeys) { + requestKeyTags[i++] = CameraMetadataNative.getTag(key.getName(), vendorId); + } + ret.set(CameraCharacteristics.REQUEST_AVAILABLE_REQUEST_KEYS, requestKeyTags); + + return ret; + } + } + + return null; + } + + @Override + public CameraMetadataNative getAvailableCaptureResultKeys() { + if (RESULT_API_SUPPORTED) { + List<CaptureResult.Key> supportedResultKeys = + mImageExtender.getAvailableCaptureResultKeys(); + + if ((supportedResultKeys != null) && !supportedResultKeys.isEmpty()) { + CameraMetadataNative ret = new CameraMetadataNative(); + long vendorId = mMetadataVendorIdMap.containsKey(mCameraId) ? + mMetadataVendorIdMap.get(mCameraId) : Long.MAX_VALUE; + ret.setVendorId(vendorId); + int resultKeyTags [] = new int[supportedResultKeys.size()]; + int i = 0; + for (CaptureResult.Key key : supportedResultKeys) { + resultKeyTags[i++] = CameraMetadataNative.getTag(key.getName(), vendorId); + } + ret.set(CameraCharacteristics.REQUEST_AVAILABLE_RESULT_KEYS, resultKeyTags); + + return ret; + } + } + + return null; + } + } + + private class ProcessResultCallback implements ProcessResultImpl { + private final IProcessResultImpl mProcessResult; + private final String mCameraId; + + private ProcessResultCallback(IProcessResultImpl processResult, String cameraId) { + mProcessResult = processResult; + mCameraId = cameraId; + } + + @Override + public void onCaptureCompleted(long shutterTimestamp, + List<Pair<CaptureResult.Key, Object>> result) { + if (result == null) { + Log.e(TAG, "Invalid capture result received!"); + } + + CameraMetadataNative captureResults = new CameraMetadataNative(); + if (mMetadataVendorIdMap.containsKey(mCameraId)) { + captureResults.setVendorId(mMetadataVendorIdMap.get(mCameraId)); + } + for (Pair<CaptureResult.Key, Object> pair : result) { + captureResults.set(pair.first, pair.second); + } + + try { + mProcessResult.onCaptureCompleted(shutterTimestamp, captureResults); + } catch (RemoteException e) { + Log.e(TAG, "Remote client doesn't respond to capture results!"); + } + } } private class CaptureProcessorImplStub extends ICaptureProcessorImpl.Stub { private final CaptureProcessorImpl mCaptureProcessor; + private final String mCameraId; - public CaptureProcessorImplStub(CaptureProcessorImpl captureProcessor) { + public CaptureProcessorImplStub(CaptureProcessorImpl captureProcessor, String cameraId) { mCaptureProcessor = captureProcessor; + mCameraId = cameraId; } @Override @@ -1415,7 +1504,7 @@ public class CameraExtensionsProxyService extends Service { } @Override - public void process(List<CaptureBundle> captureList) { + public void process(List<CaptureBundle> captureList, IProcessResultImpl resultCallback) { HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap = new HashMap<>(); for (CaptureBundle captureBundle : captureList) { captureMap.put(captureBundle.stage, new Pair<> ( @@ -1424,7 +1513,14 @@ public class CameraExtensionsProxyService extends Service { captureBundle.sequenceId))); } if (!captureMap.isEmpty()) { - mCaptureProcessor.process(captureMap); + if ((resultCallback != null) && (RESULT_API_SUPPORTED)) { + mCaptureProcessor.process(captureMap, new ProcessResultCallback(resultCallback, + mCameraId), null /*executor*/); + } else if (resultCallback == null) { + mCaptureProcessor.process(captureMap); + } else { + Log.e(TAG, "Process requests with capture results are not supported!"); + } } else { Log.e(TAG, "Process request with absent capture stages!"); } @@ -1433,9 +1529,11 @@ public class CameraExtensionsProxyService extends Service { private class PreviewImageProcessorImplStub extends IPreviewImageProcessorImpl.Stub { private final PreviewImageProcessorImpl mProcessor; + private final String mCameraId; - public PreviewImageProcessorImplStub(PreviewImageProcessorImpl processor) { + public PreviewImageProcessorImplStub(PreviewImageProcessorImpl processor, String cameraId) { mProcessor = processor; + mCameraId = cameraId; } @Override @@ -1455,9 +1553,17 @@ public class CameraExtensionsProxyService extends Service { @Override public void process(android.hardware.camera2.extension.ParcelImage image, - CameraMetadataNative result, int sequenceId) { - mProcessor.process(new ExtensionImage(image), - new TotalCaptureResult(result, sequenceId)); + CameraMetadataNative result, int sequenceId, IProcessResultImpl resultCallback) { + if ((resultCallback != null) && RESULT_API_SUPPORTED) { + mProcessor.process(new ExtensionImage(image), + new TotalCaptureResult(result, sequenceId), + new ProcessResultCallback(resultCallback, mCameraId), null /*executor*/); + } else if (resultCallback == null) { + mProcessor.process(new ExtensionImage(image), + new TotalCaptureResult(result, sequenceId)); + } else { + + } } } diff --git a/proto/src/camera.proto b/proto/src/camera.proto index 0338b93c8842..2d62f32d3941 100644 --- a/proto/src/camera.proto +++ b/proto/src/camera.proto @@ -65,4 +65,6 @@ message CameraStreamProto { // The dynamic range profile of the stream optional int32 dynamic_range_profile = 14; + // The stream use case + optional int32 stream_use_case = 15; } diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index 2845fbfc6ebf..6667d1b45027 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -311,14 +311,15 @@ public class AdbService extends IAdbManager.Stub { } /** - * @return true if the device supports secure ADB over Wi-Fi. + * @return true if the device supports secure ADB over Wi-Fi or Ethernet. * @hide */ @Override public boolean isAdbWifiSupported() { mContext.enforceCallingPermission( android.Manifest.permission.MANAGE_DEBUGGING, "AdbService"); - return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI) || + mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_ETHERNET); } /** diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 145a298af95e..4eba77168b8e 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -473,7 +473,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } try { - mWindowManagerInternal.addTaskOverlay( + mWindowManagerInternal.addTrustedTaskOverlay( taskId, createGameSessionResult.getSurfacePackage()); } catch (IllegalArgumentException ex) { @@ -519,7 +519,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan SurfacePackage surfacePackage = gameSessionRecord.getSurfacePackage(); if (surfacePackage != null) { try { - mWindowManagerInternal.removeTaskOverlay( + mWindowManagerInternal.removeTrustedTaskOverlay( gameSessionRecord.getTaskId(), surfacePackage); } catch (IllegalArgumentException ex) { diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index 366718c65d84..aa0d3da6a94f 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -35,6 +35,8 @@ import android.app.ActivityThread; import android.app.IActivityManager; import android.app.StatsManager; import android.app.StatsManager.StatsPullAtomCallback; +import android.app.usage.StorageStats; +import android.app.usage.StorageStatsManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; @@ -78,6 +80,7 @@ import com.android.server.SystemService; import java.io.File; import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -111,6 +114,7 @@ public final class AppHibernationService extends SystemService { private final PackageManagerInternal mPackageManagerInternal; private final IActivityManager mIActivityManager; private final UserManager mUserManager; + private final StorageStatsManager mStorageStatsManager; @GuardedBy("mLock") private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>(); @@ -147,6 +151,7 @@ public final class AppHibernationService extends SystemService { mPackageManagerInternal = injector.getPackageManagerInternal(); mIActivityManager = injector.getActivityManager(); mUserManager = injector.getUserManager(); + mStorageStatsManager = injector.getStorageStatsManager(); mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore(); mBackgroundExecutor = injector.getBackgroundExecutor(); mOatArtifactDeletionEnabled = injector.isOatArtifactDeletionEnabled(); @@ -217,7 +222,7 @@ public final class AppHibernationService extends SystemService { */ boolean isHibernatingForUser(String packageName, int userId) { String methodName = "isHibernatingForUser"; - if (!checkHibernationEnabled(methodName)) { + if (!sIsServiceEnabled) { return false; } getContext().enforceCallingOrSelfPermission( @@ -246,7 +251,7 @@ public final class AppHibernationService extends SystemService { * @param packageName package to check */ boolean isHibernatingGlobally(String packageName) { - if (!checkHibernationEnabled("isHibernatingGlobally")) { + if (!sIsServiceEnabled) { return false; } getContext().enforceCallingOrSelfPermission( @@ -272,7 +277,7 @@ public final class AppHibernationService extends SystemService { */ void setHibernatingForUser(String packageName, int userId, boolean isHibernating) { String methodName = "setHibernatingForUser"; - if (!checkHibernationEnabled(methodName)) { + if (!sIsServiceEnabled) { return; } getContext().enforceCallingOrSelfPermission( @@ -297,7 +302,8 @@ public final class AppHibernationService extends SystemService { pkgState.hibernated = isHibernating; if (isHibernating) { - mBackgroundExecutor.execute(() -> hibernatePackageForUser(packageName, realUserId)); + mBackgroundExecutor.execute( + () -> hibernatePackageForUser(packageName, realUserId, pkgState)); } else { mBackgroundExecutor.execute( () -> unhibernatePackageForUser(packageName, realUserId)); @@ -326,7 +332,7 @@ public final class AppHibernationService extends SystemService { * @param isHibernating new hibernation state */ void setHibernatingGlobally(String packageName, boolean isHibernating) { - if (!checkHibernationEnabled("setHibernatingGlobally")) { + if (!sIsServiceEnabled) { return; } getContext().enforceCallingOrSelfPermission( @@ -359,7 +365,7 @@ public final class AppHibernationService extends SystemService { @NonNull List<String> getHibernatingPackagesForUser(int userId) { ArrayList<String> hibernatingPackages = new ArrayList<>(); String methodName = "getHibernatingPackagesForUser"; - if (!checkHibernationEnabled(methodName)) { + if (!sIsServiceEnabled) { return hibernatingPackages; } getContext().enforceCallingOrSelfPermission( @@ -390,6 +396,9 @@ public final class AppHibernationService extends SystemService { @Nullable Set<String> packageNames, int userId) { Map<String, HibernationStats> statsMap = new ArrayMap<>(); String methodName = "getHibernationStatsForUser"; + if (!sIsServiceEnabled) { + return statsMap; + } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); @@ -412,8 +421,9 @@ public final class AppHibernationService extends SystemService { + "the package was uninstalled? ", pkgName, userId)); continue; } - HibernationStats stats = new HibernationStats( - mGlobalHibernationStates.get(pkgName).savedByte); + long diskBytesSaved = mGlobalHibernationStates.get(pkgName).savedByte + + userPackageStates.get(pkgName).savedByte; + HibernationStats stats = new HibernationStats(diskBytesSaved); statsMap.put(pkgName, stats); } } @@ -424,16 +434,28 @@ public final class AppHibernationService extends SystemService { * Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do * not hold {@link #mLock} while calling this to avoid deadlock scenarios. */ - private void hibernatePackageForUser(@NonNull String packageName, int userId) { + private void hibernatePackageForUser(@NonNull String packageName, int userId, + UserLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage"); final long caller = Binder.clearCallingIdentity(); try { + ApplicationInfo info = mIPackageManager.getApplicationInfo( + packageName, PACKAGE_MATCH_FLAGS, userId); + StorageStats stats = mStorageStatsManager.queryStatsForPackage( + info.storageUuid, packageName, new UserHandle(userId)); mIActivityManager.forceStopPackage(packageName, userId); mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId, null /* observer */); + synchronized (mLock) { + state.savedByte = stats.getCacheBytes(); + } } catch (RemoteException e) { throw new IllegalStateException( "Failed to hibernate due to manager not being available", e); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Package name not found when querying storage stats", e); + } catch (IOException e) { + Slog.e(TAG, "Storage device not found", e); } finally { Binder.restoreCallingIdentity(caller); Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); @@ -677,6 +699,7 @@ public final class AppHibernationService extends SystemService { for (String key : properties.getKeyset()) { if (TextUtils.equals(KEY_APP_HIBERNATION_ENABLED, key)) { sIsServiceEnabled = isDeviceConfigAppHibernationEnabled(); + Slog.d(TAG, "App hibernation changed to enabled=" + sIsServiceEnabled); break; } } @@ -721,13 +744,6 @@ public final class AppHibernationService extends SystemService { return true; } - private boolean checkHibernationEnabled(String methodName) { - if (!sIsServiceEnabled) { - Slog.w(TAG, String.format("Attempted to call %s on unsupported device.", methodName)); - } - return sIsServiceEnabled; - } - private void dump(PrintWriter pw) { // Check usage stats permission since hibernation indirectly informs usage. if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return; @@ -923,6 +939,8 @@ public final class AppHibernationService extends SystemService { UserManager getUserManager(); + StorageStatsManager getStorageStatsManager(); + Executor getBackgroundExecutor(); UsageStatsManagerInternal getUsageStatsManagerInternal(); @@ -972,6 +990,11 @@ public final class AppHibernationService extends SystemService { } @Override + public StorageStatsManager getStorageStatsManager() { + return mContext.getSystemService(StorageStatsManager.class); + } + + @Override public Executor getBackgroundExecutor() { return mScheduledExecutorService; } diff --git a/services/core/java/com/android/server/apphibernation/UserLevelState.java b/services/core/java/com/android/server/apphibernation/UserLevelState.java index 68c363c8256a..6a489d28ac17 100644 --- a/services/core/java/com/android/server/apphibernation/UserLevelState.java +++ b/services/core/java/com/android/server/apphibernation/UserLevelState.java @@ -28,6 +28,8 @@ final class UserLevelState { public String packageName; public boolean hibernated; + // Saved bytes from user level hibernation. + public long savedByte; @CurrentTimeMillisLong public long lastUnhibernatedMs; @@ -36,6 +38,7 @@ final class UserLevelState { UserLevelState(UserLevelState state) { packageName = state.packageName; hibernated = state.hibernated; + savedByte = state.savedByte; lastUnhibernatedMs = state.lastUnhibernatedMs; } @@ -44,6 +47,7 @@ final class UserLevelState { return "UserLevelState{" + "packageName='" + packageName + '\'' + ", hibernated=" + hibernated + '\'' + + ", savedByte=" + savedByte + '\'' + ", lastUnhibernated=" + DATE_FORMAT.format(lastUnhibernatedMs) + '}'; } diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 1b0341c1ce26..e0b768d7c592 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -808,6 +808,7 @@ public class CameraServiceProxy extends SystemService streamProtos[i].histogramBins = streamStats.getHistogramBins(); streamProtos[i].histogramCounts = streamStats.getHistogramCounts(); streamProtos[i].dynamicRangeProfile = streamStats.getDynamicRangeProfile(); + streamProtos[i].streamUseCase = streamStats.getStreamUseCase(); if (CameraServiceProxy.DEBUG) { String histogramTypeName = @@ -828,7 +829,8 @@ public class CameraServiceProxy extends SystemService + Arrays.toString(streamProtos[i].histogramBins) + ", histogramCounts " + Arrays.toString(streamProtos[i].histogramCounts) - + ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile); + + ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile + + ", streamUseCase " + streamProtos[i].streamUseCase); } } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 8ce67a657740..76d06c8801f4 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -119,9 +119,7 @@ import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; import static com.android.internal.util.XmlUtils.readStringAttribute; -import static com.android.internal.util.XmlUtils.readThisIntArrayXml; import static com.android.internal.util.XmlUtils.writeBooleanAttribute; -import static com.android.internal.util.XmlUtils.writeIntArrayXml; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; @@ -246,7 +244,6 @@ import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.StatLogger; -import com.android.internal.util.XmlUtils; import com.android.net.module.util.NetworkIdentityUtils; import com.android.net.module.util.NetworkStatsUtils; import com.android.net.module.util.PermissionUtils; @@ -260,8 +257,6 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import libcore.io.IoUtils; -import org.xmlpull.v1.XmlPullParserException; - import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -336,7 +331,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int VERSION_ADDED_CYCLE = 11; private static final int VERSION_ADDED_NETWORK_TYPES = 12; private static final int VERSION_SUPPORTED_CARRIER_USAGE = 13; - private static final int VERSION_LATEST = VERSION_SUPPORTED_CARRIER_USAGE; + private static final int VERSION_REMOVED_SUBSCRIPTION_PLANS = 14; + private static final int VERSION_LATEST = VERSION_REMOVED_SUBSCRIPTION_PLANS; @VisibleForTesting public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING; @@ -349,7 +345,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final String TAG_POLICY_LIST = "policy-list"; private static final String TAG_NETWORK_POLICY = "network-policy"; - private static final String TAG_SUBSCRIPTION_PLAN = "subscription-plan"; private static final String TAG_UID_POLICY = "uid-policy"; private static final String TAG_APP_POLICY = "app-policy"; private static final String TAG_WHITELIST = "whitelist"; @@ -426,6 +421,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * obj = oldBlockedReasons */ private static final int MSG_BLOCKED_REASON_CHANGED = 21; + /** + * Message to indicate that subscription plans expired and should be cleared. + * arg1 = subId + * arg2 = setSubscriptionPlans call ID + * obj = callingPackage + */ + private static final int MSG_CLEAR_SUBSCRIPTION_PLANS = 22; private static final int UID_MSG_STATE_CHANGED = 100; private static final int UID_MSG_GONE = 101; @@ -492,6 +494,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** Map from subId to package name that owns subscription plans. */ @GuardedBy("mNetworkPoliciesSecondLock") final SparseArray<String> mSubscriptionPlansOwner = new SparseArray<>(); + /** Map from subId to the ID of the clear plans request. */ + @GuardedBy("mNetworkPoliciesSecondLock") + final SparseIntArray mSetSubscriptionPlansIds = new SparseIntArray(); + /** Atomic integer to generate a new ID for each clear plans request. */ + @GuardedBy("mNetworkPoliciesSecondLock") + int mSetSubscriptionPlansIdCounter = 0; /** Map from subId to daily opportunistic quota. */ @GuardedBy("mNetworkPoliciesSecondLock") @@ -2523,56 +2531,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { warningBytes, limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred)); } - - } else if (TAG_SUBSCRIPTION_PLAN.equals(tag)) { - final String start = readStringAttribute(in, ATTR_CYCLE_START); - final String end = readStringAttribute(in, ATTR_CYCLE_END); - final String period = readStringAttribute(in, ATTR_CYCLE_PERIOD); - final SubscriptionPlan.Builder builder = new SubscriptionPlan.Builder( - RecurrenceRule.convertZonedDateTime(start), - RecurrenceRule.convertZonedDateTime(end), - RecurrenceRule.convertPeriod(period)); - builder.setTitle(readStringAttribute(in, ATTR_TITLE)); - builder.setSummary(readStringAttribute(in, ATTR_SUMMARY)); - - final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES, - SubscriptionPlan.BYTES_UNKNOWN); - final int limitBehavior = readIntAttribute(in, ATTR_LIMIT_BEHAVIOR, - SubscriptionPlan.LIMIT_BEHAVIOR_UNKNOWN); - if (limitBytes != SubscriptionPlan.BYTES_UNKNOWN - && limitBehavior != SubscriptionPlan.LIMIT_BEHAVIOR_UNKNOWN) { - builder.setDataLimit(limitBytes, limitBehavior); - } - - final long usageBytes = readLongAttribute(in, ATTR_USAGE_BYTES, - SubscriptionPlan.BYTES_UNKNOWN); - final long usageTime = readLongAttribute(in, ATTR_USAGE_TIME, - SubscriptionPlan.TIME_UNKNOWN); - if (usageBytes != SubscriptionPlan.BYTES_UNKNOWN - && usageTime != SubscriptionPlan.TIME_UNKNOWN) { - builder.setDataUsage(usageBytes, usageTime); - } - - final int subId = readIntAttribute(in, ATTR_SUB_ID); - final String ownerPackage = readStringAttribute(in, ATTR_OWNER_PACKAGE); - - if (version >= VERSION_ADDED_NETWORK_TYPES) { - final int depth = in.getDepth(); - while (XmlUtils.nextElementWithin(in, depth)) { - if (TAG_XML_UTILS_INT_ARRAY.equals(in.getName()) - && ATTR_NETWORK_TYPES.equals( - readStringAttribute(in, ATTR_XML_UTILS_NAME))) { - final int[] networkTypes = - readThisIntArrayXml(in, TAG_XML_UTILS_INT_ARRAY, null); - builder.setNetworkTypes(networkTypes); - } - } - } - - final SubscriptionPlan plan = builder.build(); - mSubscriptionPlans.put(subId, ArrayUtils.appendElement( - SubscriptionPlan.class, mSubscriptionPlans.get(subId), plan)); - mSubscriptionPlansOwner.put(subId, ownerPackage); } else if (TAG_UID_POLICY.equals(tag)) { final int uid = readIntAttribute(in, ATTR_UID); final int policy = readIntAttribute(in, ATTR_POLICY); @@ -2763,38 +2721,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { out.endTag(null, TAG_NETWORK_POLICY); } - // write all known subscription plans - for (int i = 0; i < mSubscriptionPlans.size(); i++) { - final int subId = mSubscriptionPlans.keyAt(i); - if (subId == INVALID_SUBSCRIPTION_ID) continue; - final String ownerPackage = mSubscriptionPlansOwner.get(subId); - final SubscriptionPlan[] plans = mSubscriptionPlans.valueAt(i); - if (ArrayUtils.isEmpty(plans)) continue; - - for (SubscriptionPlan plan : plans) { - out.startTag(null, TAG_SUBSCRIPTION_PLAN); - writeIntAttribute(out, ATTR_SUB_ID, subId); - writeStringAttribute(out, ATTR_OWNER_PACKAGE, ownerPackage); - final RecurrenceRule cycleRule = plan.getCycleRule(); - writeStringAttribute(out, ATTR_CYCLE_START, - RecurrenceRule.convertZonedDateTime(cycleRule.start)); - writeStringAttribute(out, ATTR_CYCLE_END, - RecurrenceRule.convertZonedDateTime(cycleRule.end)); - writeStringAttribute(out, ATTR_CYCLE_PERIOD, - RecurrenceRule.convertPeriod(cycleRule.period)); - writeStringAttribute(out, ATTR_TITLE, plan.getTitle()); - writeStringAttribute(out, ATTR_SUMMARY, plan.getSummary()); - writeLongAttribute(out, ATTR_LIMIT_BYTES, plan.getDataLimitBytes()); - writeIntAttribute(out, ATTR_LIMIT_BEHAVIOR, plan.getDataLimitBehavior()); - writeLongAttribute(out, ATTR_USAGE_BYTES, plan.getDataUsageBytes()); - writeLongAttribute(out, ATTR_USAGE_TIME, plan.getDataUsageTime()); - try { - writeIntArrayXml(plan.getNetworkTypes(), ATTR_NETWORK_TYPES, out); - } catch (XmlPullParserException ignored) { } - out.endTag(null, TAG_SUBSCRIPTION_PLAN); - } - } - // write all known uid policies for (int i = 0; i < mUidPolicy.size(); i++) { final int uid = mUidPolicy.keyAt(i); @@ -3668,7 +3594,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage) { + public void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, + long expirationDurationMillis, String callingPackage) { enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage); enforceSubscriptionPlanValidity(plans); @@ -3678,31 +3605,44 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long token = Binder.clearCallingIdentity(); try { - synchronized (mUidRulesFirstLock) { - synchronized (mNetworkPoliciesSecondLock) { - mSubscriptionPlans.put(subId, plans); - mSubscriptionPlansOwner.put(subId, callingPackage); + setSubscriptionPlansInternal(subId, plans, expirationDurationMillis, callingPackage); + } finally { + Binder.restoreCallingIdentity(token); + } + } - final String subscriberId = mSubIdToSubscriberId.get(subId, null); - if (subscriberId != null) { - ensureActiveCarrierPolicyAL(subId, subscriberId); - maybeUpdateCarrierPolicyCycleAL(subId, subscriberId); - } else { - Slog.wtf(TAG, "Missing subscriberId for subId " + subId); - } + private void setSubscriptionPlansInternal(int subId, SubscriptionPlan[] plans, + long expirationDurationMillis, String callingPackage) { + synchronized (mUidRulesFirstLock) { + synchronized (mNetworkPoliciesSecondLock) { + mSubscriptionPlans.put(subId, plans); + mSubscriptionPlansOwner.put(subId, callingPackage); - handleNetworkPoliciesUpdateAL(true); + final String subscriberId = mSubIdToSubscriberId.get(subId, null); + if (subscriberId != null) { + ensureActiveCarrierPolicyAL(subId, subscriberId); + maybeUpdateCarrierPolicyCycleAL(subId, subscriberId); + } else { + Slog.wtf(TAG, "Missing subscriberId for subId " + subId); } - } - final Intent intent = new Intent(SubscriptionManager.ACTION_SUBSCRIPTION_PLANS_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); - mContext.sendBroadcast(intent, android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS); - mHandler.sendMessage( - mHandler.obtainMessage(MSG_SUBSCRIPTION_PLANS_CHANGED, subId, 0, plans)); - } finally { - Binder.restoreCallingIdentity(token); + handleNetworkPoliciesUpdateAL(true); + + final Intent intent = new Intent( + SubscriptionManager.ACTION_SUBSCRIPTION_PLANS_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); + mContext.sendBroadcast(intent, + android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS); + mHandler.sendMessage(mHandler.obtainMessage( + MSG_SUBSCRIPTION_PLANS_CHANGED, subId, 0, plans)); + final int setPlansId = mSetSubscriptionPlansIdCounter++; + mSetSubscriptionPlansIds.put(subId, setPlansId); + if (expirationDurationMillis > 0) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_SUBSCRIPTION_PLANS, + subId, setPlansId, callingPackage), expirationDurationMillis); + } + } } } @@ -3728,7 +3668,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Override public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, - int[] networkTypes, long timeoutMillis, String callingPackage) { + int[] networkTypes, long expirationDurationMillis, String callingPackage) { enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage); final ArraySet<Integer> allNetworksSet = new ArraySet<>(); @@ -3766,10 +3706,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { args.arg3 = overrideValue; args.arg4 = applicableNetworks.toArray(); mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args)); - if (timeoutMillis > 0) { + if (expirationDurationMillis > 0) { args.arg3 = 0; mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args), - timeoutMillis); + expirationDurationMillis); } } } @@ -5184,6 +5124,22 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mListeners.finishBroadcast(); return true; } + case MSG_CLEAR_SUBSCRIPTION_PLANS: { + synchronized (mUidRulesFirstLock) { + synchronized (mNetworkPoliciesSecondLock) { + int subId = msg.arg1; + if (msg.arg2 == mSetSubscriptionPlansIds.get(subId)) { + if (LOGD) Slog.d(TAG, "Clearing expired subscription plans."); + setSubscriptionPlansInternal(subId, new SubscriptionPlan[]{}, + 0 /* expirationDurationMillis */, + (String) msg.obj /* callingPackage */); + } else { + if (LOGD) Slog.d(TAG, "Ignoring stale CLEAR_SUBSCRIPTION_PLANS."); + } + } + } + return true; + } case MSG_BLOCKED_REASON_CHANGED: { final int uid = msg.arg1; final int newBlockedReasons = msg.arg2; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6b27321f856f..bc38087ebc73 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2463,7 +2463,7 @@ public class NotificationManagerService extends SystemService { }; mAllowFgsDismissal = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, false); + SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, true); DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_SYSTEMUI, new HandlerExecutor(mHandler), diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java new file mode 100644 index 000000000000..750d4004cb60 --- /dev/null +++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.sensorprivacy; + +import android.app.AppOpsManager; +import android.content.Context; +import android.hardware.lights.Light; +import android.hardware.lights.LightState; +import android.hardware.lights.LightsManager; +import android.hardware.lights.LightsRequest; +import android.permission.PermissionManager; +import android.util.ArraySet; + +import com.android.internal.R; +import com.android.server.FgThread; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener { + + private final Context mContext; + private final LightsManager mLightsManager; + + private final Set<String> mActivePackages = new ArraySet<>(); + private final Set<String> mActivePhonePackages = new ArraySet<>(); + + private final int mCameraPrivacyLightColor; + + private final List<Light> mCameraLights = new ArrayList<>(); + private final AppOpsManager mAppOpsManager; + + private LightsManager.LightsSession mLightsSession = null; + + CameraPrivacyLightController(Context context) { + mContext = context; + + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + mLightsManager = mContext.getSystemService(LightsManager.class); + + mCameraPrivacyLightColor = mContext.getColor(R.color.camera_privacy_light); + + List<Light> lights = mLightsManager.getLights(); + for (int i = 0; i < lights.size(); i++) { + Light light = lights.get(i); + if (light.getType() == Light.LIGHT_TYPE_CAMERA) { + mCameraLights.add(light); + } + } + + if (mCameraLights.isEmpty()) { + return; + } + + mAppOpsManager.startWatchingActive( + new String[] {AppOpsManager.OPSTR_CAMERA, AppOpsManager.OPSTR_PHONE_CALL_CAMERA}, + FgThread.getExecutor(), this); + } + + @Override + public void onOpActiveChanged(String op, int uid, String packageName, boolean active) { + final Set<String> activePackages; + if (AppOpsManager.OPSTR_CAMERA.equals(op)) { + activePackages = mActivePackages; + } else if (AppOpsManager.OPSTR_PHONE_CALL_CAMERA.equals(op)) { + activePackages = mActivePhonePackages; + } else { + return; + } + + if (active) { + activePackages.add(packageName); + } else { + activePackages.remove(packageName); + } + + updateLightSession(); + } + + private void updateLightSession() { + Set<String> exemptedPackages = PermissionManager.getIndicatorExemptedPackages(mContext); + + boolean shouldSessionEnd = exemptedPackages.containsAll(mActivePackages) + && exemptedPackages.containsAll(mActivePhonePackages); + + if (shouldSessionEnd) { + if (mLightsSession == null) { + return; + } + + mLightsSession.close(); + mLightsSession = null; + } else { + if (mLightsSession != null) { + return; + } + + LightsRequest.Builder requestBuilder = new LightsRequest.Builder(); + for (int i = 0; i < mCameraLights.size(); i++) { + requestBuilder.addLight(mCameraLights.get(i), + new LightState.Builder() + .setColor(mCameraPrivacyLightColor) + .build()); + } + + mLightsSession = mLightsManager.openSession(Integer.MAX_VALUE); + mLightsSession.requestLights(requestBuilder.build()); + } + } +} diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index e9b5f1156d39..040fffa885f1 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -154,6 +154,8 @@ public final class SensorPrivacyService extends SystemService { private final AppOpsManagerInternal mAppOpsManagerInternal; private final TelephonyManager mTelephonyManager; + private CameraPrivacyLightController mCameraPrivacyLightController; + private final IBinder mAppOpsRestrictionToken = new Binder(); private SensorPrivacyManagerInternalImpl mSensorPrivacyManagerInternal; @@ -190,6 +192,8 @@ public final class SensorPrivacyService extends SystemService { if (phase == PHASE_SYSTEM_SERVICES_READY) { mKeyguardManager = mContext.getSystemService(KeyguardManager.class); mEmergencyCallHelper = new EmergencyCallHelper(); + } else if (phase == PHASE_ACTIVITY_MANAGER_READY) { + mCameraPrivacyLightController = new CameraPrivacyLightController(mContext); } } diff --git a/services/core/java/com/android/server/wm/OverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java index 90f5b09968ea..975b21c6f02c 100644 --- a/services/core/java/com/android/server/wm/OverlayHost.java +++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java @@ -32,14 +32,20 @@ import java.util.ArrayList; * * Also handles multiplexing of event dispatch and tracking of overlays * to make things easier for WindowContainer. + * + * These overlays are to be used for various types of System UI and UI + * under the systems control. Provided SurfacePackages will be able + * to overlay application content, without engaging the usual cross process + * obscured touch filtering mechanisms. It's imperative that all UI provided + * be under complete control of the system. */ -class OverlayHost { +class TrustedOverlayHost { // Lazily initialized when required SurfaceControl mSurfaceControl; final ArrayList<SurfaceControlViewHost.SurfacePackage> mOverlays = new ArrayList<>(); final WindowManagerService mWmService; - OverlayHost(WindowManagerService wms) { + TrustedOverlayHost(WindowManagerService wms) { mWmService = wms; } @@ -51,6 +57,8 @@ class OverlayHost { .setName("Overlay Host Leash"); mSurfaceControl = b.build(); + SurfaceControl.Transaction t = mWmService.mTransactionFactory.get(); + t.setTrustedOverlay(mSurfaceControl, true).apply(); } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 1bd153b2a577..8a373bf5c09c 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -314,7 +314,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< private final List<WindowContainerListener> mListeners = new ArrayList<>(); - protected OverlayHost mOverlayHost; + protected TrustedOverlayHost mOverlayHost; WindowContainer(WindowManagerService wms) { mWmService = wms; @@ -3600,9 +3600,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @AnimationType int type, @Nullable AnimationAdapter snapshotAnim); } - void addOverlay(SurfaceControlViewHost.SurfacePackage overlay) { + void addTrustedOverlay(SurfaceControlViewHost.SurfacePackage overlay) { if (mOverlayHost == null) { - mOverlayHost = new OverlayHost(mWmService); + mOverlayHost = new TrustedOverlayHost(mWmService); } mOverlayHost.addOverlay(overlay, mSurfaceControl); @@ -3613,11 +3613,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< overlay.getRemoteInterface().onConfigurationChanged(getConfiguration()); } catch (Exception e) { Slog.e(TAG, "Error sending initial configuration change to WindowContainer overlay"); - removeOverlay(overlay); + removeTrustedOverlay(overlay); } } - void removeOverlay(SurfaceControlViewHost.SurfacePackage overlay) { + void removeTrustedOverlay(SurfaceControlViewHost.SurfacePackage overlay) { if (mOverlayHost != null && !mOverlayHost.removeOverlay(overlay)) { mOverlayHost.release(); mOverlayHost = null; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 4900f9292f2a..9585a4b93a97 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -827,6 +827,11 @@ public abstract class WindowManagerInternal { * Internal methods for other parts of SystemServer to manage * SurfacePackage based overlays on tasks. * + * Since these overlays will overlay application content, they exist + * in a container with setTrustedOverlay(true). This means its imperative + * that this overlay feature only be used with UI completely under the control + * of the system, without 3rd party content. + * * Callers prepare a view hierarchy with SurfaceControlViewHost * and send the package to WM here. The remote view hierarchy will receive * configuration change, lifecycle events, etc, forwarded over the @@ -837,8 +842,10 @@ public abstract class WindowManagerInternal { * The embedded hierarchy exists in a coordinate space relative to the task * bounds. */ - public abstract void addTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay); - public abstract void removeTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay); + public abstract void addTrustedTaskOverlay(int taskId, + SurfaceControlViewHost.SurfacePackage overlay); + public abstract void removeTrustedTaskOverlay(int taskId, + SurfaceControlViewHost.SurfacePackage overlay); /** * Get a SurfaceControl that is the container layer that should be used to receive input to diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c4907c2d9903..c9c3f1da5b40 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8012,27 +8012,29 @@ public class WindowManagerService extends IWindowManager.Stub @Override public boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { return WindowManagerService.this.shouldRestoreImeVisibility(imeTargetWindowToken); - } + } @Override - public void addTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay) { + public void addTrustedTaskOverlay(int taskId, + SurfaceControlViewHost.SurfacePackage overlay) { synchronized (mGlobalLock) { final Task task = mRoot.getRootTask(taskId); if (task == null) { throw new IllegalArgumentException("no task with taskId" + taskId); } - task.addOverlay(overlay); + task.addTrustedOverlay(overlay); } } @Override - public void removeTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay) { + public void removeTrustedTaskOverlay(int taskId, + SurfaceControlViewHost.SurfacePackage overlay) { synchronized (mGlobalLock) { final Task task = mRoot.getRootTask(taskId); if (task == null) { throw new IllegalArgumentException("no task with taskId" + taskId); } - task.removeOverlay(overlay); + task.removeTrustedOverlay(overlay); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java index 7f571195f5f8..ed232e5458b0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -406,7 +406,7 @@ public final class GameServiceProviderInstanceImplTest { mFakeGameSessionService.removePendingFutureForTaskId(10) .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); - verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verify(mMockWindowManagerInternal).addTrustedTaskOverlay(eq(10), eq(mockSurfacePackage10)); } @Test @@ -556,8 +556,9 @@ public final class GameServiceProviderInstanceImplTest { stopTask(10); - verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); - verify(mMockWindowManagerInternal).removeTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verify(mMockWindowManagerInternal).addTrustedTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verify(mMockWindowManagerInternal).removeTrustedTaskOverlay(eq(10), + eq(mockSurfacePackage10)); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java new file mode 100644 index 000000000000..fa3e05a6b001 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.sensorprivacy; + +import static android.app.AppOpsManager.OPSTR_CAMERA; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; + +import android.app.AppOpsManager; +import android.content.Context; +import android.hardware.lights.Light; +import android.hardware.lights.LightsManager; +import android.hardware.lights.LightsRequest; +import android.permission.PermissionManager; +import android.util.ArraySet; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; + +public class CameraPrivacyLightControllerTest { + + private MockitoSession mMockitoSession; + + @Mock + private Context mContext; + + @Mock + private LightsManager mLightsManager; + + @Mock + private AppOpsManager mAppOpsManager; + + @Mock + private LightsManager.LightsSession mLightsSession; + + private ArgumentCaptor<AppOpsManager.OnOpActiveChangedListener> mAppOpsListenerCaptor = + ArgumentCaptor.forClass(AppOpsManager.OnOpActiveChangedListener.class); + + private ArgumentCaptor<LightsRequest> mLightsRequestCaptor = + ArgumentCaptor.forClass(LightsRequest.class); + + private Set<String> mExemptedPackages = new ArraySet<>(); + private List<Light> mLights = new ArrayList<>(); + + private int mNextLightId = 1; + + @Before + public void setUp() { + mMockitoSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.WARN) + .spyStatic(PermissionManager.class) + .startMocking(); + + doReturn(mLightsManager).when(mContext).getSystemService(LightsManager.class); + doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class); + + doReturn(mLights).when(mLightsManager).getLights(); + doReturn(mLightsSession).when(mLightsManager).openSession(anyInt()); + + doReturn(mExemptedPackages) + .when(() -> PermissionManager.getIndicatorExemptedPackages(any())); + } + + @After + public void tearDown() { + mExemptedPackages.clear(); + mLights.clear(); + + mMockitoSession.finishMocking(); + } + + @Test + public void testAppsOpsListenerNotRegisteredWithoutCameraLights() { + mLights.add(getNextLight(false)); + new CameraPrivacyLightController(mContext); + + verify(mAppOpsManager, times(0)).startWatchingActive(any(), any(), any()); + } + + @Test + public void testAppsOpsListenerRegisteredWithCameraLight() { + mLights.add(getNextLight(true)); + + new CameraPrivacyLightController(mContext); + + verify(mAppOpsManager, times(1)).startWatchingActive(any(), any(), any()); + } + + @Test + public void testAllCameraLightsAreRequestedOnOpActive() { + Random r = new Random(0); + for (int i = 0; i < 30; i++) { + mLights.add(getNextLight(r.nextBoolean())); + } + + new CameraPrivacyLightController(mContext); + + // Verify no session has been opened at this point. + verify(mLightsManager, times(0)).openSession(anyInt()); + + // Set camera op as active. + verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); + mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true); + + // Verify session has been opened exactly once + verify(mLightsManager, times(1)).openSession(anyInt()); + + verify(mLightsSession).requestLights(mLightsRequestCaptor.capture()); + assertEquals("requestLights() not invoked exactly once", + 1, mLightsRequestCaptor.getAllValues().size()); + + List<Integer> expectedCameraLightIds = mLights.stream() + .filter(l -> l.getType() == Light.LIGHT_TYPE_CAMERA) + .map(l -> l.getId()) + .collect(Collectors.toList()); + List<Integer> lightsRequestLightIds = mLightsRequestCaptor.getValue().getLights(); + + // We don't own lights framework, don't assume it will retain order + lightsRequestLightIds.sort(Integer::compare); + expectedCameraLightIds.sort(Integer::compare); + + assertEquals(expectedCameraLightIds, lightsRequestLightIds); + } + + @Test + public void testWillOnlyOpenOnceWhenTwoPackagesStartOp() { + mLights.add(getNextLight(true)); + + new CameraPrivacyLightController(mContext); + + verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); + + AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue(); + listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true); + verify(mLightsManager, times(1)).openSession(anyInt()); + listener.onOpActiveChanged(OPSTR_CAMERA, 10102, "pkg2", true); + verify(mLightsManager, times(1)).openSession(anyInt()); + } + + @Test + public void testWillCloseOnFinishOp() { + mLights.add(getNextLight(true)); + + new CameraPrivacyLightController(mContext); + + verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); + + AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue(); + listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true); + + verify(mLightsSession, times(0)).close(); + listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", false); + verify(mLightsSession, times(1)).close(); + } + + @Test + public void testWillCloseOnFinishOpForAllPackages() { + mLights.add(getNextLight(true)); + + new CameraPrivacyLightController(mContext); + + int numUids = 100; + List<Integer> uids = new ArrayList<>(numUids); + for (int i = 0; i < numUids; i++) { + uids.add(10001 + i); + } + + verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); + + AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue(); + + for (int i = 0; i < numUids; i++) { + listener.onOpActiveChanged(OPSTR_CAMERA, uids.get(i), "pkg" + (int) uids.get(i), true); + } + + // Change the order which their ops are finished + Collections.shuffle(uids, new Random(0)); + + for (int i = 0; i < numUids - 1; i++) { + listener.onOpActiveChanged(OPSTR_CAMERA, uids.get(i), "pkg" + (int) uids.get(i), false); + } + + verify(mLightsSession, times(0)).close(); + int lastUid = uids.get(numUids - 1); + listener.onOpActiveChanged(OPSTR_CAMERA, lastUid, "pkg" + lastUid, false); + verify(mLightsSession, times(1)).close(); + } + + @Test + public void testWontOpenForExemptedPackage() { + mLights.add(getNextLight(true)); + mExemptedPackages.add("pkg1"); + + new CameraPrivacyLightController(mContext); + + verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture()); + + AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue(); + listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true); + verify(mLightsManager, times(0)).openSession(anyInt()); + } + + private Light getNextLight(boolean cameraType) { + Light light = ExtendedMockito.mock(Light.class); + if (cameraType) { + doReturn(Light.LIGHT_TYPE_CAMERA).when(light).getType(); + } else { + doReturn(Light.LIGHT_TYPE_MICROPHONE).when(light).getType(); + } + doReturn(mNextLightId++).when(light).getId(); + return light; + } +} diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index e1aa08d9176a..51607e528216 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -29,6 +29,8 @@ import static org.mockito.AdditionalAnswers.returnsArgAt; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.longThat; import static org.mockito.Mockito.doAnswer; @@ -38,6 +40,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.IActivityManager; +import android.app.usage.StorageStats; +import android.app.usage.StorageStatsManager; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; @@ -48,6 +52,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; @@ -68,10 +73,12 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.Executor; /** @@ -104,6 +111,8 @@ public final class AppHibernationServiceTest { @Mock private UserManager mUserManager; @Mock + private StorageStatsManager mStorageStatsManager; + @Mock private HibernationStateDiskStore<UserLevelState> mUserLevelDiskStore; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; @@ -115,7 +124,7 @@ public final class AppHibernationServiceTest { private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor; @Before - public void setUp() throws RemoteException { + public void setUp() throws RemoteException, PackageManager.NameNotFoundException, IOException { // Share class loader to allow access to package-private classes System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); @@ -140,6 +149,11 @@ public final class AppHibernationServiceTest { packages.add(makePackageInfo(PACKAGE_NAME_3)); doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages( longThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt()); + doReturn(mock(ApplicationInfo.class)).when(mIPackageManager).getApplicationInfo( + any(), anyLong(), anyInt()); + StorageStats storageStats = new StorageStats(); + doReturn(storageStats).when(mStorageStatsManager).queryStatsForPackage( + (UUID) any(), anyString(), any()); mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); UserInfo userInfo = addUser(USER_ID_1); @@ -381,18 +395,31 @@ public final class AppHibernationServiceTest { } @Test - public void testGetHibernationStatsForUser_getsStatsForPackage() { - // GIVEN a package is hibernating globally and for a user + public void testGetHibernationStatsForUser_getsStatsForPackage() + throws PackageManager.NameNotFoundException, IOException, RemoteException { + // GIVEN a package is hibernating globally and for a user with some storage saved + final long cacheSavings = 1000; + StorageStats storageStats = new StorageStats(); + storageStats.cacheBytes = cacheSavings; + doReturn(storageStats).when(mStorageStatsManager).queryStatsForPackage( + (UUID) any(), eq(PACKAGE_NAME_1), any()); + final long oatDeletionSavings = 2000; + doReturn(oatDeletionSavings).when(mPackageManagerInternal).deleteOatArtifactsOfPackage( + PACKAGE_NAME_1); + mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); // WHEN we ask for the hibernation stats for the package - Map<String, HibernationStats> stats = + Map<String, HibernationStats> statsMap = mAppHibernationService.getHibernationStatsForUser( Set.of(PACKAGE_NAME_1), USER_ID_1); - // THEN the stats exist for the package - assertTrue(stats.containsKey(PACKAGE_NAME_1)); + // THEN the stats exist for the package and add up to the OAT deletion and cache deletion + // savings + HibernationStats stats = statsMap.get(PACKAGE_NAME_1); + assertNotNull(stats); + assertEquals(cacheSavings + oatDeletionSavings, stats.getDiskBytesSaved()); } @Test @@ -519,6 +546,11 @@ public final class AppHibernationServiceTest { } @Override + public StorageStatsManager getStorageStatsManager() { + return mStorageStatsManager; + } + + @Override public UsageStatsManagerInternal getUsageStatsManagerInternal() { return mUsageStatsManagerInternal; } diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 94cf20f9c15b..a7b045f10f2c 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -2237,7 +2237,7 @@ public class NetworkPolicyManagerServiceTest { private void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage) throws InterruptedException { - mService.setSubscriptionPlans(subId, plans, callingPackage); + mService.setSubscriptionPlans(subId, plans, 0, callingPackage); // setSubscriptionPlans() triggers async events, wait for those to be completed before // moving forward as they could interfere with the tests later. postMsgAndWaitForCompletion(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 9f92294135c0..d4d076566fb3 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -372,6 +372,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { "android.permission.WRITE_DEVICE_CONFIG", "android.permission.READ_DEVICE_CONFIG", "android.permission.READ_CONTACTS"); + Settings.Secure.putIntForUser( + getContext().getContentResolver(), + Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM); MockitoAnnotations.initMocks(this); diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index 184e1541f1b0..30ed7c287421 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -257,6 +257,13 @@ public abstract class EuiccService extends Service { "android.service.euicc.extra.RESOLUTION_CARD_ID"; /** + * Intent extra set for resolution requests containing an int indicating the subscription id + * to be enabled. + */ + public static final String EXTRA_RESOLUTION_SUBSCRIPTION_ID = + "android.service.euicc.extra.RESOLUTION_SUBSCRIPTION_ID"; + + /** * Intent extra set for resolution requests containing an int indicating the current port index. */ public static final String EXTRA_RESOLUTION_PORT_INDEX = diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 250e55cf5014..0aaafef492bf 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -2863,10 +2863,43 @@ public class SubscriptionManager { * outlined above. * @throws IllegalArgumentException if plans don't meet the requirements * defined in {@link SubscriptionPlan}. + * @deprecated use {@link #setSubscriptionPlans(int, List, long)} instead. */ + @Deprecated public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) { + setSubscriptionPlans(subId, plans, 0); + } + + /** + * Set the description of the billing relationship plan between a carrier + * and a specific subscriber. + * <p> + * This method is only accessible to the following narrow set of apps: + * <ul> + * <li>The carrier app for this subscriberId, as determined by + * {@link TelephonyManager#hasCarrierPrivileges()}. + * <li>The carrier app explicitly delegated access through + * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. + * </ul> + * + * @param subId the subscriber this relationship applies to. An empty list + * may be sent to clear any existing plans. + * @param plans the list of plans. The first plan is always the primary and + * most important plan. Any additional plans are secondary and + * may not be displayed or used by decision making logic. + * @param expirationDurationMillis the duration after which the subscription plans + * will be automatically cleared, or {@code 0} to leave the plans until + * explicitly cleared, or the next reboot, whichever happens first. + * @throws SecurityException if the caller doesn't meet the requirements + * outlined above. + * @throws IllegalArgumentException if plans don't meet the requirements + * defined in {@link SubscriptionPlan}. + */ + public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans, + @DurationMillisLong long expirationDurationMillis) { getNetworkPolicyManager().setSubscriptionPlans(subId, - plans.toArray(new SubscriptionPlan[plans.size()]), mContext.getOpPackageName()); + plans.toArray(new SubscriptionPlan[0]), expirationDurationMillis, + mContext.getOpPackageName()); } /** @@ -2885,17 +2918,17 @@ public class SubscriptionManager { * @param subId the subscriber this override applies to. * @param overrideUnmetered set if the billing relationship should be * considered unmetered. - * @param timeoutMillis the timeout after which the requested override will - * be automatically cleared, or {@code 0} to leave in the + * @param expirationDurationMillis the duration after which the requested override + * will be automatically cleared, or {@code 0} to leave in the * requested state until explicitly cleared, or the next reboot, * whichever happens first. * @throws SecurityException if the caller doesn't meet the requirements * outlined above. */ public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, - @DurationMillisLong long timeoutMillis) { + @DurationMillisLong long expirationDurationMillis) { setSubscriptionOverrideUnmetered(subId, overrideUnmetered, - TelephonyManager.getAllNetworkTypes(), timeoutMillis); + TelephonyManager.getAllNetworkTypes(), expirationDurationMillis); } /** @@ -2917,8 +2950,8 @@ public class SubscriptionManager { * @param networkTypes the network types this override applies to. If no * network types are specified, override values will be ignored. * {@see TelephonyManager#getAllNetworkTypes()} - * @param timeoutMillis the timeout after which the requested override will - * be automatically cleared, or {@code 0} to leave in the + * @param expirationDurationMillis the duration after which the requested override + * will be automatically cleared, or {@code 0} to leave in the * requested state until explicitly cleared, or the next reboot, * whichever happens first. * @throws SecurityException if the caller doesn't meet the requirements @@ -2926,10 +2959,10 @@ public class SubscriptionManager { */ public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, @NonNull @Annotation.NetworkType int[] networkTypes, - @DurationMillisLong long timeoutMillis) { + @DurationMillisLong long expirationDurationMillis) { final int overrideValue = overrideUnmetered ? SUBSCRIPTION_OVERRIDE_UNMETERED : 0; getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_UNMETERED, - overrideValue, networkTypes, timeoutMillis, mContext.getOpPackageName()); + overrideValue, networkTypes, expirationDurationMillis, mContext.getOpPackageName()); } /** @@ -2949,17 +2982,17 @@ public class SubscriptionManager { * @param subId the subscriber this override applies to. * @param overrideCongested set if the subscription should be considered * congested. - * @param timeoutMillis the timeout after which the requested override will - * be automatically cleared, or {@code 0} to leave in the + * @param expirationDurationMillis the duration after which the requested override + * will be automatically cleared, or {@code 0} to leave in the * requested state until explicitly cleared, or the next reboot, * whichever happens first. * @throws SecurityException if the caller doesn't meet the requirements * outlined above. */ public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, - @DurationMillisLong long timeoutMillis) { + @DurationMillisLong long expirationDurationMillis) { setSubscriptionOverrideCongested(subId, overrideCongested, - TelephonyManager.getAllNetworkTypes(), timeoutMillis); + TelephonyManager.getAllNetworkTypes(), expirationDurationMillis); } /** @@ -2982,8 +3015,8 @@ public class SubscriptionManager { * @param networkTypes the network types this override applies to. If no * network types are specified, override values will be ignored. * {@see TelephonyManager#getAllNetworkTypes()} - * @param timeoutMillis the timeout after which the requested override will - * be automatically cleared, or {@code 0} to leave in the + * @param expirationDurationMillis the duration after which the requested override + * will be automatically cleared, or {@code 0} to leave in the * requested state until explicitly cleared, or the next reboot, * whichever happens first. * @throws SecurityException if the caller doesn't meet the requirements @@ -2991,10 +3024,10 @@ public class SubscriptionManager { */ public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, @NonNull @Annotation.NetworkType int[] networkTypes, - @DurationMillisLong long timeoutMillis) { + @DurationMillisLong long expirationDurationMillis) { final int overrideValue = overrideCongested ? SUBSCRIPTION_OVERRIDE_CONGESTED : 0; getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_CONGESTED, - overrideValue, networkTypes, timeoutMillis, mContext.getOpPackageName()); + overrideValue, networkTypes, expirationDurationMillis, mContext.getOpPackageName()); } /** diff --git a/tests/SilkFX/AndroidManifest.xml b/tests/SilkFX/AndroidManifest.xml index c30d76137f76..21256d8c9d0b 100644 --- a/tests/SilkFX/AndroidManifest.xml +++ b/tests/SilkFX/AndroidManifest.xml @@ -20,17 +20,20 @@ <uses-sdk android:minSdkVersion="30"/> <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:label="SilkFX" android:theme="@android:style/Theme.Material"> <activity android:name=".Main" android:label="SilkFX Demos" + android:banner="@drawable/background1" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.LAUNCHER"/> + <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> </intent-filter> </activity> @@ -41,13 +44,16 @@ <activity android:name=".materials.GlassActivity" android:label="Glass Examples" - android:banner="@drawable/background1" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter> </activity> + <activity android:name=".materials.BackgroundBlurActivity" + android:theme="@style/Theme.BackgroundBlurTheme" + android:exported="true"> + </activity> + </application> </manifest> diff --git a/tests/SilkFX/res/drawable/background_blur_drawable.xml b/tests/SilkFX/res/drawable/background_blur_drawable.xml new file mode 100644 index 000000000000..173ca99bdfdf --- /dev/null +++ b/tests/SilkFX/res/drawable/background_blur_drawable.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#20FFFFFF"/> + <corners android:radius="10dp"/> +</shape> diff --git a/tests/SilkFX/res/drawable/blur_activity_background_drawable_white.xml b/tests/SilkFX/res/drawable/blur_activity_background_drawable_white.xml new file mode 100644 index 000000000000..bd8942d46383 --- /dev/null +++ b/tests/SilkFX/res/drawable/blur_activity_background_drawable_white.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="10dp"/> +</shape> diff --git a/tests/SilkFX/res/layout/activity_background_blur.xml b/tests/SilkFX/res/layout/activity_background_blur.xml new file mode 100644 index 000000000000..f13c0883cb01 --- /dev/null +++ b/tests/SilkFX/res/layout/activity_background_blur.xml @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/background" + android:layout_width="390dp" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:padding="15dp" + android:orientation="vertical" + tools:context=".materials.BackgroundBlurActivity"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:padding="10dp" + android:textColor="#ffffffff" + android:text="Hello blurry world!"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Background blur"/> + + <SeekBar + android:id="@+id/set_background_blur" + android:min="0" + android:max="300" + android:layout_width="160dp" + android:layout_height="wrap_content"/> + <TextView + android:id="@+id/background_blur_radius" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#ffffffff" + android:ems="3" + android:gravity="center" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:text="TODO"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Background alpha"/> + + <SeekBar + android:id="@+id/set_background_alpha" + android:min="0" + android:max="100" + android:layout_width="160dp" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/background_alpha" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#ffffffff" + android:ems="3" + android:gravity="center" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:text="TODO"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Blur behind"/> + + <SeekBar + android:id="@+id/set_blur_behind" + android:min="0" + android:max="300" + android:layout_width="160dp" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/blur_behind_radius" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="#ffffffff" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:ems="3" + android:text="TODO"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Dim amount"/> + + <SeekBar + android:id="@+id/set_dim_amount" + android:min="0" + android:max="100" + android:layout_width="160dp" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/dim_amount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="#ffffffff" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:ems="3" + android:text="TODO"/> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginTop="5dp" + android:orientation="vertical" + android:gravity="center"> + + <Button + android:id="@+id/toggle_blur_enabled" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Disable blur" + android:onClick="toggleForceBlurDisabled"/> + + <Button + android:id="@+id/toggle_battery_saving_mode" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="TODO" + android:onClick="toggleBatterySavingMode"/> + </LinearLayout> + <requestFocus/> + +</LinearLayout> diff --git a/tests/SilkFX/res/values/style.xml b/tests/SilkFX/res/values/style.xml new file mode 100644 index 000000000000..66edbb5c9382 --- /dev/null +++ b/tests/SilkFX/res/values/style.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<!-- Styles for immersive actions UI. --> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <style name="Theme.BackgroundBlurTheme" parent= "Theme.AppCompat.Dialog"> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBlurBehindEnabled">true</item> + <item name="android:backgroundDimEnabled">false</item> + <item name="android:windowElevation">0dp</item> + <item name="buttonStyle">@style/AppTheme.Button</item> + <item name="colorAccent">#bbffffff</item> + </style> + <style name="AppTheme.Button" parent="Widget.AppCompat.Button"> + <item name="android:textColor">#ffffffff</item> + </style> + +</resources> diff --git a/tests/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/SilkFX/src/com/android/test/silkfx/Main.kt index 9ed8d2f5edf7..7132ae8772ea 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/Main.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/Main.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import com.android.test.silkfx.app.EXTRA_LAYOUT import com.android.test.silkfx.app.EXTRA_TITLE import com.android.test.silkfx.hdr.GlowActivity import com.android.test.silkfx.materials.GlassActivity +import com.android.test.silkfx.materials.BackgroundBlurActivity import kotlin.reflect.KClass class Demo(val name: String, val makeIntent: (Context) -> Intent) { @@ -51,7 +52,8 @@ private val AllDemos = listOf( Demo("Blingy Notifications", R.layout.bling_notifications) )), DemoGroup("Materials", listOf( - Demo("Glass", GlassActivity::class) + Demo("Glass", GlassActivity::class), + Demo("Background Blur", BackgroundBlurActivity::class) )) ) @@ -126,4 +128,4 @@ class Main : Activity() { AllDemos.forEachIndexed { index, _ -> list.expandGroup(index) } } -}
\ No newline at end of file +} diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt new file mode 100644 index 000000000000..9d17d38d4298 --- /dev/null +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.test.silkfx.materials + +import android.app.Activity +import android.content.Intent +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.PaintDrawable +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.provider.Settings +import android.util.TypedValue +import android.view.View +import android.view.WindowManager +import android.widget.ImageView +import android.widget.SeekBar +import android.widget.Switch +import android.widget.TextView +import com.android.test.silkfx.R +import com.android.internal.graphics.drawable.BackgroundBlurDrawable +import android.widget.LinearLayout +import android.widget.Button + +import android.view.ViewRootImpl + +class BackgroundBlurActivity : Activity(), SeekBar.OnSeekBarChangeListener { + var mBackgroundDrawable = PaintDrawable(Color.WHITE) + var mBackgroundBlurRadius = 50 + var mAlphaWithBlur = 0.2f + var mAlphaNoBlur = 0.5f + + var mBlurBehindRadius = 10 + var mDimAmountWithBlur = 0.2f + var mDimAmountNoBlur = 0.2f + + var mBlurForceDisabled = false + var mBatterySavingModeOn = false + + lateinit var blurBackgroundSeekBar: SeekBar + lateinit var backgroundAlphaSeekBar : SeekBar + lateinit var blurBehindSeekBar : SeekBar + lateinit var dimAmountSeekBar : SeekBar + + val blurEnabledListener = { enabled : Boolean -> + blurBackgroundSeekBar.setProgress(mBackgroundBlurRadius) + blurBehindSeekBar.setProgress(mBlurBehindRadius) + + if (enabled) { + setBackgroundBlur(mBackgroundBlurRadius) + setBackgroundColorAlpha(mAlphaWithBlur) + + setBlurBehind(mBlurBehindRadius) + setDimAmount(mDimAmountWithBlur) + + backgroundAlphaSeekBar.setProgress((mAlphaWithBlur * 100).toInt()) + dimAmountSeekBar.setProgress((mDimAmountWithBlur * 100).toInt()) + } else { + setBackgroundColorAlpha(mAlphaNoBlur) + setDimAmount(mDimAmountNoBlur) + + backgroundAlphaSeekBar.setProgress((mAlphaNoBlur * 100).toInt()) + dimAmountSeekBar.setProgress((mDimAmountNoBlur * 100).toInt()) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_background_blur) + + window.addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND) + window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + + mBackgroundDrawable.setCornerRadius(30f) + window.setBackgroundDrawable(mBackgroundDrawable) + + mBatterySavingModeOn = + Settings.Global.getInt(getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) == 1 + setBatterySavingModeOn(mBatterySavingModeOn) + + blurBackgroundSeekBar = requireViewById(R.id.set_background_blur) + backgroundAlphaSeekBar = requireViewById(R.id.set_background_alpha) + blurBehindSeekBar = requireViewById(R.id.set_blur_behind) + dimAmountSeekBar = requireViewById(R.id.set_dim_amount) + + arrayOf(blurBackgroundSeekBar, backgroundAlphaSeekBar, blurBehindSeekBar, dimAmountSeekBar) + .forEach { + it.setOnSeekBarChangeListener(this) + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + getWindowManager().addCrossWindowBlurEnabledListener(blurEnabledListener) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + getWindowManager().removeCrossWindowBlurEnabledListener(blurEnabledListener) + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + when (seekBar) { + blurBackgroundSeekBar -> setBackgroundBlur(progress) + backgroundAlphaSeekBar -> setBackgroundColorAlpha(progress / 100.0f) + blurBehindSeekBar -> setBlurBehind(progress) + dimAmountSeekBar -> setDimAmount(progress / 100.0f) + else -> throw IllegalArgumentException("Unknown seek bar") + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + + fun setBlurDisabled(disabled: Boolean) { + mBlurForceDisabled = disabled + Settings.Global.putInt(getContentResolver(), Settings.Global.DISABLE_WINDOW_BLURS, + if (mBlurForceDisabled) 1 else 0) + (findViewById(R.id.toggle_blur_enabled) as Button) + .setText(if (mBlurForceDisabled) "Enable blurs" else "Disable blurs") + } + + fun toggleForceBlurDisabled(v: View) { + setBlurDisabled(!mBlurForceDisabled) + } + + fun setBackgroundBlur(radius: Int) { + mBackgroundBlurRadius = radius + (findViewById(R.id.background_blur_radius) as TextView).setText(radius.toString()) + window.setBackgroundBlurRadius(mBackgroundBlurRadius) + } + + fun setBlurBehind(radius: Int) { + mBlurBehindRadius = radius + (findViewById(R.id.blur_behind_radius) as TextView).setText(radius.toString()) + window.getAttributes().setBlurBehindRadius(mBlurBehindRadius) + window.setAttributes(window.getAttributes()) + } + + fun setDimAmount(amount: Float) { + if (getWindowManager().isCrossWindowBlurEnabled()) { + mDimAmountWithBlur = amount + } else { + mDimAmountNoBlur = amount + } + (findViewById(R.id.dim_amount) as TextView).setText("%.2f".format(amount)) + window.getAttributes().dimAmount = amount + window.setAttributes(window.getAttributes()) + } + + fun setBatterySavingModeOn(on: Boolean) { + mBatterySavingModeOn = on + Settings.Global.putInt(getContentResolver(), + Settings.Global.LOW_POWER_MODE, if (on) 1 else 0) + (findViewById(R.id.toggle_battery_saving_mode) as Button).setText( + if (on) "Exit low power mode" else "Enter low power mode") + } + + fun toggleBatterySavingMode(v: View) { + setBatterySavingModeOn(!mBatterySavingModeOn) + } + + fun setBackgroundColorAlpha(alpha: Float) { + if (getWindowManager().isCrossWindowBlurEnabled()) { + mAlphaWithBlur = alpha + } else { + mAlphaNoBlur = alpha + } + (findViewById(R.id.background_alpha) as TextView).setText("%.2f".format(alpha)) + mBackgroundDrawable.setAlpha((alpha * 255f).toInt()) + getWindowManager().updateViewLayout(window.getDecorView(), window.getAttributes()) + } +} |