diff options
74 files changed, 2115 insertions, 1495 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 10957ff8bb58..38f60a10852a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9010,8 +9010,6 @@ package android.companion { method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName); method public void requestNotificationAccess(android.content.ComponentName); method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; - method @Deprecated public void startSystemDataTransfer(int) throws android.companion.DeviceNotAssociatedException; - method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException; method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION"; field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; @@ -9027,19 +9025,14 @@ package android.companion { public abstract class CompanionDeviceService extends android.app.Service { ctor public CompanionDeviceService(); - method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessageToSystem(int, int, @NonNull byte[]) throws android.companion.DeviceNotAssociatedException; method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String); method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo); method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String); method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo); - method public void onMessageDispatchedFromSystem(int, int, @NonNull byte[]); field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService"; } - public class CompanionException extends java.lang.RuntimeException { - } - public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable { } @@ -18215,8 +18208,6 @@ package android.hardware.camera2.params { } public final class Face { - ctor public Face(@NonNull android.graphics.Rect, int, int, @NonNull android.graphics.Point, @NonNull android.graphics.Point, @NonNull android.graphics.Point); - ctor public Face(@NonNull android.graphics.Rect, int); method public android.graphics.Rect getBounds(); method public int getId(); method public android.graphics.Point getLeftEyePosition(); @@ -18228,6 +18219,18 @@ package android.hardware.camera2.params { field public static final int SCORE_MIN = 1; // 0x1 } + public static final class Face.Builder { + ctor public Face.Builder(); + ctor public Face.Builder(@NonNull android.hardware.camera2.params.Face); + method @NonNull public android.hardware.camera2.params.Face build(); + method @NonNull public android.hardware.camera2.params.Face.Builder setBounds(@NonNull android.graphics.Rect); + method @NonNull public android.hardware.camera2.params.Face.Builder setId(int); + method @NonNull public android.hardware.camera2.params.Face.Builder setLeftEyePosition(@NonNull android.graphics.Point); + method @NonNull public android.hardware.camera2.params.Face.Builder setMouthPosition(@NonNull android.graphics.Point); + method @NonNull public android.hardware.camera2.params.Face.Builder setRightEyePosition(@NonNull android.graphics.Point); + method @NonNull public android.hardware.camera2.params.Face.Builder setScore(int); + } + public final class InputConfiguration { ctor public InputConfiguration(int, int, int); ctor public InputConfiguration(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b1d458cc1746..3a808c796634 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -167,6 +167,7 @@ package android.app { method public void setEligibleForLegacyPermissionPrompt(boolean); method public static void setExitTransitionTimeout(long); method public void setLaunchActivityType(int); + method public void setLaunchTaskDisplayAreaFeatureId(int); method public void setLaunchWindowingMode(int); method public void setLaunchedFromBubble(boolean); method public void setTaskAlwaysOnTop(boolean); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index d6441a2b629b..00ab559697f4 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -22,6 +22,7 @@ import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; +import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import android.annotation.IntDef; import android.annotation.NonNull; @@ -223,6 +224,14 @@ public class ActivityOptions extends ComponentOptions { "android.activity.launchTaskDisplayAreaToken"; /** + * The task display area feature id the activity should be launched into. + * @see #setLaunchTaskDisplayAreaFeatureId(int) + * @hide + */ + private static final String KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID = + "android.activity.launchTaskDisplayAreaFeatureId"; + + /** * The root task token the activity should be launched into. * @see #setLaunchRootTask(WindowContainerToken) * @hide @@ -432,6 +441,7 @@ public class ActivityOptions extends ComponentOptions { private int mLaunchDisplayId = INVALID_DISPLAY; private int mCallerDisplayId = INVALID_DISPLAY; private WindowContainerToken mLaunchTaskDisplayArea; + private int mLaunchTaskDisplayAreaFeatureId = FEATURE_UNDEFINED; private WindowContainerToken mLaunchRootTask; private IBinder mLaunchTaskFragmentToken; @WindowConfiguration.WindowingMode @@ -1225,6 +1235,8 @@ public class ActivityOptions extends ComponentOptions { mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY); mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY); mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN); + mLaunchTaskDisplayAreaFeatureId = opts.getInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, + FEATURE_UNDEFINED); mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN); mLaunchTaskFragmentToken = opts.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN); mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED); @@ -1585,6 +1597,23 @@ public class ActivityOptions extends ComponentOptions { } /** @hide */ + public int getLaunchTaskDisplayAreaFeatureId() { + return mLaunchTaskDisplayAreaFeatureId; + } + + /** + * Sets the TaskDisplayArea feature Id the activity should launch into. + * Note: It is possible to have TaskDisplayAreas with the same featureId on multiple displays. + * If launch display id is not specified, the TaskDisplayArea on the default display will be + * used. + * @hide + */ + @TestApi + public void setLaunchTaskDisplayAreaFeatureId(int launchTaskDisplayAreaFeatureId) { + mLaunchTaskDisplayAreaFeatureId = launchTaskDisplayAreaFeatureId; + } + + /** @hide */ public WindowContainerToken getLaunchRootTask() { return mLaunchRootTask; } @@ -2075,6 +2104,9 @@ public class ActivityOptions extends ComponentOptions { if (mLaunchTaskDisplayArea != null) { b.putParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN, mLaunchTaskDisplayArea); } + if (mLaunchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) { + b.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mLaunchTaskDisplayAreaFeatureId); + } if (mLaunchRootTask != null) { b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask); } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 1b51faf3d429..633b734d4adc 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -800,18 +800,15 @@ public final class CompanionDeviceManager { * * @hide */ + @Deprecated @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) throws DeviceNotAssociatedException { - try { - mService.dispatchMessage(messageId, associationId, message); - } catch (RemoteException e) { - ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); - throw e.rethrowFromSystemServer(); - } + Log.w(LOG_TAG, "dispatchMessage replaced by attachSystemDataTransport"); } /** {@hide} */ + @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void attachSystemDataTransport(int associationId, @NonNull InputStream in, @NonNull OutputStream out) throws DeviceNotAssociatedException { synchronized (mTransports) { @@ -830,6 +827,7 @@ public final class CompanionDeviceManager { } /** {@hide} */ + @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void detachSystemDataTransport(int associationId) throws DeviceNotAssociatedException { synchronized (mTransports) { @@ -927,7 +925,7 @@ public final class CompanionDeviceManager { * * <p>The permission transfer doesn't happen immediately after the call or user consented. * The app needs to trigger the system data transfer manually by calling - * {@link #startSystemDataTransfer(int)}, when it confirms the communication channel between + * {@code #startSystemDataTransfer(int)}, when it confirms the communication channel between * the two devices is established.</p> * * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the association @@ -965,8 +963,8 @@ public final class CompanionDeviceManager { * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association * of the companion device recorded by CompanionDeviceManager * @throws DeviceNotAssociatedException Exception if the companion device is not associated - * * @deprecated Use {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver)} instead. + * @hide */ @Deprecated @UserHandleAware @@ -993,6 +991,7 @@ public final class CompanionDeviceManager { * @param executor The executor which will be used to invoke the result callback. * @param result The callback to notify the app of the result of the system data transfer. * @throws DeviceNotAssociatedException Exception if the companion device is not associated + * @hide */ @UserHandleAware public void startSystemDataTransfer( @@ -1125,12 +1124,14 @@ public final class CompanionDeviceManager { public void start() throws IOException { final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(); - mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(pair[0]); - mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(pair[0]); + final ParcelFileDescriptor localFd = pair[0]; + final ParcelFileDescriptor remoteFd = pair[1]; + mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(localFd); + mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(localFd); try { mService.attachSystemDataTransport(mContext.getPackageName(), - mContext.getUserId(), mAssociationId, pair[1]); + mContext.getUserId(), mAssociationId, remoteFd); } catch (RemoteException e) { throw new IOException("Failed to configure transport", e); } diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index 83d2713ea114..a79983a653f0 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -156,9 +156,12 @@ public abstract class CompanionDeviceService extends Service { * @param messageId system assigned id of the message to be sent * @param associationId association id of the associated device * @param message message to be sent + * @hide */ + @Deprecated public void onMessageDispatchedFromSystem(int messageId, int associationId, @NonNull byte[] message) { + Log.w(LOG_TAG, "Replaced by attachSystemDataTransport"); // do nothing. Companion apps can override this function for system to send messages. } @@ -185,22 +188,14 @@ public abstract class CompanionDeviceService extends Service { * @param messageId id of the message * @param associationId id of the associated device * @param message message received from the associated device + * @hide */ + @Deprecated @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessageToSystem(int messageId, int associationId, @NonNull byte[] message) throws DeviceNotAssociatedException { - if (getBaseContext() == null) { - Log.e(LOG_TAG, "Dispatch failed. Start your service before calling this method."); - return; - } - CompanionDeviceManager companionDeviceManager = - getSystemService(CompanionDeviceManager.class); - if (companionDeviceManager != null) { - companionDeviceManager.dispatchMessage(messageId, associationId, message); - } else { - Log.e(LOG_TAG, "CompanionDeviceManager is null. Can't dispatch messages."); - } + Log.w(LOG_TAG, "Replaced by attachSystemDataTransport"); } /** @@ -223,10 +218,13 @@ public abstract class CompanionDeviceService extends Service { * device * @hide */ + @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void attachSystemDataTransport(int associationId, @NonNull InputStream in, @NonNull OutputStream out) throws DeviceNotAssociatedException { getSystemService(CompanionDeviceManager.class) - .attachSystemDataTransport(associationId, in, out); + .attachSystemDataTransport(associationId, + Objects.requireNonNull(in), + Objects.requireNonNull(out)); } /** @@ -236,6 +234,7 @@ public abstract class CompanionDeviceService extends Service { * @param associationId id of the associated device * @hide */ + @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void detachSystemDataTransport(int associationId) throws DeviceNotAssociatedException { getSystemService(CompanionDeviceManager.class) @@ -299,13 +298,5 @@ public abstract class CompanionDeviceService extends Service { public void onDeviceDisappeared(AssociationInfo associationInfo) { mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo)); } - - @Override - public void onMessageDispatchedFromSystem(int messageId, int associationId, - @NonNull byte[] message) { - mMainHandler.postAtFrontOfQueue( - () -> mService.onMessageDispatchedFromSystem(messageId, associationId, - message)); - } } } diff --git a/core/java/android/companion/CompanionException.java b/core/java/android/companion/CompanionException.java index fb78e8df446e..9a92401b8d67 100644 --- a/core/java/android/companion/CompanionException.java +++ b/core/java/android/companion/CompanionException.java @@ -19,7 +19,10 @@ package android.companion; import android.annotation.NonNull; /** - * {@code CompanionException} can be thrown during the companion system data transfer process. + * {@code CompanionException} can be thrown during the companion system data + * transfer process. + * + * @hide */ public class CompanionException extends RuntimeException { /** @hide */ diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 42f908396799..6e6e18765639 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -63,8 +63,6 @@ interface ICompanionDeviceManager { void createAssociation(in String packageName, in String macAddress, int userId, in byte[] certificate); - void dispatchMessage(int messageId, int associationId, in byte[] message); - void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId); void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId); diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl index 3c90b86ca8c0..fa6850882132 100644 --- a/core/java/android/companion/ICompanionDeviceService.aidl +++ b/core/java/android/companion/ICompanionDeviceService.aidl @@ -22,5 +22,4 @@ import android.companion.AssociationInfo; oneway interface ICompanionDeviceService { void onDeviceAppeared(in AssociationInfo associationInfo); void onDeviceDisappeared(in AssociationInfo associationInfo); - void onMessageDispatchedFromSystem(in int messageId, in int associationId, in byte[] message); } diff --git a/core/java/android/hardware/camera2/params/Face.java b/core/java/android/hardware/camera2/params/Face.java index fa1202b87efe..1d9a5a3a5153 100644 --- a/core/java/android/hardware/camera2/params/Face.java +++ b/core/java/android/hardware/camera2/params/Face.java @@ -69,10 +69,6 @@ public final class Face { * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null.</p> * - * <p>This constructor is public to allow for easier application testing by - * creating custom object instances. It's not necessary to construct these - * objects during normal use of the camera API.</p> - * * @param bounds Bounds of the face. * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. * @param id A unique ID per face visible to the tracker. @@ -87,6 +83,8 @@ public final class Face { * or if id is {@value #ID_UNSUPPORTED} and * leftEyePosition/rightEyePosition/mouthPosition aren't all null, * or else if id is negative. + * + * @hide */ public Face(@NonNull Rect bounds, int score, int id, @NonNull Point leftEyePosition, @NonNull Point rightEyePosition, @@ -106,10 +104,6 @@ public final class Face { * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition, * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p> * - * <p>This constructor is public to allow for easier application testing by - * creating custom object instances. It's not necessary to construct these - * objects during normal use of the camera API.</p> - * * @param bounds Bounds of the face. * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. * @@ -117,6 +111,8 @@ public final class Face { * if bounds is {@code null}, * or if the confidence is not in the range of * {@value #SCORE_MIN}-{@value #SCORE_MAX}. + * + * @hide */ public Face(@NonNull Rect bounds, int score) { init(bounds, score, ID_UNSUPPORTED, @@ -130,16 +126,14 @@ public final class Face { @Nullable Point leftEyePosition, @Nullable Point rightEyePosition, @Nullable Point mouthPosition) { checkNotNull("bounds", bounds); - if (score < SCORE_MIN || score > SCORE_MAX) { - throw new IllegalArgumentException("Confidence out of range"); - } else if (id < 0 && id != ID_UNSUPPORTED) { - throw new IllegalArgumentException("Id out of range"); - } + checkScore(score); + checkId(id); if (id == ID_UNSUPPORTED) { checkNull("leftEyePosition", leftEyePosition); checkNull("rightEyePosition", rightEyePosition); checkNull("mouthPosition", mouthPosition); } + checkFace(leftEyePosition, rightEyePosition, mouthPosition); mBounds = bounds; mScore = score; @@ -156,7 +150,7 @@ public final class Face { * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0) * representing the top-left corner of the active array rectangle.</p> * - * <p>There is no constraints on the the Rectangle value other than it + * <p>There is no constraints on the Rectangle value other than it * is not-{@code null}.</p> */ public Rect getBounds() { @@ -190,7 +184,7 @@ public final class Face { * If the face leaves the field-of-view and comes back, it will get a new * id.</p> * - * <p>This is an optional field, may not be supported on all devices. + * <p>This is an optional field and may not be supported on all devices. * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null. When devices @@ -212,7 +206,7 @@ public final class Face { * * <p>The coordinates are in * the same space as the ones for {@link #getBounds}. This is an - * optional field, may not be supported on all devices. If not + * optional field and may not be supported on all devices. If not * supported, the value will always be set to null. * This value will always be null only if {@link #getId()} returns * {@value #ID_UNSUPPORTED}.</p> @@ -228,7 +222,7 @@ public final class Face { * * <p>The coordinates are * in the same space as the ones for {@link #getBounds}.This is an - * optional field, may not be supported on all devices. If not + * optional field and may not be supported on all devices. If not * supported, the value will always be set to null. * This value will always be null only if {@link #getId()} returns * {@value #ID_UNSUPPORTED}.</p> @@ -244,7 +238,7 @@ public final class Face { * * <p>The coordinates are in * the same space as the ones for {@link #getBounds}. This is an optional - * field, may not be supported on all devices. If not + * field and may not be supported on all devices. If not * supported, the value will always be set to null. * This value will always be null only if {@link #getId()} returns * {@value #ID_UNSUPPORTED}.</p> @@ -277,4 +271,253 @@ public final class Face { throw new IllegalArgumentException(name + " was required to be null, but it wasn't"); } } + + private static void checkScore(int score) { + if (score < SCORE_MIN || score > SCORE_MAX) { + throw new IllegalArgumentException("Confidence out of range"); + } + } + + private static void checkId(int id) { + if (id < 0 && id != ID_UNSUPPORTED) { + throw new IllegalArgumentException("Id out of range"); + } + } + + private static void checkFace(@Nullable Point leftEyePosition, + @Nullable Point rightEyePosition, @Nullable Point mouthPosition) { + if (leftEyePosition != null || rightEyePosition != null || mouthPosition != null) { + if (leftEyePosition == null || rightEyePosition == null || mouthPosition == null) { + throw new IllegalArgumentException("If any of leftEyePosition, rightEyePosition, " + + "or mouthPosition are non-null, all three must be non-null."); + } + } + } + + /** + * Builds a Face object. + * + * <p>This builder is public to allow for easier application testing by + * creating custom object instances. It's not necessary to construct these + * objects during normal use of the camera API.</p> + */ + public static final class Builder { + private long mBuilderFieldsSet = 0L; + + private static final long FIELD_BOUNDS = 1 << 1; + private static final long FIELD_SCORE = 1 << 2; + private static final long FIELD_ID = 1 << 3; + private static final long FIELD_LEFT_EYE = 1 << 4; + private static final long FIELD_RIGHT_EYE = 1 << 5; + private static final long FIELD_MOUTH = 1 << 6; + private static final long FIELD_BUILT = 1 << 0; + + private static final String FIELD_NAME_BOUNDS = "bounds"; + private static final String FIELD_NAME_SCORE = "score"; + private static final String FIELD_NAME_LEFT_EYE = "left eye"; + private static final String FIELD_NAME_RIGHT_EYE = "right eye"; + private static final String FIELD_NAME_MOUTH = "mouth"; + + private Rect mBounds = null; + private int mScore = 0; + private int mId = ID_UNSUPPORTED; + private Point mLeftEye = null; + private Point mRightEye = null; + private Point mMouth = null; + + public Builder() { + // Empty + } + + public Builder(@NonNull Face current) { + mBounds = current.mBounds; + mScore = current.mScore; + mId = current.mId; + mLeftEye = current.mLeftEye; + mRightEye = current.mRightEye; + mMouth = current.mMouth; + } + + /** + * Bounds of the face. + * + * <p>A rectangle relative to the sensor's + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0) + * representing the top-left corner of the active array rectangle.</p> + * + * <p>There is no constraints on the Rectangle value other than it + * is not-{@code null}.</p> + * + * @param bounds Bounds of the face. + * @return This builder. + */ + public @NonNull Builder setBounds(@NonNull Rect bounds) { + checkNotUsed(); + mBuilderFieldsSet |= FIELD_BOUNDS; + mBounds = bounds; + return this; + } + + /** + * The confidence level for the detection of the face. + * + * <p>The range is {@value #SCORE_MIN} to {@value #SCORE_MAX}. + * {@value #SCORE_MAX} is the highest confidence.</p> + * + * <p>Depending on the device, even very low-confidence faces may be + * listed, so applications should filter out faces with low confidence, + * depending on the use case. For a typical point-and-shoot camera + * application that wishes to display rectangles around detected faces, + * filtering out faces with confidence less than half of {@value #SCORE_MAX} + * is recommended.</p> + * + * @see #SCORE_MAX + * @see #SCORE_MIN + * + * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}. + * @return This builder. + */ + public @NonNull Builder setScore(int score) { + checkNotUsed(); + checkScore(score); + mBuilderFieldsSet |= FIELD_SCORE; + mScore = score; + return this; + } + + /** + * An unique id per face while the face is visible to the tracker. + * + * <p> + * If the face leaves the field-of-view and comes back, it will get a new + * id.</p> + * + * <p>This is an optional field and may not be supported on all devices. + * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and + * mouthPositions should be {@code null}. Otherwise, each of leftEyePosition, + * rightEyePosition, and mouthPosition may be independently null or not-null. When devices + * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as + * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult}, + * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p> + * + * <p>This value should either be {@value #ID_UNSUPPORTED} or + * otherwise greater than {@code 0}.</p> + * + * @see #ID_UNSUPPORTED + * + * @param id A unique ID per face visible to the tracker. + * @return This builder. + */ + public @NonNull Builder setId(int id) { + checkNotUsed(); + checkId(id); + mBuilderFieldsSet |= FIELD_ID; + mId = id; + return this; + } + + /** + * The coordinates of the center of the left eye. + * + * <p>The coordinates should be + * in the same space as the ones for {@link #setBounds}. This is an + * optional field and may not be supported on all devices. If not + * supported, the value should always be unset or set to null. + * This value should always be null if {@link #setId} is called with + * {@value #ID_UNSUPPORTED}.</p> + * + * @param leftEyePosition The position of the left eye. + * @return This builder. + */ + public @NonNull Builder setLeftEyePosition(@NonNull Point leftEyePosition) { + checkNotUsed(); + mBuilderFieldsSet |= FIELD_LEFT_EYE; + mLeftEye = leftEyePosition; + return this; + } + + /** + * The coordinates of the center of the right eye. + * + * <p>The coordinates should be + * in the same space as the ones for {@link #setBounds}.This is an + * optional field and may not be supported on all devices. If not + * supported, the value should always be set to null. + * This value should always be null if {@link #setId} is called with + * {@value #ID_UNSUPPORTED}.</p> + * + * @param rightEyePosition The position of the right eye. + * @return This builder. + */ + public @NonNull Builder setRightEyePosition(@NonNull Point rightEyePosition) { + checkNotUsed(); + mBuilderFieldsSet |= FIELD_RIGHT_EYE; + mRightEye = rightEyePosition; + return this; + } + + /** + * The coordinates of the center of the mouth. + * + * <p>The coordinates should be in + * the same space as the ones for {@link #setBounds}. This is an optional + * field and may not be supported on all devices. If not + * supported, the value should always be set to null. + * This value should always be null if {@link #setId} is called with + * {@value #ID_UNSUPPORTED}.</p> + * </p> + * + * @param mouthPosition The position of the mouth. + * @return This builder. + */ + public @NonNull Builder setMouthPosition(@NonNull Point mouthPosition) { + checkNotUsed(); + mBuilderFieldsSet |= FIELD_MOUTH; + mMouth = mouthPosition; + return this; + } + + /** + * Returns an instance of <code>Face</code> created from the fields set + * on this builder. + * + * @return A Face. + */ + public @NonNull Face build() { + checkNotUsed(); + checkFieldSet(FIELD_BOUNDS, FIELD_NAME_BOUNDS); + checkFieldSet(FIELD_SCORE, FIELD_NAME_SCORE); + if (mId == ID_UNSUPPORTED) { + checkIdUnsupportedThenNull(mLeftEye, FIELD_NAME_LEFT_EYE); + checkIdUnsupportedThenNull(mRightEye, FIELD_NAME_RIGHT_EYE); + checkIdUnsupportedThenNull(mMouth, FIELD_NAME_MOUTH); + } + checkFace(mLeftEye, mRightEye, mMouth); + + mBuilderFieldsSet |= FIELD_BUILT; + + return new Face(mBounds, mScore, mId, mLeftEye, mRightEye, mMouth); + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & FIELD_BUILT) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + + private void checkFieldSet(long field, String fieldName) { + if ((mBuilderFieldsSet & field) == 0) { + throw new IllegalStateException( + "Field \"" + fieldName + "\" must be set before building."); + } + } + + private void checkIdUnsupportedThenNull(Object obj, String fieldName) { + if (obj != null) { + throw new IllegalArgumentException("Field \"" + fieldName + + "\" must be unset or null if id is ID_UNSUPPORTED."); + } + } + } } diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 8f241722a445..e960df112ae3 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -31,18 +31,15 @@ import android.os.ResultReceiver; import android.util.Log; import android.view.InputChannel; import android.view.MotionEvent; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodSession; import android.view.inputmethod.InputMethodSubtype; -import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInputMethod; -import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.IInputMethodSession; import com.android.internal.inputmethod.IInputMethodSessionCallback; import com.android.internal.inputmethod.IRemoteInputConnection; @@ -98,9 +95,9 @@ class IInputMethodWrapper extends IInputMethod.Stub * * <p>This field must be set and cleared only from the binder thread(s), where the system * guarantees that {@link #bindInput(InputBinding)}, - * {@link #startInput(IBinder, IRemoteInputConnection, int, EditorInfo, boolean, boolean)}, and - * {@link #unbindInput()} are called with the same order as the original calls - * in {@link com.android.server.inputmethod.InputMethodManagerService}. + * {@link #startInput(IInputMethod.StartInputParams)}, and {@link #unbindInput()} are called + * with the same order as the original calls in + * {@link com.android.server.inputmethod.InputMethodManagerService}. * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p> */ @Nullable @@ -174,17 +171,9 @@ class IInputMethodWrapper extends IInputMethod.Stub args.recycle(); return; } - case DO_INITIALIZE_INTERNAL: { - SomeArgs args = (SomeArgs) msg.obj; - try { - inputMethod.initializeInternal((IBinder) args.arg1, - (IInputMethodPrivilegedOperations) args.arg2, msg.arg1, - (boolean) args.arg3, msg.arg2); - } finally { - args.recycle(); - } + case DO_INITIALIZE_INTERNAL: + inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj); return; - } case DO_SET_INPUT_CONTEXT: { inputMethod.bindInput((InputBinding)msg.obj); return; @@ -194,16 +183,10 @@ class IInputMethodWrapper extends IInputMethod.Stub return; case DO_START_INPUT: { final SomeArgs args = (SomeArgs) msg.obj; - final IBinder startInputToken = (IBinder) args.arg1; - final InputConnection ic = (InputConnection) args.arg2; - final EditorInfo info = (EditorInfo) args.arg3; - final ImeOnBackInvokedDispatcher imeDispatcher = - (ImeOnBackInvokedDispatcher) args.arg4; - final boolean restarting = args.argi1 == 1; - @InputMethodNavButtonFlags - final int navButtonFlags = args.argi2; - inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken, - navButtonFlags, imeDispatcher); + final InputConnection inputConnection = (InputConnection) args.arg1; + final IInputMethod.StartInputParams params = + (IInputMethod.StartInputParams) args.arg2; + inputMethod.dispatchStartInput(inputConnection, params); args.recycle(); return; } @@ -301,11 +284,8 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, - int configChanges, boolean stylusHwSupported, - @InputMethodNavButtonFlags int navButtonFlags) { - mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL, - configChanges, navButtonFlags, token, privOps, stylusHwSupported)); + public void initializeInternal(@NonNull IInputMethod.InitParams params) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_INITIALIZE_INTERNAL, params)); } @BinderThread @@ -345,28 +325,19 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection, - EditorInfo editorInfo, boolean restarting, - @InputMethodNavButtonFlags int navButtonFlags, - @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { + public void startInput(@NonNull IInputMethod.StartInputParams params) { if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); mCancellationGroup = new CancellationGroup(); } - editorInfo.makeCompatible(mTargetSdkVersion); + params.editorInfo.makeCompatible(mTargetSdkVersion); - final InputConnection ic = inputConnection == null ? null - : new RemoteInputConnection(mTarget, inputConnection, mCancellationGroup); + final InputConnection ic = params.remoteInputConnection == null ? null + : new RemoteInputConnection(mTarget, params.remoteInputConnection, + mCancellationGroup); - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = startInputToken; - args.arg2 = ic; - args.arg3 = editorInfo; - args.argi1 = restarting ? 1 : 0; - args.argi2 = navButtonFlags; - args.arg4 = imeDispatcher; - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_INPUT, args)); + mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT, ic, params)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index e442e6dcd8a8..c02f870edbc2 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -144,7 +144,7 @@ import android.window.WindowMetricsHelper; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInputContentUriToken; -import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; @@ -698,23 +698,21 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public final void initializeInternal(@NonNull IBinder token, - IInputMethodPrivilegedOperations privilegedOperations, int configChanges, - boolean stylusHwSupported, @InputMethodNavButtonFlags int navButtonFlags) { + public final void initializeInternal(@NonNull IInputMethod.InitParams params) { if (mDestroyed) { Log.i(TAG, "The InputMethodService has already onDestroyed()." + "Ignore the initialization."); return; } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal"); - mConfigTracker.onInitialize(configChanges); - mPrivOps.set(privilegedOperations); - InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps); - if (stylusHwSupported) { + mConfigTracker.onInitialize(params.configChanges); + mPrivOps.set(params.privilegedOperations); + InputMethodPrivilegedOperationsRegistry.put(params.token, mPrivOps); + if (params.stylusHandWritingSupported) { mInkWindow = new InkWindow(mWindow.getContext()); } - mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags); - attachToken(token); + mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags); + attachToken(params.token); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -821,25 +819,23 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, - @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags, - @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { - mPrivOps.reportStartInputAsync(startInputToken); - mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags); - if (restarting) { - restartInput(inputConnection, editorInfo); + public final void dispatchStartInput(@Nullable InputConnection inputConnection, + @NonNull IInputMethod.StartInputParams params) { + mPrivOps.reportStartInputAsync(params.startInputToken); + mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags); + if (params.restarting) { + restartInput(inputConnection, params.editorInfo); } else { - startInput(inputConnection, editorInfo); + startInput(inputConnection, params.editorInfo); } // Update the IME dispatcher last, so that the previously registered back callback // (if any) can be unregistered using the old dispatcher if {@link #doFinishInput()} // is called from {@link #startInput(InputConnection, EditorInfo)} or // {@link #restartInput(InputConnection, EditorInfo)}. - mImeDispatcher = imeDispatcher; + mImeDispatcher = params.imeDispatcher; if (mWindow != null) { mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher( - imeDispatcher); + params.imeDispatcher); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 657c0b7801b5..e93b80251d39 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2820,10 +2820,6 @@ public final class ViewRootImpl implements ViewParent, // Execute enqueued actions on every traversal in case a detached view enqueued an action getRunQueue().executeActions(mAttachInfo.mHandler); - if (mApplyInsetsRequested) { - dispatchApplyInsets(host); - } - if (mFirst) { // make sure touch mode code executes by setting cached value // to opposite of the added touch mode. @@ -2887,6 +2883,18 @@ public final class ViewRootImpl implements ViewParent, } } + if (mApplyInsetsRequested) { + dispatchApplyInsets(host); + if (mLayoutRequested) { + // Short-circuit catching a new layout request here, so + // we don't need to go through two layout passes when things + // change due to fitting system windows, which can happen a lot. + windowSizeMayChange |= measureHierarchy(host, lp, + mView.getContext().getResources(), + desiredWindowWidth, desiredWindowHeight); + } + } + if (layoutRequested) { // Clear this now, so that if anything requests a layout in the // rest of this function we will catch it and re-run a full diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 54ff11ccac9d..95add2978879 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -29,10 +29,9 @@ import android.util.Log; import android.view.InputChannel; import android.view.MotionEvent; import android.view.View; -import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; -import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.internal.inputmethod.InputMethodNavButtonFlags; @@ -100,21 +99,12 @@ public interface InputMethod { * unique token for the session it has with the system service as well as * IPC endpoint to do some other privileged operations. * - * @param token special token for the system to identify - * {@link InputMethodService} - * @param privilegedOperations IPC endpoint to do some privileged - * operations that are allowed only to the - * current IME. - * @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME. - * @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME. - * @param navButtonFlags The initial state of {@link InputMethodNavButtonFlags}. + * @param params Contains parameters to initialize the {@link InputMethodService}. * @hide */ @MainThread - default void initializeInternal(IBinder token, - IInputMethodPrivilegedOperations privilegedOperations, int configChanges, - boolean stylusHwSupported, @InputMethodNavButtonFlags int navButtonFlags) { - attachToken(token); + default void initializeInternal(@NonNull IInputMethod.InitParams params) { + attachToken(params.token); } /** @@ -210,47 +200,30 @@ public interface InputMethod { public void restartInput(InputConnection inputConnection, EditorInfo editorInfo); /** - * This method is called when {@code {@link #startInput(InputConnection, EditorInfo)} or - * {@code {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched. + * This method is called when {@link #startInput(InputConnection, EditorInfo)} or + * {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched. * - * <p>Note: This method is hidden because the {@code startInputToken} that this method is - * dealing with is one of internal details, which should not be exposed to the IME developers. - * If you override this method, you are responsible for not breaking existing IMEs that expect + * <p>Note: This method is hidden because {@link IInputMethod.StartInputParams} is an internal + * details, which should not be exposed to the IME developers. If you override this method, you + * are responsible for not breaking existing IMEs that expect * {@link #startInput(InputConnection, EditorInfo)} to be still called back.</p> * * @param inputConnection optional specific input connection for communicating with the text * box; if {@code null}, you should use the generic bound input * connection - * @param editorInfo information about the text box (typically, an EditText) that requests input - * @param restarting {@code false} if this corresponds to - * {@link #startInput(InputConnection, EditorInfo)}. Otherwise this - * corresponds to {@link #restartInput(InputConnection, EditorInfo)}. - * @param startInputToken a token that identifies a logical session that starts with this method - * call. Some internal IPCs such as {@link - * InputMethodManager#setImeWindowStatus(IBinder, IBinder, int, int)} - * require this token to work, and you have to keep the token alive until - * the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as - * long as your implementation of {@link InputMethod} relies on such - * IPCs - * @param navButtonFlags {@link InputMethodNavButtonFlags} in the initial state of this session. - * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher} to be set on the - * IME's {@link android.window.WindowOnBackInvokedDispatcher}, so that IME - * {@link android.window.OnBackInvokedCallback}s can be forwarded to - * the client requesting to start input. + * @param params Raw object of {@link IInputMethod.StartInputParams}. * @see #startInput(InputConnection, EditorInfo) * @see #restartInput(InputConnection, EditorInfo) * @see EditorInfo * @hide */ @MainThread - default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, - @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags, - @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { - if (restarting) { - restartInput(inputConnection, editorInfo); + default void dispatchStartInput(@Nullable InputConnection inputConnection, + @NonNull IInputMethod.StartInputParams params) { + if (params.restarting) { + restartInput(inputConnection, params.editorInfo); } else { - startInput(inputConnection, editorInfo); + startInput(inputConnection, params.editorInfo); } } diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl index b8196157ffd9..8bab5c31217d 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl @@ -35,8 +35,16 @@ import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; * Top-level interface to an input method component (implemented in a Service). */ oneway interface IInputMethod { - void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, - int configChanges, boolean stylusHwSupported, int navigationBarFlags); + + parcelable InitParams { + IBinder token; + IInputMethodPrivilegedOperations privilegedOperations; + int configChanges; + boolean stylusHandWritingSupported; + int navigationBarFlags; + } + + void initializeInternal(in InitParams params); void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo, in IInlineSuggestionsRequestCallback cb); @@ -45,9 +53,16 @@ oneway interface IInputMethod { void unbindInput(); - void startInput(in IBinder startInputToken, in IRemoteInputConnection inputConnection, - in EditorInfo editorInfo, boolean restarting, int navigationBarFlags, - in ImeOnBackInvokedDispatcher imeDispatcher); + parcelable StartInputParams { + IBinder startInputToken; + IRemoteInputConnection remoteInputConnection; + EditorInfo editorInfo; + boolean restarting; + int navigationBarFlags; + ImeOnBackInvokedDispatcher imeDispatcher; + } + + void startInput(in StartInputParams params); void onNavButtonFlagsChanged(int navButtonFlags); diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java index be04b6c91a8a..6bd498c583db 100644 --- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java +++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java @@ -16,12 +16,15 @@ package android.companion; +import android.content.Context; import android.os.SystemClock; import android.test.InstrumentationTestCase; import android.util.Log; import com.android.internal.util.HexDump; +import libcore.util.EmptyArray; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FilterInputStream; @@ -31,69 +34,81 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Random; public class SystemDataTransportTest extends InstrumentationTestCase { private static final String TAG = "SystemDataTransportTest"; - private static final int COMMAND_INVALID = 0xF00DCAFE; - private static final int COMMAND_PING_V0 = 0x50490000; - private static final int COMMAND_PONG_V0 = 0x504F0000; + private static final int MESSAGE_INVALID = 0xF00DCAFE; + + private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ???? + private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN + + private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!! + private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC + private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI + private Context mContext; private CompanionDeviceManager mCdm; + private int mAssociationId; @Override protected void setUp() throws Exception { super.setUp(); - mCdm = getInstrumentation().getTargetContext() - .getSystemService(CompanionDeviceManager.class); + mContext = getInstrumentation().getTargetContext(); + mCdm = mContext.getSystemService(CompanionDeviceManager.class); + mAssociationId = createAssociation(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + mCdm.disassociate(mAssociationId); } public void testPingHandRolled() { // NOTE: These packets are explicitly hand-rolled to verify wire format; // the remainder of the tests are fine using generated packets - // PING v0 with payload "HELLO WORLD!" + // MESSAGE_REQUEST_PING with payload "HELLO WORLD!" final byte[] input = new byte[] { - 0x50, 0x49, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0C, + 0x63, (byte) 0x80, 0x73, 0x78, // message: MESSAGE_REQUEST_PING + 0x00, 0x00, 0x00, 0x2A, // sequence: 42 + 0x00, 0x00, 0x00, 0x0C, // length: 12 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21, }; - // PONG v0 with payload "HELLO WORLD!" + // MESSAGE_RESPONSE_SUCCESS with payload "HELLO WORLD!" final byte[] expected = new byte[] { - 0x50, 0x4F, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0C, + 0x33, (byte) 0x83, (byte) 0x85, 0x67, // message: MESSAGE_RESPONSE_SUCCESS + 0x00, 0x00, 0x00, 0x2A, // sequence: 42 + 0x00, 0x00, 0x00, 0x0C, // length: 12 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21, }; - - final ByteArrayInputStream in = new ByteArrayInputStream(input); - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - mCdm.attachSystemDataTransport(42, in, out); - - final byte[] actual = waitForByteArray(out, expected.length); - assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual)); + assertTransportBehavior(input, expected); } public void testPingTrickle() { - final byte[] input = generatePacket(COMMAND_PING_V0, TAG); - final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG); + final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, TAG); + final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, TAG); final ByteArrayInputStream in = new ByteArrayInputStream(input); final ByteArrayOutputStream out = new ByteArrayOutputStream(); - mCdm.attachSystemDataTransport(42, new TrickleInputStream(in), out); + mCdm.attachSystemDataTransport(mAssociationId, new TrickleInputStream(in), out); final byte[] actual = waitForByteArray(out, expected.length); assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual)); } public void testPingDelay() { - final byte[] input = generatePacket(COMMAND_PING_V0, TAG); - final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG); + final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, TAG); + final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, TAG); final ByteArrayInputStream in = new ByteArrayInputStream(input); final ByteArrayOutputStream out = new ByteArrayOutputStream(); - mCdm.attachSystemDataTransport(42, new DelayingInputStream(in, 1000), + mCdm.attachSystemDataTransport(mAssociationId, new DelayingInputStream(in, 1000), new DelayingOutputStream(out, 1000)); final byte[] actual = waitForByteArray(out, expected.length); @@ -104,49 +119,54 @@ public class SystemDataTransportTest extends InstrumentationTestCase { final byte[] blob = new byte[500_000]; new Random().nextBytes(blob); - final byte[] input = generatePacket(COMMAND_PING_V0, blob); - final byte[] expected = generatePacket(COMMAND_PONG_V0, blob); - - final ByteArrayInputStream in = new ByteArrayInputStream(input); - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - mCdm.attachSystemDataTransport(42, in, out); - - final byte[] actual = waitForByteArray(out, expected.length); - assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual)); + final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, blob); + final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, blob); + assertTransportBehavior(input, expected); } public void testMutiplePingPing() { - final byte[] input = concat(generatePacket(COMMAND_PING_V0, "red"), - generatePacket(COMMAND_PING_V0, "green")); - final byte[] expected = concat(generatePacket(COMMAND_PONG_V0, "red"), - generatePacket(COMMAND_PONG_V0, "green")); - - final ByteArrayInputStream in = new ByteArrayInputStream(input); - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - mCdm.attachSystemDataTransport(42, in, out); - - final byte[] actual = waitForByteArray(out, expected.length); - assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual)); + final byte[] input = concat( + generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, "red"), + generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green")); + final byte[] expected = concat( + generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, "red"), + generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green")); + assertTransportBehavior(input, expected); } public void testMultipleInvalidPing() { - final byte[] input = concat(generatePacket(COMMAND_INVALID, "red"), - generatePacket(COMMAND_PING_V0, "green")); - final byte[] expected = generatePacket(COMMAND_PONG_V0, "green"); + final byte[] input = concat( + generatePacket(MESSAGE_INVALID, /* sequence */ 1, "red"), + generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green")); + final byte[] expected = + generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green"); + assertTransportBehavior(input, expected); + } - final ByteArrayInputStream in = new ByteArrayInputStream(input); - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - mCdm.attachSystemDataTransport(42, in, out); + public void testMultipleInvalidRequestPing() { + final byte[] input = concat( + generatePacket(MESSAGE_REQUEST_INVALID, /* sequence */ 1, "red"), + generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green")); + final byte[] expected = concat( + generatePacket(MESSAGE_RESPONSE_FAILURE, /* sequence */ 1), + generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green")); + assertTransportBehavior(input, expected); + } - final byte[] actual = waitForByteArray(out, expected.length); - assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual)); + public void testMultipleInvalidResponsePing() { + final byte[] input = concat( + generatePacket(MESSAGE_RESPONSE_INVALID, /* sequence */ 1, "red"), + generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green")); + final byte[] expected = + generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green"); + assertTransportBehavior(input, expected); } public void testDoubleAttach() { // Connect an empty connection that is stalled out final InputStream in = new EmptyInputStream(); final OutputStream out = new ByteArrayOutputStream(); - mCdm.attachSystemDataTransport(42, in, out); + mCdm.attachSystemDataTransport(mAssociationId, in, out); SystemClock.sleep(1000); // Attach a second transport that has some packets; it should disconnect @@ -154,17 +174,57 @@ public class SystemDataTransportTest extends InstrumentationTestCase { testPingHandRolled(); } - public static byte[] concat(byte[] a, byte[] b) { - return ByteBuffer.allocate(a.length + b.length).put(a).put(b).array(); + public static byte[] concat(byte[]... blobs) { + int length = 0; + for (byte[] blob : blobs) { + length += blob.length; + } + final ByteBuffer buf = ByteBuffer.allocate(length); + for (byte[] blob : blobs) { + buf.put(blob); + } + return buf.array(); + } + + public static byte[] generatePacket(int message, int sequence) { + return generatePacket(message, sequence, EmptyArray.BYTE); } - public static byte[] generatePacket(int command, String data) { - return generatePacket(command, data.getBytes(StandardCharsets.UTF_8)); + public static byte[] generatePacket(int message, int sequence, String data) { + return generatePacket(message, sequence, data.getBytes(StandardCharsets.UTF_8)); } - public static byte[] generatePacket(int command, byte[] data) { - return ByteBuffer.allocate(data.length + 8) - .putInt(command).putInt(data.length).put(data).array(); + public static byte[] generatePacket(int message, int sequence, byte[] data) { + return ByteBuffer.allocate(data.length + 12) + .putInt(message) + .putInt(sequence) + .putInt(data.length) + .put(data) + .array(); + } + + private int createAssociation() { + getInstrumentation().getUiAutomation().executeShellCommand("cmd companiondevice associate " + + mContext.getUserId() + " " + mContext.getPackageName() + " AA:BB:CC:DD:EE:FF"); + List<AssociationInfo> infos; + for (int i = 0; i < 100; i++) { + infos = mCdm.getMyAssociations(); + if (!infos.isEmpty()) { + return infos.get(0).getId(); + } else { + SystemClock.sleep(100); + } + } + throw new AssertionError(); + } + + private void assertTransportBehavior(byte[] input, byte[] expected) { + final ByteArrayInputStream in = new ByteArrayInputStream(input); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + mCdm.attachSystemDataTransport(mAssociationId, in, out); + + final byte[] actual = waitForByteArray(out, expected.length); + assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual)); } private static byte[] waitForByteArray(ByteArrayOutputStream out, int size) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 41791afa45a3..2f79caeec7ba 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -43,6 +43,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.SystemProperties; import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -70,6 +71,8 @@ import java.util.function.Consumer; public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback, ActivityEmbeddingComponent { static final String TAG = "SplitController"; + static final boolean ENABLE_SHELL_TRANSITIONS = + SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); @VisibleForTesting @GuardedBy("mLock") @@ -332,6 +335,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * bounds is large enough for at least one split rule. */ private void updateAnimationOverride(@NonNull TaskContainer taskContainer) { + if (ENABLE_SHELL_TRANSITIONS) { + // TODO(b/207070762): cleanup with legacy app transition + // Animation will be handled by WM Shell with Shell transition enabled. + return; + } if (!taskContainer.isTaskBoundsInitialized() || !taskContainer.isWindowingModeInitialized()) { // We don't know about the Task bounds/windowingMode yet. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt index d4298b8f29a0..49eca63a23ec 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt @@ -25,11 +25,13 @@ import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until +import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.traces.common.FlickerComponentName import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME import com.android.wm.shell.flicker.testapp.Components +import org.junit.Assert class SplitScreenHelper( instrumentation: Instrumentation, @@ -187,5 +189,23 @@ class SplitScreenHelper( SystemClock.sleep(GESTURE_STEP_MS) } } + + fun createShortcutOnHotseatIfNotExist( + taplInstrumentation: LauncherInstrumentation, + appName: String + ) { + taplInstrumentation.workspace + .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0)) + val allApps = taplInstrumentation.workspace.switchToAllApps() + allApps.freeze() + try { + val appIconSrc = allApps.getAppIcon(appName) + Assert.assertNotNull("Unable to find app icon", appIconSrc) + val appIconDest = appIconSrc.dragToHotseat(0) + Assert.assertNotNull("Unable to drag app icon on hotseat", appIconDest) + } finally { + allApps.unfreeze() + } + } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt new file mode 100644 index 000000000000..60b0f8ed1135 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt @@ -0,0 +1,129 @@ +/* + * 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.flicker.splitscreen + +import android.platform.test.annotations.Presubmit +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.appWindowBecomesVisible +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test enter split screen by dragging app icon from taskbar. + * This test is only for large screen devices. + * + * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +class EnterSplitScreenByDragFromTaskbar( + testSpec: FlickerTestParameter +) : SplitScreenBase(testSpec) { + + @Before + fun before() { + Assume.assumeTrue(taplInstrumentation.isTablet) + } + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + eachRun { + taplInstrumentation.goHome() + SplitScreenHelper.createShortcutOnHotseatIfNotExist( + taplInstrumentation, secondaryApp.appName + ) + primaryApp.launchViaIntent(wmHelper) + } + } + transitions { + taplInstrumentation.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen( + secondaryApp.`package`, + primaryApp.`package` + ) + } + } + + @Presubmit + @Test + fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible() + + @Presubmit + @Test + fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component) + + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( + testSpec.endRotation, primaryApp.component, false /* splitLeftTop */ + ) + + @Presubmit + @Test + fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( + testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */ + ) + + @Presubmit + @Test + fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = + testSpec.appWindowBecomesVisible(secondaryApp.component) + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = SplitScreenHelper.TEST_REPETITIONS, + supportedNavigationModes = + listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt index d2c229b8ead1..a3351e1a6440 100644 --- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -47,7 +47,8 @@ import com.android.systemui.animation.Interpolators open class DisplayCutoutBaseView : View, RegionInterceptableView { private var shouldDrawCutout: Boolean = DisplayCutout.getFillBuiltInDisplayCutout( - context.resources, context.display?.uniqueId) + context.resources, context.display?.uniqueId + ) private var displayUniqueId: String? = null private var displayMode: Display.Mode? = null protected val location = IntArray(2) @@ -74,8 +75,8 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) - : super(context, attrs, defStyleAttr) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : + super(context, attrs, defStyleAttr) override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -85,7 +86,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { onUpdate() } - fun onDisplayChanged(displayId: Int) { + fun onDisplayChanged(newDisplayUniqueId: String?) { val oldMode: Display.Mode? = displayMode val display: Display? = context.display displayMode = display?.mode @@ -93,7 +94,8 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { if (displayUniqueId != display?.uniqueId) { displayUniqueId = display?.uniqueId shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout( - context.resources, displayUniqueId) + context.resources, displayUniqueId + ) } // Skip if display mode or cutout hasn't changed. @@ -101,7 +103,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { display?.cutout == displayInfo.displayCutout) { return } - if (displayId == display?.displayId) { + if (newDisplayUniqueId == display?.uniqueId) { updateCutout() updateProtectionBoundingPath() onUpdate() @@ -147,8 +149,9 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { cutoutBounds.translate(-location[0], -location[1]) // Intersect with window's frame - cutoutBounds.op(rootView.left, rootView.top, rootView.right, rootView.bottom, - Region.Op.INTERSECT) + cutoutBounds.op( + rootView.left, rootView.top, rootView.right, rootView.bottom, Region.Op.INTERSECT + ) return cutoutBounds } @@ -171,9 +174,12 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { protected open fun drawCutoutProtection(canvas: Canvas) { if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE && - !protectionRect.isEmpty) { - canvas.scale(cameraProtectionProgress, cameraProtectionProgress, - protectionRect.centerX(), protectionRect.centerY()) + !protectionRect.isEmpty + ) { + canvas.scale( + cameraProtectionProgress, cameraProtectionProgress, protectionRect.centerX(), + protectionRect.centerY() + ) canvas.drawPath(protectionPath, paint) } } @@ -205,14 +211,17 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { requestLayout() } cameraProtectionAnimator?.cancel() - cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress, - if (showProtection) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).setDuration(750) + cameraProtectionAnimator = ValueAnimator.ofFloat( + cameraProtectionProgress, + if (showProtection) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE + ).setDuration(750) cameraProtectionAnimator?.interpolator = Interpolators.DECELERATE_QUINT - cameraProtectionAnimator?.addUpdateListener(ValueAnimator.AnimatorUpdateListener { - animation: ValueAnimator -> - cameraProtectionProgress = animation.animatedValue as Float - invalidate() - }) + cameraProtectionAnimator?.addUpdateListener( + ValueAnimator.AnimatorUpdateListener { animation: ValueAnimator -> + cameraProtectionProgress = animation.animatedValue as Float + invalidate() + } + ) cameraProtectionAnimator?.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { cameraProtectionAnimator = null @@ -245,8 +254,10 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { // Apply rotation. val lw: Int = displayInfo.logicalWidth val lh: Int = displayInfo.logicalHeight - val flipped = (displayInfo.rotation == Surface.ROTATION_90 || - displayInfo.rotation == Surface.ROTATION_270) + val flipped = ( + displayInfo.rotation == Surface.ROTATION_90 || + displayInfo.rotation == Surface.ROTATION_270 + ) val dw = if (flipped) lh else lw val dh = if (flipped) lw else lh transformPhysicalToLogicalCoordinates(displayInfo.rotation, dw, dh, m) @@ -275,7 +286,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { // We purposely ignore refresh rate and id changes here, because we don't need to // invalidate for those, and they can trigger the refresh rate to increase return oldMode?.physicalHeight != newMode?.physicalHeight || - oldMode?.physicalWidth != newMode?.physicalWidth + oldMode?.physicalWidth != newMode?.physicalWidth } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index f99293a9d148..aaaa3f77924a 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -32,7 +32,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; -import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; @@ -92,10 +91,8 @@ import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -448,6 +445,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } + boolean needToUpdateProviderViews = false; final String newUniqueId = mDisplayInfo.uniqueId; if (!Objects.equals(newUniqueId, mDisplayUniqueId)) { mDisplayUniqueId = newUniqueId; @@ -470,8 +468,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateHwLayerRoundedCornerDrawable(); updateHwLayerRoundedCornerExistAndSize(); } - - updateOverlayProviderViews(); + needToUpdateProviderViews = true; } final float newRatio = getPhysicalPixelDisplaySizeRatio(); @@ -480,7 +477,13 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mScreenDecorHwcLayer != null) { updateHwLayerRoundedCornerExistAndSize(); } - updateOverlayProviderViews(); + needToUpdateProviderViews = true; + } + + if (needToUpdateProviderViews) { + updateOverlayProviderViews(null); + } else { + updateOverlayProviderViews(new Integer[] { mFaceScanningViewId }); } if (mCutoutViews != null) { @@ -490,18 +493,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (cutoutView == null) { continue; } - cutoutView.onDisplayChanged(displayId); + cutoutView.onDisplayChanged(newUniqueId); } } - DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(mFaceScanningViewId); - if (overlay != null) { - // handle display resolution changes - overlay.onDisplayChanged(displayId); - } - if (mScreenDecorHwcLayer != null) { - mScreenDecorHwcLayer.onDisplayChanged(displayId); + mScreenDecorHwcLayer.onDisplayChanged(newUniqueId); } } }; @@ -804,7 +801,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } removeOverlayView(provider.getViewId()); - overlay.addDecorProvider(provider, mRotation); + overlay.addDecorProvider(provider, mRotation, mTintColor); }); } // Use visibility of privacy dot views & face scanning view to determine the overlay's @@ -954,24 +951,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } - // When the hwc supports screen decorations, the layer will use the A8 color mode which - // won't be affected by the color inversion. If the composition goes the client composition - // route, the color inversion will be handled by the RenderEngine. - final Set<Integer> viewsMayNeedColorUpdate = new HashSet<>(); - if (mHwcScreenDecorationSupport == null) { - ColorStateList tintList = ColorStateList.valueOf(mTintColor); - mRoundedCornerResDelegate.setColorTintList(tintList); - viewsMayNeedColorUpdate.add(R.id.rounded_corner_top_left); - viewsMayNeedColorUpdate.add(R.id.rounded_corner_top_right); - viewsMayNeedColorUpdate.add(R.id.rounded_corner_bottom_left); - viewsMayNeedColorUpdate.add(R.id.rounded_corner_bottom_right); - viewsMayNeedColorUpdate.add(R.id.display_cutout); - } - if (getOverlayView(mFaceScanningViewId) != null) { - viewsMayNeedColorUpdate.add(mFaceScanningViewId); - } - final Integer[] views = new Integer[viewsMayNeedColorUpdate.size()]; - viewsMayNeedColorUpdate.toArray(views); for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (mOverlays[i] == null) { continue; @@ -981,14 +960,19 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab View child; for (int j = 0; j < size; j++) { child = overlayView.getChildAt(j); - if (viewsMayNeedColorUpdate.contains(child.getId()) - && child instanceof DisplayCutoutView) { + if (child instanceof DisplayCutoutView && child.getId() == R.id.display_cutout) { ((DisplayCutoutView) child).setColor(mTintColor); } } - mOverlays[i].onReloadResAndMeasure(views, mProviderRefreshToken, - mRotation, mDisplayUniqueId); } + + updateOverlayProviderViews(new Integer[] { + mFaceScanningViewId, + R.id.rounded_corner_top_left, + R.id.rounded_corner_top_right, + R.id.rounded_corner_bottom_left, + R.id.rounded_corner_bottom_right + }); } @VisibleForTesting @@ -1119,7 +1103,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } // update all provider views inside overlay - updateOverlayProviderViews(); + updateOverlayProviderViews(null); } FaceScanningOverlay faceScanningOverlay = @@ -1191,7 +1175,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab context.getResources(), context.getDisplay().getUniqueId()); } - private void updateOverlayProviderViews() { + private void updateOverlayProviderViews(@Nullable Integer[] filterIds) { if (mOverlays == null) { return; } @@ -1200,7 +1184,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (overlay == null) { continue; } - overlay.onReloadResAndMeasure(null, mProviderRefreshToken, mRotation, mDisplayUniqueId); + overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation, mTintColor, + mDisplayUniqueId); } } @@ -1239,19 +1224,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } catch (NumberFormatException e) { mRoundedCornerResDelegate.setTuningSizeFactor(null); } - Integer[] filterIds = { + updateOverlayProviderViews(new Integer[] { R.id.rounded_corner_top_left, R.id.rounded_corner_top_right, R.id.rounded_corner_bottom_left, R.id.rounded_corner_bottom_right - }; - for (final OverlayWindow overlay: mOverlays) { - if (overlay == null) { - continue; - } - overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation, - mDisplayUniqueId); - } + }); updateHwLayerRoundedCornerExistAndSize(); }); } diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt index 169b50edccfc..de6d7278971c 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt @@ -33,7 +33,7 @@ abstract class DecorProvider { /** The number of total aligned bounds */ val numOfAlignedEdge: Int - get() = alignedBounds.size + get() = alignedBounds.size /** The aligned bounds for the view which is created through inflateView() */ abstract val alignedBounds: List<Int> @@ -46,14 +46,16 @@ abstract class DecorProvider { view: View, reloadToken: Int, @Surface.Rotation rotation: Int, - displayUniqueId: String? = null + tintColor: Int, + displayUniqueId: String? ) /** Inflate view into parent as current rotation */ abstract fun inflateView( context: Context, parent: ViewGroup, - @Surface.Rotation rotation: Int + @Surface.Rotation rotation: Int, + tintColor: Int ): View } diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index 81d3d6caebd7..5925c57b4518 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -115,19 +115,25 @@ class FaceScanningOverlayProviderImpl( override fun onReloadResAndMeasure( view: View, reloadToken: Int, - rotation: Int, + @Surface.Rotation rotation: Int, + tintColor: Int, displayUniqueId: String? ) { (view.layoutParams as FrameLayout.LayoutParams).let { updateLayoutParams(it, rotation) view.layoutParams = it + (view as? FaceScanningOverlay)?.let { overlay -> + overlay.setColor(tintColor) + overlay.onDisplayChanged(displayUniqueId) + } } } override fun inflateView( context: Context, parent: ViewGroup, - @Surface.Rotation rotation: Int + @Surface.Rotation rotation: Int, + tintColor: Int ): View { val view = FaceScanningOverlay( context, @@ -137,6 +143,7 @@ class FaceScanningOverlayProviderImpl( mainExecutor ) view.id = viewId + view.setColor(tintColor) FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT).let { updateLayoutParams(it, rotation) diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt index 3c0748e02552..dfb0b5aad912 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt @@ -34,9 +34,10 @@ class OverlayWindow(private val context: Context) { fun addDecorProvider( decorProvider: DecorProvider, - @Surface.Rotation rotation: Int + @Surface.Rotation rotation: Int, + tintColor: Int ) { - val view = decorProvider.inflateView(context, rootView, rotation) + val view = decorProvider.inflateView(context, rootView, rotation, tintColor) viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider) } @@ -69,7 +70,7 @@ class OverlayWindow(private val context: Context) { */ fun hasSameProviders(newProviders: List<DecorProvider>): Boolean { return (newProviders.size == viewProviderMap.size) && - newProviders.all { getView(it.viewId) != null } + newProviders.all { getView(it.viewId) != null } } /** @@ -82,23 +83,28 @@ class OverlayWindow(private val context: Context) { filterIds: Array<Int>? = null, reloadToken: Int, @Surface.Rotation rotation: Int, + tintColor: Int, displayUniqueId: String? = null ) { filterIds?.forEach { id -> viewProviderMap[id]?.let { it.second.onReloadResAndMeasure( - view = it.first, - reloadToken = reloadToken, - displayUniqueId = displayUniqueId, - rotation = rotation) + view = it.first, + reloadToken = reloadToken, + rotation = rotation, + tintColor = tintColor, + displayUniqueId = displayUniqueId + ) } } ?: run { viewProviderMap.values.forEach { it.second.onReloadResAndMeasure( - view = it.first, - reloadToken = reloadToken, - displayUniqueId = displayUniqueId, - rotation = rotation) + view = it.first, + reloadToken = reloadToken, + rotation = rotation, + tintColor = tintColor, + displayUniqueId = displayUniqueId + ) } } } @@ -111,4 +117,4 @@ class OverlayWindow(private val context: Context) { pw.println(" child[$i]=$child") } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt index 9f624b3dcb09..e18c0e15b57a 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt @@ -20,9 +20,9 @@ import android.content.Context import android.content.res.Resources import android.view.DisplayCutout import android.view.LayoutInflater +import android.view.Surface import android.view.View import android.view.ViewGroup -import android.view.Surface import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main @@ -85,6 +85,7 @@ class PrivacyDotCornerDecorProviderImpl( view: View, reloadToken: Int, rotation: Int, + tintColor: Int, displayUniqueId: String? ) { // Do nothing here because it is handled inside PrivacyDotViewController @@ -93,7 +94,8 @@ class PrivacyDotCornerDecorProviderImpl( override fun inflateView( context: Context, parent: ViewGroup, - @Surface.Rotation rotation: Int + @Surface.Rotation rotation: Int, + tintColor: Int ): View { LayoutInflater.from(context).inflate(layoutId, parent, true) return parent.getChildAt(parent.childCount - 1 /* latest new added child */) diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt index e316722b64ea..8156797c5a67 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt @@ -17,6 +17,7 @@ package com.android.systemui.decor import android.content.Context +import android.content.res.ColorStateList import android.view.DisplayCutout import android.view.Gravity import android.view.Surface @@ -38,12 +39,13 @@ class RoundedCornerDecorProviderImpl( override fun inflateView( context: Context, parent: ViewGroup, - @Surface.Rotation rotation: Int + @Surface.Rotation rotation: Int, + tintColor: Int ): View { return ImageView(context).also { view -> // View view.id = viewId - initView(view, rotation) + initView(view, rotation, tintColor) // LayoutParams val layoutSize = if (isTop) { @@ -52,31 +54,36 @@ class RoundedCornerDecorProviderImpl( roundedCornerResDelegate.bottomRoundedSize } val params = FrameLayout.LayoutParams( - layoutSize.width, - layoutSize.height, - alignedBound1.toLayoutGravity(rotation) or - alignedBound2.toLayoutGravity(rotation)) + layoutSize.width, + layoutSize.height, + alignedBound1.toLayoutGravity(rotation) or alignedBound2.toLayoutGravity(rotation) + ) // AddView parent.addView(view, params) } } - private fun initView(view: ImageView, @Surface.Rotation rotation: Int) { + private fun initView( + view: ImageView, + @Surface.Rotation rotation: Int, + tintColor: Int + ) { view.setRoundedCornerImage(roundedCornerResDelegate, isTop) view.adjustRotation(alignedBounds, rotation) - view.imageTintList = roundedCornerResDelegate.colorTintList + view.imageTintList = ColorStateList.valueOf(tintColor) } override fun onReloadResAndMeasure( view: View, reloadToken: Int, @Surface.Rotation rotation: Int, + tintColor: Int, displayUniqueId: String? ) { roundedCornerResDelegate.updateDisplayUniqueId(displayUniqueId, reloadToken) - initView((view as ImageView), rotation) + initView((view as ImageView), rotation, tintColor) val layoutSize = if (isTop) { roundedCornerResDelegate.topRoundedSize @@ -87,7 +94,7 @@ class RoundedCornerDecorProviderImpl( it.width = layoutSize.width it.height = layoutSize.height it.gravity = alignedBound1.toLayoutGravity(rotation) or - alignedBound2.toLayoutGravity(rotation) + alignedBound2.toLayoutGravity(rotation) view.setLayoutParams(it) } } @@ -134,10 +141,10 @@ private fun ImageView.setRoundedCornerImage( setImageDrawable(drawable) } else { setImageResource( - if (isTop) - R.drawable.rounded_corner_top - else - R.drawable.rounded_corner_bottom + if (isTop) + R.drawable.rounded_corner_top + else + R.drawable.rounded_corner_bottom ) } } @@ -187,4 +194,4 @@ private fun ImageView.adjustRotation(alignedBounds: List<Int>, @Surface.Rotation this.rotation = newRotation this.scaleX = newScaleX this.scaleY = newScaleY -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt index b5a0cfcb1c18..a25286438387 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt @@ -18,9 +18,7 @@ package com.android.systemui.decor import android.annotation.ArrayRes import android.annotation.DrawableRes -import android.content.res.ColorStateList import android.content.res.Resources -import android.graphics.Color import android.graphics.drawable.Drawable import android.util.DisplayUtils import android.util.Size @@ -57,8 +55,6 @@ class RoundedCornerResDelegate( var bottomRoundedSize = Size(0, 0) private set - var colorTintList = ColorStateList.valueOf(Color.BLACK) - var tuningSizeFactor: Int? = null set(value) { if (field == value) { @@ -107,19 +103,19 @@ class RoundedCornerResDelegate( val hasDefaultRadius = RoundedCorners.getRoundedCornerRadius(res, displayUniqueId) > 0 hasTop = hasDefaultRadius || - (RoundedCorners.getRoundedCornerTopRadius(res, displayUniqueId) > 0) + (RoundedCorners.getRoundedCornerTopRadius(res, displayUniqueId) > 0) hasBottom = hasDefaultRadius || - (RoundedCorners.getRoundedCornerBottomRadius(res, displayUniqueId) > 0) + (RoundedCorners.getRoundedCornerBottomRadius(res, displayUniqueId) > 0) topRoundedDrawable = getDrawable( - displayConfigIndex = configIdx, - arrayResId = R.array.config_roundedCornerTopDrawableArray, - backupDrawableId = R.drawable.rounded_corner_top + displayConfigIndex = configIdx, + arrayResId = R.array.config_roundedCornerTopDrawableArray, + backupDrawableId = R.drawable.rounded_corner_top ) bottomRoundedDrawable = getDrawable( - displayConfigIndex = configIdx, - arrayResId = R.array.config_roundedCornerBottomDrawableArray, - backupDrawableId = R.drawable.rounded_corner_bottom + displayConfigIndex = configIdx, + arrayResId = R.array.config_roundedCornerBottomDrawableArray, + backupDrawableId = R.drawable.rounded_corner_bottom ) } @@ -147,13 +143,15 @@ class RoundedCornerResDelegate( if (physicalPixelDisplaySizeRatio != 1f) { if (topRoundedSize.width != 0) { topRoundedSize = Size( - (physicalPixelDisplaySizeRatio * topRoundedSize.width + 0.5f).toInt(), - (physicalPixelDisplaySizeRatio * topRoundedSize.height + 0.5f).toInt()) + (physicalPixelDisplaySizeRatio * topRoundedSize.width + 0.5f).toInt(), + (physicalPixelDisplaySizeRatio * topRoundedSize.height + 0.5f).toInt() + ) } if (bottomRoundedSize.width != 0) { bottomRoundedSize = Size( - (physicalPixelDisplaySizeRatio * bottomRoundedSize.width + 0.5f).toInt(), - (physicalPixelDisplaySizeRatio * bottomRoundedSize.height + 0.5f).toInt()) + (physicalPixelDisplaySizeRatio * bottomRoundedSize.width + 0.5f).toInt(), + (physicalPixelDisplaySizeRatio * bottomRoundedSize.height + 0.5f).toInt() + ) } } } @@ -180,8 +178,9 @@ class RoundedCornerResDelegate( pw.println(" hasTop=$hasTop") pw.println(" hasBottom=$hasBottom") pw.println(" topRoundedSize(w,h)=(${topRoundedSize.width},${topRoundedSize.height})") - pw.println(" bottomRoundedSize(w,h)=(${bottomRoundedSize.width}," + - "${bottomRoundedSize.height})") + pw.println( + " bottomRoundedSize(w,h)=(${bottomRoundedSize.width},${bottomRoundedSize.height})" + ) pw.println(" physicalPixelDisplaySizeRatio=$physicalPixelDisplaySizeRatio") } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 90cca15ddf21..d0da18aaba05 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -27,6 +27,8 @@ import com.android.systemui.log.LogBufferFactory; import com.android.systemui.log.LogcatEchoTracker; import com.android.systemui.log.LogcatEchoTrackerDebug; import com.android.systemui.log.LogcatEchoTrackerProd; +import com.android.systemui.statusbar.notification.NotifPipelineFlags; +import com.android.systemui.util.Compile; import dagger.Module; import dagger.Provides; @@ -48,8 +50,14 @@ public class LogModule { @Provides @SysUISingleton @NotificationLog - public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) { - return factory.create("NotifLog", 1000 /* maxSize */, false /* systrace */); + public static LogBuffer provideNotificationsLogBuffer( + LogBufferFactory factory, + NotifPipelineFlags notifPipelineFlags) { + int maxSize = 1000; + if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) { + maxSize *= 10; + } + return factory.create("NotifLog", maxSize, false /* systrace */); } /** Provides a logging buffer for logs related to heads up presentation of notifications. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 6a01df61705d..3b5b333a8aab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -310,7 +310,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { } locallyDismissNotifications(entriesToLocallyDismiss); - dispatchEventsAndRebuildList(); + dispatchEventsAndRebuildList("dismissNotifications"); } /** @@ -354,7 +354,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { } locallyDismissNotifications(entries); - dispatchEventsAndRebuildList(); + dispatchEventsAndRebuildList("dismissAllNotifications"); } /** @@ -401,7 +401,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { postNotification(sbn, requireRanking(rankingMap, sbn.getKey())); applyRanking(rankingMap); - dispatchEventsAndRebuildList(); + dispatchEventsAndRebuildList("onNotificationPosted"); } private void onNotificationGroupPosted(List<CoalescedEvent> batch) { @@ -412,7 +412,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { for (CoalescedEvent event : batch) { postNotification(event.getSbn(), event.getRanking()); } - dispatchEventsAndRebuildList(); + dispatchEventsAndRebuildList("onNotificationGroupPosted"); } private void onNotificationRemoved( @@ -433,14 +433,14 @@ public class NotifCollection implements Dumpable, PipelineDumpable { entry.mCancellationReason = reason; tryRemoveNotification(entry); applyRanking(rankingMap); - dispatchEventsAndRebuildList(); + dispatchEventsAndRebuildList("onNotificationRemoved"); } private void onNotificationRankingUpdate(RankingMap rankingMap) { Assert.isMainThread(); mEventQueue.add(new RankingUpdatedEvent(rankingMap)); applyRanking(rankingMap); - dispatchEventsAndRebuildList(); + dispatchEventsAndRebuildList("onNotificationRankingUpdate"); } private void onNotificationChannelModified( @@ -450,7 +450,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { int modificationType) { Assert.isMainThread(); mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType)); - dispatchEventsAndRebuildList(); + dispatchEventsAndRebuildList("onNotificationChannelModified"); } private void onNotificationsInitialized() { @@ -610,7 +610,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { mEventQueue.add(new RankingAppliedEvent()); } - private void dispatchEventsAndRebuildList() { + private void dispatchEventsAndRebuildList(String reason) { Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList"); mAmDispatchingToOtherCode = true; while (!mEventQueue.isEmpty()) { @@ -619,7 +619,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { mAmDispatchingToOtherCode = false; if (mBuildListener != null) { - mBuildListener.onBuildList(mReadOnlyNotificationSet); + mBuildListener.onBuildList(mReadOnlyNotificationSet, reason); } Trace.endSection(); } @@ -654,7 +654,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { if (!isLifetimeExtended(entry)) { if (tryRemoveNotification(entry)) { - dispatchEventsAndRebuildList(); + dispatchEventsAndRebuildList("onEndLifetimeExtension"); } } } @@ -963,7 +963,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { mEventQueue.add(new EntryUpdatedEvent(entry, false /* fromSystem */)); // Skip the applyRanking step and go straight to dispatching the events - dispatchEventsAndRebuildList(); + dispatchEventsAndRebuildList("updateNotificationInternally"); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 26d2ee349f50..8a18d3183136 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -304,11 +304,11 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { private final CollectionReadyForBuildListener mReadyForBuildListener = new CollectionReadyForBuildListener() { @Override - public void onBuildList(Collection<NotificationEntry> entries) { + public void onBuildList(Collection<NotificationEntry> entries, String reason) { Assert.isMainThread(); mPipelineState.requireIsBefore(STATE_BUILD_STARTED); - mLogger.logOnBuildList(); + mLogger.logOnBuildList(reason); mAllEntries = entries; mChoreographer.schedule(); } @@ -456,7 +456,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { mLogger.logEndBuildList( mIterationCount, mReadOnlyNotifList.size(), - countChildren(mReadOnlyNotifList)); + countChildren(mReadOnlyNotifList), + /* enforcedVisualStability */ !mNotifStabilityManager.isEveryChangeAllowed()); if (mAlwaysLogList || mIterationCount % 10 == 0) { Trace.beginSection("ShadeListBuilder.logFinalList"); mLogger.logFinalList(mNotifList); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index 8d1759b8f475..10a627d65b83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -21,31 +21,42 @@ import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.logKey +import com.android.systemui.util.Compile import javax.inject.Inject class ShadeListBuilderLogger @Inject constructor( + notifPipelineFlags: NotifPipelineFlags, @NotificationLog private val buffer: LogBuffer ) { - fun logOnBuildList() { + fun logOnBuildList(reason: String?) { buffer.log(TAG, INFO, { + str1 = reason }, { - "Request received from NotifCollection" + "Request received from NotifCollection for $str1" }) } - fun logEndBuildList(buildId: Int, topLevelEntries: Int, numChildren: Int) { + fun logEndBuildList( + buildId: Int, + topLevelEntries: Int, + numChildren: Int, + enforcedVisualStability: Boolean + ) { buffer.log(TAG, INFO, { long1 = buildId.toLong() int1 = topLevelEntries int2 = numChildren + bool1 = enforcedVisualStability }, { - "(Build $long1) Build complete ($int1 top-level entries, $int2 children)" + "(Build $long1) Build complete ($int1 top-level entries, $int2 children)" + + " enforcedVisualStability=$bool1" }) } @@ -280,6 +291,8 @@ class ShadeListBuilderLogger @Inject constructor( }) } + val logRankInFinalList = Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled() + fun logFinalList(entries: List<ListEntry>) { if (entries.isEmpty()) { buffer.log(TAG, DEBUG, {}, { "(empty list)" }) @@ -289,16 +302,20 @@ class ShadeListBuilderLogger @Inject constructor( buffer.log(TAG, DEBUG, { int1 = i str1 = entry.logKey + bool1 = logRankInFinalList + int2 = entry.representativeEntry!!.ranking.rank }, { - "[$int1] $str1" + "[$int1] $str1".let { if (bool1) "$it rank=$int2" else it } }) if (entry is GroupEntry) { entry.summary?.let { buffer.log(TAG, DEBUG, { str1 = it.logKey + bool1 = logRankInFinalList + int2 = it.ranking.rank }, { - " [*] $str1 (summary)" + " [*] $str1 (summary)".let { if (bool1) "$it rank=$int2" else it } }) } for (j in entry.children.indices) { @@ -306,8 +323,10 @@ class ShadeListBuilderLogger @Inject constructor( buffer.log(TAG, DEBUG, { int1 = j str1 = child.logKey + bool1 = logRankInFinalList + int2 = child.ranking.rank }, { - " [$int1] $str1" + " [$int1] $str1".let { if (bool1) "$it rank=$int2" else it } }) } } @@ -318,4 +337,4 @@ class ShadeListBuilderLogger @Inject constructor( buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." } } -private const val TAG = "ShadeListBuilder"
\ No newline at end of file +private const val TAG = "ShadeListBuilder" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java index 4023474bf6a7..941b2ae4f771 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java @@ -29,5 +29,5 @@ public interface CollectionReadyForBuildListener { * Called by the NotifCollection to indicate that something in the collection has changed and * that the list builder should regenerate the list. */ - void onBuildList(Collection<NotificationEntry> entries); + void onBuildList(Collection<NotificationEntry> entries, String reason); } diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java index 4e9030f8045c..dac8a0bff28a 100644 --- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java @@ -92,7 +92,15 @@ public class Monitor { } private void updateConditionMetState(Condition condition) { - mConditions.get(condition).stream().forEach(token -> mSubscriptions.get(token).update()); + final ArraySet<Subscription.Token> subscriptions = mConditions.get(condition); + + // It's possible the condition was removed between the time the callback occurred and + // update was executed on the main thread. + if (subscriptions == null) { + return; + } + + subscriptions.stream().forEach(token -> mSubscriptions.get(token).update()); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 90609fa2772f..64a7986d05b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -1383,7 +1383,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { mScreenDecorations.mDisplayListener.onDisplayChanged(1); - verify(hwcLayer, times(1)).onDisplayChanged(1); + verify(hwcLayer, times(1)).onDisplayChanged(any()); } @Test @@ -1407,7 +1407,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { mScreenDecorations.mDisplayListener.onDisplayChanged(1); - verify(cutoutView, times(1)).onDisplayChanged(1); + verify(cutoutView, times(1)).onDisplayChanged(any()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt index 69366fa0d4a9..8bf17d7c62f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.decor +import android.graphics.Color import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.DisplayCutout @@ -51,35 +52,45 @@ class OverlayWindowTest : SysuiTestCase() { @Before fun setUp() { - decorProvider1 = spy(PrivacyDotCornerDecorProviderImpl( + decorProvider1 = spy( + PrivacyDotCornerDecorProviderImpl( viewId = TEST_DECOR_VIEW_ID_1, alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP, alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT, - layoutId = R.layout.privacy_dot_top_left)) - decorProvider2 = spy(PrivacyDotCornerDecorProviderImpl( + layoutId = R.layout.privacy_dot_top_left + ) + ) + decorProvider2 = spy( + PrivacyDotCornerDecorProviderImpl( viewId = TEST_DECOR_VIEW_ID_2, alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM, alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT, - layoutId = R.layout.privacy_dot_bottom_left)) - decorProvider3 = spy(PrivacyDotCornerDecorProviderImpl( + layoutId = R.layout.privacy_dot_bottom_left + ) + ) + decorProvider3 = spy( + PrivacyDotCornerDecorProviderImpl( viewId = TEST_DECOR_VIEW_ID_3, alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM, alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT, - layoutId = R.layout.privacy_dot_bottom_right)) - + layoutId = R.layout.privacy_dot_bottom_right + ) + ) overlay = OverlayWindow(mContext) } @Test fun testAddProvider() { @Surface.Rotation val rotation = Surface.ROTATION_270 - overlay.addDecorProvider(decorProvider1, rotation) - overlay.addDecorProvider(decorProvider2, rotation) + overlay.addDecorProvider(decorProvider1, rotation, Color.BLACK) + overlay.addDecorProvider(decorProvider2, rotation, Color.YELLOW) verify(decorProvider1, times(1)).inflateView( - mContext, overlay.rootView, rotation) + mContext, overlay.rootView, rotation, Color.BLACK + ) verify(decorProvider2, times(1)).inflateView( - mContext, overlay.rootView, rotation) + mContext, overlay.rootView, rotation, Color.YELLOW + ) val view1FoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1) Assert.assertNotNull(view1FoundFromRootView) @@ -91,8 +102,8 @@ class OverlayWindowTest : SysuiTestCase() { @Test fun testRemoveView() { - overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270) - overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270) + overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270, Color.BLACK) + overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270, Color.BLACK) overlay.removeView(TEST_DECOR_VIEW_ID_1) val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1) @@ -102,39 +113,47 @@ class OverlayWindowTest : SysuiTestCase() { @Test fun testOnReloadResAndMeasureWithoutIds() { - overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0) - overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0) + overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0, Color.BLACK) + overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0, Color.BLACK) overlay.onReloadResAndMeasure( - reloadToken = 1, - rotation = Surface.ROTATION_90, - displayUniqueId = null) + reloadToken = 1, + rotation = Surface.ROTATION_90, + tintColor = Color.BLACK, + displayUniqueId = null + ) verify(decorProvider1, times(1)).onReloadResAndMeasure( - overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null) + overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, Color.BLACK, null + ) verify(decorProvider2, times(1)).onReloadResAndMeasure( - overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null) + overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, Color.BLACK, null + ) } @Test fun testOnReloadResAndMeasureWithIds() { - overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0) - overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0) + overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0, Color.BLACK) + overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0, Color.BLACK) overlay.onReloadResAndMeasure( - filterIds = arrayOf(TEST_DECOR_VIEW_ID_2), - reloadToken = 1, - rotation = Surface.ROTATION_90, - displayUniqueId = null) + filterIds = arrayOf(TEST_DECOR_VIEW_ID_2), + reloadToken = 1, + rotation = Surface.ROTATION_90, + tintColor = Color.BLACK, + displayUniqueId = null + ) verify(decorProvider1, never()).onReloadResAndMeasure( - overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null) + overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, Color.BLACK, null + ) verify(decorProvider2, times(1)).onReloadResAndMeasure( - overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null) + overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, Color.BLACK, null + ) } @Test fun testRemoveRedundantViewsWithNullParameter() { - overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270) - overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270) + overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270, Color.BLACK) + overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270, Color.BLACK) overlay.removeRedundantViews(null) @@ -146,13 +165,15 @@ class OverlayWindowTest : SysuiTestCase() { @Test fun testRemoveRedundantViewsWith2Providers() { - overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270) - overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270) + overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270, Color.BLACK) + overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270, Color.BLACK) - overlay.removeRedundantViews(IntArray(2).apply { - this[0] = TEST_DECOR_VIEW_ID_3 - this[1] = TEST_DECOR_VIEW_ID_1 - }) + overlay.removeRedundantViews( + IntArray(2).apply { + this[0] = TEST_DECOR_VIEW_ID_3 + this[1] = TEST_DECOR_VIEW_ID_1 + } + ) Assert.assertNotNull(overlay.getView(TEST_DECOR_VIEW_ID_1)) Assert.assertNotNull(overlay.rootView.findViewById(TEST_DECOR_VIEW_ID_1)) @@ -167,16 +188,16 @@ class OverlayWindowTest : SysuiTestCase() { Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2))) Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1))) - overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0) + overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0, Color.BLACK) Assert.assertFalse(overlay.hasSameProviders(emptyList())) Assert.assertTrue(overlay.hasSameProviders(listOf(decorProvider1))) Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2))) Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1))) - overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0) + overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0, Color.BLACK) Assert.assertFalse(overlay.hasSameProviders(emptyList())) Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider1))) Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2))) Assert.assertTrue(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1))) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index f286349971d2..af43826091a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -1684,9 +1684,9 @@ public class NotifCollectionTest extends SysuiTestCase { return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue())); } - private void verifyBuiltList(Collection<NotificationEntry> list) { - verify(mBuildListener).onBuildList(mBuildListCaptor.capture()); - assertEquals(new ArraySet<>(list), new ArraySet<>(mBuildListCaptor.getValue())); + private void verifyBuiltList(Collection<NotificationEntry> expectedList) { + verify(mBuildListener).onBuildList(mBuildListCaptor.capture(), any()); + assertThat(mBuildListCaptor.getValue()).containsExactly(expectedList.toArray()); } private static class RecordingCollectionListener implements NotifCollectionListener { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 95460583e857..555adfdfdc31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -570,7 +570,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { assertTrue(entry.hasFinishedInitialization()); // WHEN the pipeline is kicked off - mReadyForBuildListener.onBuildList(singletonList(entry)); + mReadyForBuildListener.onBuildList(singletonList(entry), "test"); mPipelineChoreographer.runIfScheduled(); // THEN the entry's initialization time is reset @@ -2092,7 +2092,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { mPendingSet.clear(); } - mReadyForBuildListener.onBuildList(mEntrySet); + mReadyForBuildListener.onBuildList(mEntrySet, "test"); mPipelineChoreographer.runIfScheduled(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java index 1e35b0f0e68a..125b3627b342 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java @@ -159,6 +159,24 @@ public class ConditionMonitorTest extends SysuiTestCase { Mockito.clearInvocations(callback); } + // Ensure that updating a callback that is removed doesn't result in an exception due to the + // absence of the condition. + @Test + public void testUpdateRemovedCallback() { + final Monitor.Callback callback1 = + mock(Monitor.Callback.class); + final Monitor.Subscription.Token subscription1 = + mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build()); + ArgumentCaptor<Condition.Callback> monitorCallback = + ArgumentCaptor.forClass(Condition.Callback.class); + mExecutor.runAllReady(); + verify(mCondition1).addCallback(monitorCallback.capture()); + // This will execute first before the handler for onConditionChanged. + mConditionMonitor.removeSubscription(subscription1); + monitorCallback.getValue().onConditionChanged(mCondition1); + mExecutor.runAllReady(); + } + @Test public void addCallback_addFirstCallback_addCallbackToAllConditions() { final Monitor.Callback callback1 = diff --git a/services/accessibility/OWNERS b/services/accessibility/OWNERS index a31cfae995b2..2b9f179b832d 100644 --- a/services/accessibility/OWNERS +++ b/services/accessibility/OWNERS @@ -1,4 +1,4 @@ -svetoslavganov@google.com pweaver@google.com -rhedjao@google.com +sallyyuen@google.com ryanlwlin@google.com +fuego@google.com diff --git a/services/companion/Android.bp b/services/companion/Android.bp index d3ef6dc47256..cdeb2dcf87e9 100644 --- a/services/companion/Android.bp +++ b/services/companion/Android.bp @@ -11,7 +11,6 @@ filegroup { name: "services.companion-sources", srcs: [ "java/**/*.java", - "java/**/*.proto", ], path: "java", visibility: ["//frameworks/base/services"], @@ -20,9 +19,6 @@ filegroup { java_library_static { name: "services.companion", defaults: ["platform_service_defaults"], - proto: { - type: "stream", - }, srcs: [":services.companion-sources"], libs: [ "app-compat-annotations", diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java index 2ab1aa80176a..2acb65eafac0 100644 --- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -244,29 +244,6 @@ public class CompanionApplicationController { primaryServiceConnector.postOnDeviceDisappeared(association); } - /** Pass an encrypted secure message to the companion application for transporting. */ - public void dispatchMessage(@UserIdInt int userId, @NonNull String packageName, - int associationId, int messageId, @NonNull byte[] message) { - if (DEBUG) { - Log.i(TAG, "dispatchMessage() u" + userId + "/" + packageName - + " associationId=" + associationId); - } - - final CompanionDeviceServiceConnector primaryServiceConnector = - getPrimaryServiceConnector(userId, packageName); - if (primaryServiceConnector == null) { - if (DEBUG) { - Log.e(TAG, "dispatchMessage(): " - + "u" + userId + "/" + packageName + " is NOT bound."); - Log.d(TAG, "Stacktrace", new Throwable()); - } - return; - } - - primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, messageId, - message); - } - void dump(@NonNull PrintWriter out) { out.append("Companion Device Application Controller: \n"); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index c3545619bcb9..fa043f8e02af 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -106,11 +106,10 @@ import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.companion.datatransfer.CompanionMessageProcessor; import com.android.server.companion.datatransfer.SystemDataTransferProcessor; import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; import com.android.server.companion.presence.CompanionDevicePresenceMonitor; -import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager; +import com.android.server.companion.transport.CompanionTransportManager; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; @@ -151,10 +150,9 @@ public class CompanionDeviceManagerService extends SystemService { private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; private AssociationRequestsProcessor mAssociationRequestsProcessor; private SystemDataTransferProcessor mSystemDataTransferProcessor; - private CompanionMessageProcessor mCompanionMessageProcessor; private CompanionDevicePresenceMonitor mDevicePresenceMonitor; private CompanionApplicationController mCompanionAppController; - private CompanionSecureCommunicationsManager mSecureCommsManager; + private CompanionTransportManager mTransportManager; private final ActivityTaskManagerInternal mAtmInternal; private final ActivityManagerInternal mAmInternal; @@ -238,11 +236,9 @@ public class CompanionDeviceManagerService extends SystemService { /* cdmService */this, mAssociationStore); mCompanionAppController = new CompanionApplicationController( context, mApplicationControllerCallback); - mSecureCommsManager = new CompanionSecureCommunicationsManager( - mAssociationStore, mCompanionAppController); - mCompanionMessageProcessor = new CompanionMessageProcessor(mSecureCommsManager); + mTransportManager = new CompanionTransportManager(context); mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore, - mSystemDataTransferRequestStore, mCompanionMessageProcessor); + mSystemDataTransferRequestStore, mTransportManager); // Publish "binder" service. final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl(); @@ -319,18 +315,32 @@ public class CompanionDeviceManagerService extends SystemService { MINUTES.toMillis(10)); } - @Nullable + @NonNull AssociationInfo getAssociationWithCallerChecks( @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { - final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( + AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( userId, packageName, macAddress); - return sanitizeWithCallerChecks(getContext(), association); + association = sanitizeWithCallerChecks(getContext(), association); + if (association != null) { + return association; + } else { + throw new IllegalArgumentException("Association does not exist " + + "or the caller does not have permissions to manage it " + + "(ie. it belongs to a different package or a different user)."); + } } - @Nullable + @NonNull AssociationInfo getAssociationWithCallerChecks(int associationId) { - final AssociationInfo association = mAssociationStore.getAssociationById(associationId); - return sanitizeWithCallerChecks(getContext(), association); + AssociationInfo association = mAssociationStore.getAssociationById(associationId); + association = sanitizeWithCallerChecks(getContext(), association); + if (association != null) { + return association; + } else { + throw new IllegalArgumentException("Association does not exist " + + "or the caller does not have permissions to manage it " + + "(ie. it belongs to a different package or a different user)."); + } } private void onDeviceAppearedInternal(int associationId) { @@ -609,12 +619,6 @@ public class CompanionDeviceManagerService extends SystemService { final AssociationInfo association = getAssociationWithCallerChecks(userId, packageName, deviceMacAddress); - if (association == null) { - throw new IllegalArgumentException("Association does not exist " - + "or the caller does not have permissions to manage it " - + "(ie. it belongs to a different package or a different user)."); - } - disassociateInternal(association.getId()); } @@ -622,15 +626,9 @@ public class CompanionDeviceManagerService extends SystemService { public void disassociate(int associationId) { if (DEBUG) Log.i(TAG, "disassociate() associationId=" + associationId); - final AssociationInfo association = getAssociationWithCallerChecks(associationId); - if (association == null) { - throw new IllegalArgumentException("Association with ID " + associationId + " " - + "does not exist " - + "or belongs to a different package " - + "or belongs to a different user"); - } - - disassociateInternal(associationId); + final AssociationInfo association = + getAssociationWithCallerChecks(associationId); + disassociateInternal(association.getId()); } @Override @@ -698,27 +696,6 @@ public class CompanionDeviceManagerService extends SystemService { } @Override - public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) { - if (DEBUG) { - Log.i(TAG, "dispatchMessage() associationId=" + associationId + "\n" - + " message(Base64)=" + Base64.encodeToString(message, 0)); - } - - getContext().enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, - "dispatchMessage"); - - AssociationInfo association = getAssociationWithCallerChecks(associationId); - if (association == null) { - throw new IllegalArgumentException("Association with ID " + associationId + " " - + "does not exist " - + "or belongs to a different package " - + "or belongs to a different user"); - } - - mSecureCommsManager.receiveSecureMessage(messageId, associationId, message); - } - - @Override public PendingIntent buildPermissionTransferUserConsentIntent(String packageName, int userId, int associationId) { return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent( @@ -735,14 +712,14 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void attachSystemDataTransport(String packageName, int userId, int associationId, ParcelFileDescriptor fd) { - mSystemDataTransferProcessor.attachSystemDataTransport(packageName, userId, - associationId, fd); + getAssociationWithCallerChecks(associationId); + mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd); } @Override public void detachSystemDataTransport(String packageName, int userId, int associationId) { - mSystemDataTransferProcessor.detachSystemDataTransport(packageName, userId, - associationId); + getAssociationWithCallerChecks(associationId); + mTransportManager.detachSystemDataTransport(packageName, userId, associationId); } @Override @@ -750,13 +727,6 @@ public class CompanionDeviceManagerService extends SystemService { if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId); AssociationInfo association = getAssociationWithCallerChecks(associationId); - if (association == null) { - throw new IllegalArgumentException("Association with ID " + associationId + " " - + "does not exist " - + "or belongs to a different package " - + "or belongs to a different user"); - } - if (!association.isSelfManaged()) { throw new IllegalArgumentException("Association with ID " + associationId + " is not self-managed. notifyDeviceAppeared(int) can only be called for" @@ -777,13 +747,6 @@ public class CompanionDeviceManagerService extends SystemService { if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId); final AssociationInfo association = getAssociationWithCallerChecks(associationId); - if (association == null) { - throw new IllegalArgumentException("Association with ID " + associationId + " " - + "does not exist " - + "or belongs to a different package " - + "or belongs to a different user"); - } - if (!association.isSelfManaged()) { throw new IllegalArgumentException("Association with ID " + associationId + " is not self-managed. notifyDeviceAppeared(int) can only be called for" @@ -892,7 +855,6 @@ public class CompanionDeviceManagerService extends SystemService { final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand( CompanionDeviceManagerService.this, mAssociationStore, - mSecureCommsManager, mDevicePresenceMonitor); cmd.exec(this, in, out, err, args, callback, resultReceiver); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java index 7360f0846ed3..a288f7b2993c 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java @@ -99,12 +99,6 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe post(companionService -> companionService.onDeviceDisappeared(associationInfo)); } - void postOnMessageDispatchedFromSystem(int associationId, int messageId, - @NonNull byte[] message) { - post(companionService -> - companionService.onMessageDispatchedFromSystem(messageId, associationId, message)); - } - /** * Post "unbind" job, which will run *after* all previously posted jobs complete. * diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 0b7bc03eebba..322ad46e697a 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -16,14 +16,9 @@ package com.android.server.companion; -import static java.nio.charset.StandardCharsets.UTF_8; - import android.companion.AssociationInfo; import android.os.Binder; import android.os.ShellCommand; -import android.util.Base64; - -import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager; import com.android.server.companion.presence.CompanionDevicePresenceMonitor; @@ -35,16 +30,13 @@ class CompanionDeviceShellCommand extends ShellCommand { private final CompanionDeviceManagerService mService; private final AssociationStore mAssociationStore; - private final CompanionSecureCommunicationsManager mSecureCommsManager; private final CompanionDevicePresenceMonitor mDevicePresenceMonitor; CompanionDeviceShellCommand(CompanionDeviceManagerService service, AssociationStore associationStore, - CompanionSecureCommunicationsManager secureCommsManager, CompanionDevicePresenceMonitor devicePresenceMonitor) { mService = service; mAssociationStore = associationStore; - mSecureCommsManager = secureCommsManager; mDevicePresenceMonitor = devicePresenceMonitor; } @@ -92,32 +84,6 @@ class CompanionDeviceShellCommand extends ShellCommand { mService.loadAssociationsFromDisk(); break; - case "send-secure-message": - associationId = getNextIntArgRequired(); - final byte[] message; - - // The message should be either a UTF-8 String or Base64-encoded data. - final boolean isBase64 = "--base64".equals(getNextOption()); - if (isBase64) { - final String base64encodedMessage = getNextArgRequired(); - message = Base64.decode(base64encodedMessage, 0); - } else { - // We treat the rest of the command as the message, which should contain at - // least one word (hence getNextArg_Required() below), but there may be - // more. - final StringBuilder sb = new StringBuilder(getNextArgRequired()); - // Pick up the rest. - for (String word : peekRemainingArgs()) { - sb.append(" ").append(word); - } - // And now convert to byte[]... - message = sb.toString().getBytes(UTF_8); - } - - mSecureCommsManager.sendSecureMessage(associationId, /* messageId */ 0, - message); - break; - case "simulate-device-appeared": associationId = getNextIntArgRequired(); mDevicePresenceMonitor.simulateDeviceAppeared(associationId); @@ -167,8 +133,6 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" Create a new Association."); pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS"); pw.println(" Remove an existing Association."); - pw.println(" send-secure-message ASSOCIATION_ID [--base64] MESSAGE"); - pw.println(" Send a secure message to an associated companion device."); pw.println(" clear-association-memory-cache"); pw.println(" Clear the in-memory association cache and reload all association "); pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY."); diff --git a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java deleted file mode 100644 index 91ad93c98240..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer; - -class CompanionMessageInfo { - - private final long mId; - private final int mPage; - private final int mTotal; - private final int mType; - private final byte[] mData; - - CompanionMessageInfo(long id, int page, int total, int type, byte[] data) { - mId = id; - mPage = page; - mTotal = total; - mType = type; - mData = data; - } - - public long getId() { - return mId; - } - - public int getPage() { - return mPage; - } - - public int getType() { - return mType; - } - - public byte[] getData() { - return mData; - } -} diff --git a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java deleted file mode 100644 index 98a00aa6fadc..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.util.Slog; -import android.util.proto.ProtoInputStream; -import android.util.proto.ProtoOutputStream; - -import com.android.server.companion.proto.CompanionMessage; -import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * This class builds and reads CompanionMessage. And also paginate and combine messages. - */ -public class CompanionMessageProcessor { - - private static final String LOG_TAG = CompanionMessageProcessor.class.getSimpleName(); - - /** Listener for incoming complete messages. */ - interface Listener { - /** When a complete message is received from the companion app. */ - void onCompleteMessageReceived(@NonNull CompanionMessageInfo message); - } - - // Rough size for each CompanionMessage, each message can exceed 50K for a little, but not - // too much. Hard limit is 100K, WCS data processing limit. Closer to 100K, less stable at - // the WCS data processing layer. Refer to - // https://developers.google.com/android/reference/com/google/android/gms/wearable/MessageClient - // #public-abstract-taskinteger-sendmessage-string-nodeid,-string-path,-byte[]-data - private static final int MESSAGE_SIZE_IN_BYTES = 50000; - - private final CompanionSecureCommunicationsManager mSecureCommsManager; - - @Nullable - private Listener mListener; - - // Association id -> (parent id -> received messages) - private final Map<Integer, Map<Integer, List<CompanionMessageInfo>>> mAssociationsMessagesMap = - new HashMap<>(); - // Association id -> next parent id - private final Map<Integer, Integer> mNextParentId = new HashMap<>(); - - public CompanionMessageProcessor(CompanionSecureCommunicationsManager secureCommsManager) { - mSecureCommsManager = secureCommsManager; - mSecureCommsManager.setListener(this::onDecryptedMessageReceived); - } - - public void setListener(@NonNull Listener listener) { - mListener = listener; - } - - /** - * Paginate the data into multiple messages with size limit. And dispatch the messages to the - * companion app. - */ - public void paginateAndDispatchMessagesToApp(byte[] data, int messageType, - String packageName, int userId, int associationId) { - Slog.i(LOG_TAG, "Paginating " + data.length + " bytes."); - - final int totalMessageCount = (data.length / MESSAGE_SIZE_IN_BYTES) - + ((data.length % MESSAGE_SIZE_IN_BYTES == 0) ? 0 : 1); - int parentMessageId = findNextParentId(associationId, totalMessageCount); - - for (int i = 0; i < totalMessageCount; i++) { - ProtoOutputStream proto = new ProtoOutputStream(); - int messageId = parentMessageId + i + 1; - proto.write(CompanionMessage.ID, messageId); - - long paginationInfoToken = proto.start(CompanionMessage.PAGINATION_INFO); - proto.write(CompanionMessage.PaginationInfo.PARENT_ID, parentMessageId); - proto.write(CompanionMessage.PaginationInfo.PAGE, i + 1); - proto.write(CompanionMessage.PaginationInfo.TOTAL, totalMessageCount); - proto.end(paginationInfoToken); - - proto.write(CompanionMessage.TYPE, messageType); - byte[] currentData = Arrays.copyOfRange(data, i * MESSAGE_SIZE_IN_BYTES, - Math.min((i + 1) * MESSAGE_SIZE_IN_BYTES, data.length)); - proto.write(CompanionMessage.DATA, currentData); - - byte[] message = proto.getBytes(); - - Slog.i(LOG_TAG, "Sending [" + message.length + "] bytes to " + packageName); - - mSecureCommsManager.sendSecureMessage(associationId, messageId, message); - } - } - - /** - * Process the message and store it. If all the messages with the same parent id have been - * received, return the message with combined message data. Otherwise, return null if there's - * still data parts missing. - */ - public CompanionMessageInfo onDecryptedMessageReceived(int messageId, int associationId, - byte[] message) { - Slog.i(LOG_TAG, "Partial message received, size [" + message.length - + "], reading from protobuf."); - - ProtoInputStream proto = new ProtoInputStream(message); - try { - int id = 0; - int parentId = 0; - int page = 0; - int total = 0; - int type = CompanionMessage.UNKNOWN; - byte[] data = null; - - // Read proto data - while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (proto.getFieldNumber()) { - case (int) CompanionMessage.ID: - id = proto.readInt(CompanionMessage.ID); - break; - case (int) CompanionMessage.PAGINATION_INFO: - long paginationToken = proto.start(CompanionMessage.PAGINATION_INFO); - while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (proto.getFieldNumber()) { - case (int) CompanionMessage.PaginationInfo.PARENT_ID: - parentId = proto.readInt( - CompanionMessage.PaginationInfo.PARENT_ID); - break; - case (int) CompanionMessage.PaginationInfo.PAGE: - page = proto.readInt(CompanionMessage.PaginationInfo.PAGE); - break; - case (int) CompanionMessage.PaginationInfo.TOTAL: - total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL); - break; - default: - Slog.e(LOG_TAG, "Unexpected field id " - + proto.getFieldNumber() + " for PaginationInfo."); - break; - } - } - proto.end(paginationToken); - break; - case (int) CompanionMessage.TYPE: - type = proto.readInt(CompanionMessage.TYPE); - break; - case (int) CompanionMessage.DATA: - data = proto.readBytes(CompanionMessage.DATA); - break; - default: - Slog.e(LOG_TAG, "Unexpected field id " + proto.getFieldNumber() - + " for CompanionMessage."); - break; - } - } - - if (id == messageId) { - CompanionMessageInfo messageInfo = new CompanionMessageInfo(id, page, total, type, - data); - // Add the message into mAssociationsMessagesMap - Map<Integer, List<CompanionMessageInfo>> associationMessages = - mAssociationsMessagesMap.getOrDefault(associationId, new HashMap<>()); - List<CompanionMessageInfo> childMessages = associationMessages.getOrDefault( - parentId, new ArrayList<>()); - childMessages.add(messageInfo); - associationMessages.put(parentId, childMessages); - mAssociationsMessagesMap.put(associationId, associationMessages); - // Check if all the messages with the same parentId are received. - if (childMessages.size() == total) { - Slog.i(LOG_TAG, "All [" + total + "] messages are received for parentId [" - + parentId + "]. Processing."); - - childMessages.sort(Comparator.comparing(CompanionMessageInfo::getPage)); - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - for (int i = 0; i < childMessages.size(); i++) { - stream.write(childMessages.get(i).getData()); - } - mAssociationsMessagesMap.remove(parentId); - mListener.onCompleteMessageReceived( - new CompanionMessageInfo(parentId, 0, total, type, - stream.toByteArray())); - } else { - Slog.i(LOG_TAG, "[" + childMessages.size() + "/" + total - + "] messages are received for parentId [" + parentId + "]"); - } - } else { - Slog.e(LOG_TAG, "Message id mismatch."); - return null; - } - } catch (IOException e) { - Slog.e(LOG_TAG, "Can't read proto from the message."); - return null; - } - return null; - } - - /** - * Find the next parent id from [1, Integer.MAX_VALUE]. - * The parent and child ids are incremental. - */ - private int findNextParentId(int associationId, int totalMessageCount) { - int nextParentId = mNextParentId.getOrDefault(associationId, 1); - - // If the last child message id exceeds the Integer range, start from 1 again. - if (nextParentId > Integer.MAX_VALUE - totalMessageCount - 1) { - nextParentId = 1; - } - - mNextParentId.put(associationId, nextParentId + totalMessageCount + 1); - - return nextParentId; - } -} diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 7eede552ac71..f0a0492e3806 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -38,34 +38,27 @@ import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; import android.permission.PermissionControllerManager; import android.util.Slog; -import android.util.SparseArray; -import com.android.internal.annotations.GuardedBy; import com.android.server.companion.AssociationStore; import com.android.server.companion.CompanionDeviceManagerService; import com.android.server.companion.PermissionsUtils; -import com.android.server.companion.proto.CompanionMessage; +import com.android.server.companion.transport.CompanionTransportManager; -import libcore.io.IoUtils; -import libcore.io.Streams; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; /** - * This processor builds user consent intent for a given SystemDataTransferRequest and processes the - * request when the system is ready (a secure channel is established between the handhold and the - * companion device). + * This processor builds user consent intent for a + * {@link SystemDataTransferRequest} and processes the request once a channel is + * established between the local and remote companion device. */ public class SystemDataTransferProcessor { @@ -85,21 +78,19 @@ public class SystemDataTransferProcessor { private final Context mContext; private final AssociationStore mAssociationStore; private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; - private final CompanionMessageProcessor mCompanionMessageProcessor; + private final CompanionTransportManager mTransportManager; private final PermissionControllerManager mPermissionControllerManager; private final ExecutorService mExecutor; - @GuardedBy("mTransports") - private final SparseArray<Transport> mTransports = new SparseArray<>(); public SystemDataTransferProcessor(CompanionDeviceManagerService service, AssociationStore associationStore, SystemDataTransferRequestStore systemDataTransferRequestStore, - CompanionMessageProcessor companionMessageProcessor) { + CompanionTransportManager transportManager) { mContext = service.getContext(); mAssociationStore = associationStore; mSystemDataTransferRequestStore = systemDataTransferRequestStore; - mCompanionMessageProcessor = companionMessageProcessor; - mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived); + mTransportManager = transportManager; + mTransportManager.setListener(this::onReceivePermissionRestore); mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class); mExecutor = Executors.newSingleThreadExecutor(); } @@ -194,73 +185,42 @@ public class SystemDataTransferProcessor { return; } - // TODO: Establish a secure channel - // Start permission sync final long callingIdentityToken = Binder.clearCallingIdentity(); try { + // TODO: refactor to work with streams of data mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId), - mExecutor, - backup -> mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup, - CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId)); + mExecutor, backup -> { + Future<?> result = mTransportManager + .requestPermissionRestore(associationId, backup); + try { + result.get(15, TimeUnit.SECONDS); + try { + callback.onResult(); + } catch (RemoteException ignored) { + } + } catch (Exception e) { + try { + callback.onError(e.getMessage()); + } catch (RemoteException ignored) { + } + } + }); } finally { Binder.restoreCallingIdentity(callingIdentityToken); } } - public void attachSystemDataTransport(String packageName, int userId, int associationId, - ParcelFileDescriptor fd) { - synchronized (mTransports) { - // TODO: restore once testing has evolved - // resolveAssociation(packageName, userId, associationId); - - if (mTransports.contains(associationId)) { - detachSystemDataTransport(packageName, userId, associationId); - } - - final Transport transport = new Transport(fd); - transport.start(); - mTransports.put(associationId, transport); - } - } - - public void detachSystemDataTransport(String packageName, int userId, int associationId) { - synchronized (mTransports) { - // TODO: restore once testing has evolved - // resolveAssociation(packageName, userId, associationId); - - final Transport transport = mTransports.get(associationId); - if (transport != null) { - mTransports.delete(associationId); - transport.stop(); - } - } - } - - /** - * Process a complete decrypted message reported by the companion app. - */ - public void onCompleteMessageReceived(@NonNull CompanionMessageInfo completeMessage) { - switch (completeMessage.getType()) { - case CompanionMessage.PERMISSION_SYNC: - processPermissionSyncMessage(completeMessage); - break; - default: - Slog.e(LOG_TAG, "Unknown message type [" + completeMessage.getType() - + "]. Unable to process."); - } - } - - private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) { + private void onReceivePermissionRestore(byte[] message) { Slog.i(LOG_TAG, "Applying permissions."); // Start applying permissions UserHandle user = mContext.getUser(); final long callingIdentityToken = Binder.clearCallingIdentity(); try { + // TODO: refactor to work with streams of data mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup( - messageInfo.getData(), user); + message, user); } finally { - Slog.i(LOG_TAG, "Permissions applied."); Binder.restoreCallingIdentity(callingIdentityToken); } } @@ -291,74 +251,4 @@ public class SystemDataTransferProcessor { Slog.e(LOG_TAG, "Unknown result code:" + resultCode); } }; - - private class Transport { - private final InputStream mRemoteIn; - private final OutputStream mRemoteOut; - - private volatile boolean mStopped; - - public Transport(ParcelFileDescriptor fd) { - mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd); - mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - } - - public void start() { - new Thread(() -> { - try { - while (!mStopped) { - processNextCommand(); - } - } catch (IOException e) { - if (!mStopped) { - Slog.w(LOG_TAG, "Trouble during transport", e); - stop(); - } - } - }).start(); - } - - public void stop() { - mStopped = true; - - IoUtils.closeQuietly(mRemoteIn); - IoUtils.closeQuietly(mRemoteOut); - } - - private void processNextCommand() throws IOException { - Slog.d(LOG_TAG, "Waiting for next command..."); - - // Read message header - final byte[] headerBytes = new byte[8]; - Streams.readFully(mRemoteIn, headerBytes); - final ByteBuffer header = ByteBuffer.wrap(headerBytes); - final int command = header.getInt(); - final int length = header.getInt(); - - Slog.d(LOG_TAG, "Received command 0x" + Integer.toHexString(command) - + " length " + length); - switch (command) { - case 0x50490000: // PI(NG) version 0 - // Repeat back the given payload, within reason - final int target = Math.min(length, 1_000_000); - final byte[] payload = new byte[target]; - Streams.readFully(mRemoteIn, payload); - Streams.skipByReading(mRemoteIn, length - target); - - // Respond with PO(NG) version 0 - header.rewind(); - header.putInt(0x504F0000); - header.putInt(target); - mRemoteOut.write(header.array()); - mRemoteOut.write(payload); - break; - - default: - // Emit local warning, and skip message to - // handle next one - Slog.w(LOG_TAG, "Unknown command 0x" + Integer.toHexString(command)); - mRemoteIn.skip(length); - } - } - } } diff --git a/services/companion/java/com/android/server/companion/proto/companion_message.proto b/services/companion/java/com/android/server/companion/proto/companion_message.proto deleted file mode 100644 index 893a0dbfc852..000000000000 --- a/services/companion/java/com/android/server/companion/proto/companion_message.proto +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -syntax = "proto3"; - -option java_multiple_files = true; - -package com.android.server.companion.proto; - -/* Represents a message between companion devices */ -message CompanionMessage { - int32 id = 1; - - PaginationInfo paginationInfo = 2; - - Type type = 3; - - // message body data - bytes data = 4; - - /* Message pagination info */ - message PaginationInfo { - // id of the parent message, which should be the same for all the paginated messages - int32 parentId = 1; - - // page number of the current message in [1, total] - int32 page = 2; - - // total number of messages - int32 total = 3; - } - - /* Message type */ - enum Type { - // default value for proto3 - UNKNOWN = 0; - - // handshake message to establish secure channel - SECURE_CHANNEL_HANDSHAKE = 1; - - // permission sync - PERMISSION_SYNC = 2; - } -} diff --git a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java b/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java deleted file mode 100644 index 16a74ecd2068..000000000000 --- a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.securechannel; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.companion.AssociationInfo; -import android.util.Base64; -import android.util.Log; -import android.util.Slog; - -import com.android.server.companion.AssociationStore; -import com.android.server.companion.CompanionApplicationController; - -/** Secure Comms Manager */ -@SuppressLint("LongLogTag") -public class CompanionSecureCommunicationsManager { - static final String TAG = "CompanionDevice_SecureComms"; - static final boolean DEBUG = false; - - /** Listener for incoming decrypted messages. */ - public interface Listener { - /** When an incoming message is decrypted. */ - void onDecryptedMessageReceived(int messageId, int associationId, byte[] message); - } - - private final AssociationStore mAssociationStore; - private final CompanionApplicationController mCompanionAppController; - - @Nullable - private Listener mListener; - - /** Constructor */ - public CompanionSecureCommunicationsManager(AssociationStore associationStore, - CompanionApplicationController companionApplicationController) { - mAssociationStore = associationStore; - mCompanionAppController = companionApplicationController; - } - - public void setListener(@NonNull Listener listener) { - mListener = listener; - } - - /** - * Send a data to the associated companion device via secure channel (establishing one if - * needed). - * @param associationId associationId of the "recipient" companion device. - * @param messageId id of the message - * @param message data to be sent securely. - */ - public void sendSecureMessage(int associationId, int messageId, @NonNull byte[] message) { - if (DEBUG) { - Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n" - + " message (Base64)=\"" + Base64.encodeToString(message, 0) + "\""); - } - - final AssociationInfo association = mAssociationStore.getAssociationById(associationId); - if (association == null) { - throw new IllegalArgumentException( - "Association with ID " + associationId + " does not exist"); - } - if (DEBUG) Log.d(TAG, " association=" + association); - - final int userId = association.getUserId(); - final String packageName = association.getPackageName(); - - // Bind to the app if it hasn't been bound. - if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { - Slog.d(TAG, "userId [" + userId + "] packageName [" + packageName - + "] is not bound. Binding it now to send a secure message."); - mCompanionAppController.bindCompanionApplication(userId, packageName, - association.isSelfManaged()); - - // TODO(b/202926196): implement: encrypt and pass on the companion application for - // transporting - mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId, - message); - - Slog.d(TAG, "Unbinding userId [" + userId + "] packageName [" + packageName - + "]"); - mCompanionAppController.unbindCompanionApplication(userId, packageName); - } - - // TODO(b/202926196): implement: encrypt and pass on the companion application for - // transporting - mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId, - message); - } - - /** - * Decrypt and dispatch message received from an associated companion device. - * @param associationId associationId of the "sender" companion device. - * @param encryptedMessage data. - */ - public void receiveSecureMessage(int messageId, int associationId, - @NonNull byte[] encryptedMessage) { - if (DEBUG) { - Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n" - + " message (Base64)=\"" + Base64.encodeToString(encryptedMessage, 0) + "\""); - } - - // TODO(b/202926196): implement: decrypt and dispatch - - mListener.onDecryptedMessageReceived(messageId, associationId, encryptedMessage); - } -} diff --git a/services/companion/java/com/android/server/companion/securechannel/OWNERS b/services/companion/java/com/android/server/companion/securechannel/OWNERS deleted file mode 100644 index ecb97f402136..000000000000 --- a/services/companion/java/com/android/server/companion/securechannel/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -set noparent - -sergeynv@google.com -ewol@google.com diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java new file mode 100644 index 000000000000..4a990095cd8f --- /dev/null +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.transport; + +import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.ActivityManagerInternal; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Binder; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; + +import libcore.io.IoUtils; +import libcore.io.Streams; +import libcore.util.EmptyArray; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; + +@SuppressLint("LongLogTag") +public class CompanionTransportManager { + private static final String TAG = "CompanionTransportManager"; + // TODO: flip to false + private static final boolean DEBUG = true; + + private static final int HEADER_LENGTH = 12; + // TODO: refactor message processing to use streams to remove this limit + private static final int MAX_PAYLOAD_LENGTH = 1_000_000; + + private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN + private static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES + + private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC + private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI + + private static boolean isRequest(int message) { + return (message & 0xFF000000) == 0x63000000; + } + + private static boolean isResponse(int message) { + return (message & 0xFF000000) == 0x33000000; + } + + public interface Listener { + void onRequestPermissionRestore(byte[] data); + } + + private final Context mContext; + + @GuardedBy("mTransports") + private final SparseArray<Transport> mTransports = new SparseArray<>(); + + @Nullable + private Listener mListener; + + public CompanionTransportManager(Context context) { + mContext = context; + } + + public void setListener(@NonNull Listener listener) { + mListener = listener; + } + + /** + * For the moment, we only offer transporting of system data to built-in + * companion apps; future work will improve the security model to support + * third-party companion apps. + */ + private void enforceCallerCanTransportSystemData(String packageName, int userId) { + mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG); + + try { + final ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser( + packageName, 0, userId); + final int instrumentationUid = LocalServices.getService(ActivityManagerInternal.class) + .getInstrumentationSourceUid(Binder.getCallingUid()); + if (!Build.isDebuggable() && !info.isSystemApp() + && instrumentationUid == android.os.Process.INVALID_UID) { + throw new SecurityException("Transporting of system data currently only available " + + "to built-in companion apps or tests"); + } + } catch (NameNotFoundException e) { + throw new IllegalArgumentException(e); + } + } + + public void attachSystemDataTransport(String packageName, int userId, int associationId, + ParcelFileDescriptor fd) { + enforceCallerCanTransportSystemData(packageName, userId); + synchronized (mTransports) { + if (mTransports.contains(associationId)) { + detachSystemDataTransport(packageName, userId, associationId); + } + + final Transport transport = new Transport(associationId, fd); + transport.start(); + mTransports.put(associationId, transport); + } + } + + public void detachSystemDataTransport(String packageName, int userId, int associationId) { + enforceCallerCanTransportSystemData(packageName, userId); + synchronized (mTransports) { + final Transport transport = mTransports.get(associationId); + if (transport != null) { + mTransports.delete(associationId); + transport.stop(); + } + } + } + + public Future<?> requestPermissionRestore(int associationId, byte[] data) { + synchronized (mTransports) { + final Transport transport = mTransports.get(associationId); + if (transport != null) { + return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data); + } else { + return CompletableFuture.failedFuture(new IOException("Missing transport")); + } + } + } + + private class Transport { + private final int mAssociationId; + + private final InputStream mRemoteIn; + private final OutputStream mRemoteOut; + + private final AtomicInteger mNextSequence = new AtomicInteger(); + + @GuardedBy("mPendingRequests") + private final SparseArray<CompletableFuture<byte[]>> mPendingRequests = new SparseArray<>(); + + private volatile boolean mStopped; + + public Transport(int associationId, ParcelFileDescriptor fd) { + mAssociationId = associationId; + mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd); + mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + } + + public void start() { + new Thread(() -> { + try { + while (!mStopped) { + receiveMessage(); + } + } catch (IOException e) { + if (!mStopped) { + Slog.w(TAG, "Trouble during transport", e); + stop(); + } + } + }).start(); + } + + public void stop() { + mStopped = true; + + IoUtils.closeQuietly(mRemoteIn); + IoUtils.closeQuietly(mRemoteOut); + } + + public Future<byte[]> requestForResponse(int message, byte[] data) { + final int sequence = mNextSequence.incrementAndGet(); + final CompletableFuture<byte[]> pending = new CompletableFuture<>(); + synchronized (mPendingRequests) { + mPendingRequests.put(sequence, pending); + } + try { + sendMessage(message, sequence, data); + } catch (IOException e) { + synchronized (mPendingRequests) { + mPendingRequests.remove(sequence); + } + pending.completeExceptionally(e); + } + return pending; + } + + private void sendMessage(int message, int sequence, @NonNull byte[] data) + throws IOException { + if (DEBUG) { + Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message) + + " sequence " + sequence + " length " + data.length + + " to association " + mAssociationId); + } + + synchronized (mRemoteOut) { + final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH) + .putInt(message) + .putInt(sequence) + .putInt(data.length); + mRemoteOut.write(header.array()); + mRemoteOut.write(data); + mRemoteOut.flush(); + } + } + + private void receiveMessage() throws IOException { + if (DEBUG) { + Slog.d(TAG, "Waiting for next message..."); + } + + final byte[] headerBytes = new byte[HEADER_LENGTH]; + Streams.readFully(mRemoteIn, headerBytes); + final ByteBuffer header = ByteBuffer.wrap(headerBytes); + final int message = header.getInt(); + final int sequence = header.getInt(); + final int length = header.getInt(); + + if (DEBUG) { + Slog.d(TAG, "Received message 0x" + Integer.toHexString(message) + + " sequence " + sequence + " length " + length + + " from association " + mAssociationId); + } + if (length > MAX_PAYLOAD_LENGTH) { + Slog.w(TAG, "Ignoring message 0x" + Integer.toHexString(message) + + " sequence " + sequence + " length " + length + + " from association " + mAssociationId + " beyond maximum length"); + Streams.skipByReading(mRemoteIn, length); + return; + } + + final byte[] data = new byte[length]; + Streams.readFully(mRemoteIn, data); + + if (isRequest(message)) { + processRequest(message, sequence, data); + } else if (isResponse(message)) { + processResponse(message, sequence, data); + } else { + Slog.w(TAG, "Unknown message " + Integer.toHexString(message)); + } + } + + private void processRequest(int message, int sequence, byte[] data) + throws IOException { + switch (message) { + case MESSAGE_REQUEST_PING: { + sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data); + break; + } + case MESSAGE_REQUEST_PERMISSION_RESTORE: { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) + && !Build.isDebuggable()) { + Slog.w(TAG, "Restoring permissions only supported on watches"); + sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE); + break; + } + try { + mListener.onRequestPermissionRestore(data); + sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE); + } catch (Exception e) { + Slog.w(TAG, "Failed to restore permissions"); + sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE); + } + break; + } + default: { + Slog.w(TAG, "Unknown request " + Integer.toHexString(message)); + sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE); + break; + } + } + } + + private void processResponse(int message, int sequence, byte[] data) { + final CompletableFuture<byte[]> future; + synchronized (mPendingRequests) { + future = mPendingRequests.removeReturnOld(sequence); + } + if (future == null) { + Slog.w(TAG, "Ignoring unknown sequence " + sequence); + return; + } + + switch (message) { + case MESSAGE_RESPONSE_SUCCESS: { + future.complete(data); + } + case MESSAGE_RESPONSE_FAILURE: { + future.completeExceptionally(new RuntimeException("Remote failure")); + } + default: { + Slog.w(TAG, "Ignoring unknown response " + Integer.toHexString(message)); + } + } + } + } +} diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java index d3ef6bed46a0..07b6843b2feb 100644 --- a/services/core/java/com/android/server/VpnManagerService.java +++ b/services/core/java/com/android/server/VpnManagerService.java @@ -45,6 +45,7 @@ import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.INetworkManagementService; +import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.ServiceManager; @@ -131,6 +132,12 @@ public class VpnManagerService extends IVpnManager.Stub { return INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); } + + /** Create a VPN. */ + public Vpn createVpn(Looper looper, Context context, INetworkManagementService nms, + INetd netd, int userId) { + return new Vpn(looper, context, nms, netd, userId, new VpnProfileStore()); + } } public VpnManagerService(Context context, Dependencies deps) { @@ -688,6 +695,7 @@ public class VpnManagerService extends IVpnManager.Stub { // Listen to package add and removal events for all users. intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); @@ -738,6 +746,10 @@ public class VpnManagerService extends IVpnManager.Stub { final boolean isReplacing = intent.getBooleanExtra( Intent.EXTRA_REPLACING, false); onPackageRemoved(packageName, uid, isReplacing); + } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + final boolean isReplacing = intent.getBooleanExtra( + Intent.EXTRA_REPLACING, false); + onPackageAdded(packageName, uid, isReplacing); } else { Log.wtf(TAG, "received unexpected intent: " + action); } @@ -757,15 +769,15 @@ public class VpnManagerService extends IVpnManager.Stub { } }; - private void onUserStarted(int userId) { + @VisibleForTesting + void onUserStarted(int userId) { synchronized (mVpns) { Vpn userVpn = mVpns.get(userId); if (userVpn != null) { loge("Starting user already has a VPN"); return; } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, - new VpnProfileStore()); + userVpn = mDeps.createVpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId); mVpns.put(userId, userVpn); if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { updateLockdownVpn(); @@ -842,7 +854,8 @@ public class VpnManagerService extends IVpnManager.Stub { } } - private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { + @VisibleForTesting + void onPackageRemoved(String packageName, int uid, boolean isReplacing) { if (TextUtils.isEmpty(packageName) || uid < 0) { Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); return; @@ -851,15 +864,34 @@ public class VpnManagerService extends IVpnManager.Stub { final int userId = UserHandle.getUserId(uid); synchronized (mVpns) { final Vpn vpn = mVpns.get(userId); - if (vpn == null) { + if (vpn == null || isReplacing) { return; } // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { + if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { log("Removing always-on VPN package " + packageName + " for user " + userId); vpn.setAlwaysOnPackage(null, false, null); } + + vpn.refreshPlatformVpnAppExclusionList(); + } + } + + @VisibleForTesting + void onPackageAdded(String packageName, int uid, boolean isReplacing) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Log.wtf(TAG, "Invalid package in onPackageAdded: " + packageName + " | " + uid); + return; + } + + final int userId = UserHandle.getUserId(uid); + synchronized (mVpns) { + final Vpn vpn = mVpns.get(userId); + + if (vpn != null && !isReplacing) { + vpn.refreshPlatformVpnAppExclusionList(); + } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 2ec744fd4bb8..a3ae35eb1811 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -24,6 +24,7 @@ import static android.app.WaitResult.launchStateToString; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; +import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL; import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW; @@ -175,6 +176,7 @@ final class ActivityManagerShellCommand extends ShellCommand { private String mAgent; // Agent to attach on startup. private boolean mAttachAgentDuringBind; // Whether agent should be attached late. private int mDisplayId; + private int mTaskDisplayAreaFeatureId; private int mWindowingMode; private int mActivityType; private int mTaskId; @@ -368,6 +370,7 @@ final class ActivityManagerShellCommand extends ShellCommand { mStreaming = false; mUserId = defUser; mDisplayId = INVALID_DISPLAY; + mTaskDisplayAreaFeatureId = FEATURE_UNDEFINED; mWindowingMode = WINDOWING_MODE_UNDEFINED; mActivityType = ACTIVITY_TYPE_UNDEFINED; mTaskId = INVALID_TASK_ID; @@ -423,6 +426,8 @@ final class ActivityManagerShellCommand extends ShellCommand { mReceiverPermission = getNextArgRequired(); } else if (opt.equals("--display")) { mDisplayId = Integer.parseInt(getNextArgRequired()); + } else if (opt.equals("--task-display-area-feature-id")) { + mTaskDisplayAreaFeatureId = Integer.parseInt(getNextArgRequired()); } else if (opt.equals("--windowingMode")) { mWindowingMode = Integer.parseInt(getNextArgRequired()); } else if (opt.equals("--activityType")) { @@ -554,6 +559,12 @@ final class ActivityManagerShellCommand extends ShellCommand { options = ActivityOptions.makeBasic(); options.setLaunchDisplayId(mDisplayId); } + if (mTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setLaunchTaskDisplayAreaFeatureId(mTaskDisplayAreaFeatureId); + } if (mWindowingMode != WINDOWING_MODE_UNDEFINED) { if (options == null) { options = ActivityOptions.makeBasic(); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index fb4b683b4870..42d8778522c5 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -365,6 +365,8 @@ public class AudioService extends IAudioService.Stub private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45; private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46; private static final int MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR = 47; + private static final int MSG_ROTATION_UPDATE = 48; + private static final int MSG_FOLD_UPDATE = 49; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), @@ -1251,7 +1253,9 @@ public class AudioService extends IAudioService.Stub intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); if (mMonitorRotation) { - RotationHelper.init(mContext, mAudioHandler); + RotationHelper.init(mContext, mAudioHandler, + rotationParam -> onRotationUpdate(rotationParam), + foldParam -> onFoldUpdate(foldParam)); } intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); @@ -1398,6 +1402,20 @@ public class AudioService extends IAudioService.Stub } //----------------------------------------------------------------- + // rotation/fold updates coming from RotationHelper + void onRotationUpdate(String rotationParameter) { + // use REPLACE as only the last rotation matters + sendMsg(mAudioHandler, MSG_ROTATION_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, + /*obj*/ rotationParameter, /*delay*/ 0); + } + + void onFoldUpdate(String foldParameter) { + // use REPLACE as only the last fold state matters + sendMsg(mAudioHandler, MSG_FOLD_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, + /*obj*/ foldParameter, /*delay*/ 0); + } + + //----------------------------------------------------------------- // monitoring requests for volume range initialization @Override // AudioSystemAdapter.OnVolRangeInitRequestListener public void onVolumeRangeInitRequestFromNative() { @@ -8327,6 +8345,16 @@ public class AudioService extends IAudioService.Stub case MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR: dispatchDeviceVolumeBehavior((AudioDeviceAttributes) msg.obj, msg.arg1); break; + + case MSG_ROTATION_UPDATE: + // rotation parameter format: "rotation=x" where x is one of 0, 90, 180, 270 + mAudioSystem.setParameters((String) msg.obj); + break; + + case MSG_FOLD_UPDATE: + // fold parameter format: "device_folded=x" where x is one of on, off + mAudioSystem.setParameters((String) msg.obj); + break; } } } diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java index eb8387fe85e5..5cdf58bdd62f 100644 --- a/services/core/java/com/android/server/audio/RotationHelper.java +++ b/services/core/java/com/android/server/audio/RotationHelper.java @@ -21,13 +21,14 @@ import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; -import android.media.AudioSystem; import android.os.Handler; import android.os.HandlerExecutor; import android.util.Log; import android.view.Display; import android.view.Surface; +import java.util.function.Consumer; + /** * Class to handle device rotation events for AudioService, and forward device rotation * and folded state to the audio HALs through AudioSystem. @@ -53,6 +54,10 @@ class RotationHelper { private static AudioDisplayListener sDisplayListener; private static FoldStateListener sFoldStateListener; + /** callback to send rotation updates to AudioSystem */ + private static Consumer<String> sRotationUpdateCb; + /** callback to send folded state updates to AudioSystem */ + private static Consumer<String> sFoldUpdateCb; private static final Object sRotationLock = new Object(); private static final Object sFoldStateLock = new Object(); @@ -67,13 +72,16 @@ class RotationHelper { * - sDisplayListener != null * - sContext != null */ - static void init(Context context, Handler handler) { + static void init(Context context, Handler handler, + Consumer<String> rotationUpdateCb, Consumer<String> foldUpdateCb) { if (context == null) { throw new IllegalArgumentException("Invalid null context"); } sContext = context; sHandler = handler; sDisplayListener = new AudioDisplayListener(); + sRotationUpdateCb = rotationUpdateCb; + sFoldUpdateCb = foldUpdateCb; enable(); } @@ -115,21 +123,26 @@ class RotationHelper { if (DEBUG_ROTATION) { Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)"); } + String rotationParam; switch (rotation) { case Surface.ROTATION_0: - AudioSystem.setParameters("rotation=0"); + rotationParam = "rotation=0"; break; case Surface.ROTATION_90: - AudioSystem.setParameters("rotation=90"); + rotationParam = "rotation=90"; break; case Surface.ROTATION_180: - AudioSystem.setParameters("rotation=180"); + rotationParam = "rotation=180"; break; case Surface.ROTATION_270: - AudioSystem.setParameters("rotation=270"); + rotationParam = "rotation=270"; break; default: Log.e(TAG, "Unknown device rotation"); + rotationParam = null; + } + if (rotationParam != null) { + sRotationUpdateCb.accept(rotationParam); } } @@ -140,11 +153,13 @@ class RotationHelper { synchronized (sFoldStateLock) { if (sDeviceFold != newFolded) { sDeviceFold = newFolded; + String foldParam; if (newFolded) { - AudioSystem.setParameters("device_folded=on"); + foldParam = "device_folded=on"; } else { - AudioSystem.setParameters("device_folded=off"); + foldParam = "device_folded=off"; } + sFoldUpdateCb.accept(foldParam); } } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 5b282ced73b5..4e5ce8ae45e4 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -4085,6 +4085,20 @@ public class Vpn { @NonNull List<String> excludedApps) { enforceNotRestrictedUser(); if (!storeAppExclusionList(packageName, excludedApps)) return false; + + updateAppExclusionList(excludedApps); + + return true; + } + + /** + * Triggers an update of the VPN network's excluded UIDs if a VPN is running. + */ + public synchronized void refreshPlatformVpnAppExclusionList() { + updateAppExclusionList(getAppExclusionList(mPackage)); + } + + private synchronized void updateAppExclusionList(@NonNull List<String> excludedApps) { // Re-build and update NetworkCapabilities via NetworkAgent. if (mNetworkAgent != null) { // Only update the platform VPN @@ -4097,8 +4111,6 @@ public class Vpn { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } } - - return true; } /** diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 157057d0e4b7..aaa9ee5f5926 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -337,6 +337,8 @@ final class Constants { static final int AUDIO_CODEC_WMAPRO = 0xE; // Support WMA-Pro static final int AUDIO_CODEC_MAX = 0xF; + static final int AUDIO_FORMAT_MASK = 0b0111_1000; + @StringDef({ AUDIO_DEVICE_ARC_IN, AUDIO_DEVICE_SPDIF, diff --git a/services/core/java/com/android/server/hdmi/RequestSadAction.java b/services/core/java/com/android/server/hdmi/RequestSadAction.java index 23aaf3260bac..0188e963140e 100644 --- a/services/core/java/com/android/server/hdmi/RequestSadAction.java +++ b/services/core/java/com/android/server/hdmi/RequestSadAction.java @@ -200,8 +200,14 @@ final class RequestSadAction extends HdmiCecFeatureAction { } private boolean isValidCodec(byte codec) { - return Constants.AUDIO_CODEC_NONE < (codec & 0xFF) - && (codec & 0xFF) <= Constants.AUDIO_CODEC_MAX; + // Bit 7 needs to be 0. + if ((codec & (1 << 7)) != 0) { + return false; + } + // Bit [6, 3] is the audio format code. + int audioFormatCode = (codec & Constants.AUDIO_FORMAT_MASK) >> 3; + return Constants.AUDIO_CODEC_NONE < audioFormatCode + && audioFormatCode <= Constants.AUDIO_CODEC_MAX; } private void updateResult(byte[] sad) { diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index 4ffad91bbfad..6c75dbf033d6 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -108,12 +108,17 @@ final class IInputMethodInvoker { } @AnyThread - void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, - int configChanges, boolean stylusHwSupported, - @InputMethodNavButtonFlags int navButtonFlags) { + void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations, + int configChanges, boolean stylusHandWritingSupported, + @InputMethodNavButtonFlags int navigationBarFlags) { + final IInputMethod.InitParams params = new IInputMethod.InitParams(); + params.token = token; + params.privilegedOperations = privilegedOperations; + params.configChanges = configChanges; + params.stylusHandWritingSupported = stylusHandWritingSupported; + params.navigationBarFlags = navigationBarFlags; try { - mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported, - navButtonFlags); + mTarget.initializeInternal(params); } catch (RemoteException e) { logRemoteException(e); } @@ -148,13 +153,19 @@ final class IInputMethodInvoker { } @AnyThread - void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection, + void startInput(IBinder startInputToken, IRemoteInputConnection remoteInputConnection, EditorInfo editorInfo, boolean restarting, @InputMethodNavButtonFlags int navButtonFlags, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { + final IInputMethod.StartInputParams params = new IInputMethod.StartInputParams(); + params.startInputToken = startInputToken; + params.remoteInputConnection = remoteInputConnection; + params.editorInfo = editorInfo; + params.restarting = restarting; + params.navigationBarFlags = navButtonFlags; + params.imeDispatcher = imeDispatcher; try { - mTarget.startInput(startInputToken, inputConnection, editorInfo, restarting, - navButtonFlags, imeDispatcher); + mTarget.startInput(params); } catch (RemoteException e) { logRemoteException(e); } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index b0b9e61ca21c..075fb47cceac 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -163,7 +163,8 @@ final class DeletePackageHelper { return PackageManager.DELETE_FAILED_INTERNAL_ERROR; } - if (PackageManagerServiceUtils.isSystemApp(uninstalledPs)) { + if (PackageManagerServiceUtils.isUpdatedSystemApp(uninstalledPs) + && ((deleteFlags & PackageManager.DELETE_SYSTEM_APP) == 0)) { UserInfo userInfo = mUserManagerInternal.getUserInfo(userId); if (userInfo == null || (!userInfo.isAdmin() && !mUserManagerInternal.getUserInfo( mUserManagerInternal.getProfileParentId(userId)).isAdmin())) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 9637aca4c690..c92cf33063b7 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -79,6 +79,10 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY; import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; +import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; +import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; +import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; +import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; import android.annotation.NonNull; @@ -132,6 +136,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; import com.android.server.wm.LaunchParamsController.LaunchParams; +import com.android.server.wm.TaskFragment.EmbeddingCheckResult; import java.io.PrintWriter; import java.text.DateFormat; @@ -2074,24 +2079,6 @@ class ActivityStarter { } } - if (mInTaskFragment != null && !canEmbedActivity(mInTaskFragment, r, newTask, targetTask)) { - final StringBuilder errorMsg = new StringBuilder("Permission denied: Cannot embed " + r - + " to " + mInTaskFragment.getTask() + ". newTask=" + newTask + ", targetTask= " - + targetTask); - if (newTask && isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, - LAUNCH_SINGLE_INSTANCE_PER_TASK, LAUNCH_SINGLE_TASK)) { - errorMsg.append("\nActivity tries to launch on a new task because the launch mode" - + " is " + launchModeToString(mLaunchMode)); - } else if (newTask && (mLaunchFlags & (FLAG_ACTIVITY_NEW_DOCUMENT - | FLAG_ACTIVITY_NEW_TASK)) != 0) { - errorMsg.append("\nActivity tries to launch on a new task because the launch flags" - + " contains FLAG_ACTIVITY_NEW_DOCUMENT or FLAG_ACTIVITY_NEW_TASK. " - + "mLaunchFlag=" + mLaunchFlags); - } - Slog.e(TAG, errorMsg.toString()); - return START_PERMISSION_DENIED; - } - // Do not start the activity if target display's DWPC does not allow it. // We can't return fatal error code here because it will crash the caller of // startActivity() if they don't catch the exception. We don't expect 3P apps to make @@ -2118,19 +2105,21 @@ class ActivityStarter { } /** - * Return {@code true} if an activity can be embedded to the TaskFragment. + * Returns whether embedding of {@code starting} is allowed. + * * @param taskFragment the TaskFragment for embedding. * @param starting the starting activity. - * @param newTask whether the starting activity is going to be launched on a new task. * @param targetTask the target task for launching activity, which could be different from * the one who hosting the embedding. */ - private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, - @NonNull ActivityRecord starting, boolean newTask, Task targetTask) { + @VisibleForTesting + @EmbeddingCheckResult + static int canEmbedActivity(@NonNull TaskFragment taskFragment, + @NonNull ActivityRecord starting, @NonNull Task targetTask) { final Task hostTask = taskFragment.getTask(); // Not allowed embedding a separate task or without host task. - if (hostTask == null || newTask || targetTask != hostTask) { - return false; + if (hostTask == null || targetTask != hostTask) { + return EMBEDDING_DISALLOWED_NEW_TASK; } return taskFragment.isAllowedToEmbedActivity(starting); @@ -2970,19 +2959,16 @@ class ActivityStarter { mIntentDelivered = true; } + /** Places {@link #mStartActivity} in {@code task} or an embedded {@link TaskFragment}. */ private void addOrReparentStartingActivity(@NonNull Task task, String reason) { TaskFragment newParent = task; if (mInTaskFragment != null) { - // TODO(b/234351413): remove remaining embedded Task logic. - // mInTaskFragment is created and added to the leaf task by task fragment organizer's - // request. If the task was resolved and different than mInTaskFragment, reparent the - // task to mInTaskFragment for embedding. - if (mInTaskFragment.getTask() != task) { - if (shouldReparentInTaskFragment(task)) { - task.reparent(mInTaskFragment, POSITION_TOP); - } - } else { + int embeddingCheckResult = canEmbedActivity(mInTaskFragment, mStartActivity, task); + if (embeddingCheckResult == EMBEDDING_ALLOWED) { newParent = mInTaskFragment; + } else { + // Start mStartActivity to task instead if it can't be embedded to mInTaskFragment. + sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult); } } else { TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null; @@ -2994,20 +2980,12 @@ class ActivityStarter { } } if (candidateTf != null && candidateTf.isEmbedded() - && canEmbedActivity(candidateTf, mStartActivity, false /* newTask */, task)) { + && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) { // Use the embedded TaskFragment of the top activity as the new parent if the // activity can be embedded. newParent = candidateTf; } } - // Start Activity to the Task if mStartActivity's min dimensions are not satisfied. - if (newParent.isEmbedded() && newParent.smallerThanMinDimension(mStartActivity)) { - reason += " - MinimumDimensionViolation"; - mService.mWindowOrganizerController.sendMinimumDimensionViolation( - newParent, mStartActivity.getMinDimensions(), mRequest.errorCallbackToken, - reason); - newParent = task; - } if (mStartActivity.getTaskFragment() == null || mStartActivity.getTaskFragment() == newParent) { newParent.addChild(mStartActivity, POSITION_TOP); @@ -3016,16 +2994,41 @@ class ActivityStarter { } } - private boolean shouldReparentInTaskFragment(Task task) { - // The task has not been embedded. We should reparent the task to TaskFragment. - if (!task.isEmbedded()) { - return true; + /** + * Notifies the client side that {@link #mStartActivity} cannot be embedded to + * {@code taskFragment}. + */ + private void sendCanNotEmbedActivityError(TaskFragment taskFragment, + @EmbeddingCheckResult int result) { + final String errMsg; + switch(result) { + case EMBEDDING_DISALLOWED_NEW_TASK: { + errMsg = "Cannot embed " + mStartActivity + " that launched on another task" + + ",mLaunchMode=" + launchModeToString(mLaunchMode) + + ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags); + break; + } + case EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION: { + errMsg = "Cannot embed " + mStartActivity + + ". TaskFragment's bounds:" + taskFragment.getBounds() + + ", minimum dimensions:" + mStartActivity.getMinDimensions(); + break; + } + case EMBEDDING_DISALLOWED_UNTRUSTED_HOST: { + errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity; + break; + } + default: + errMsg = "Unhandled embed result:" + result; + } + if (taskFragment.isOrganized()) { + mService.mWindowOrganizerController.sendTaskFragmentOperationFailure( + taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken, + new SecurityException(errMsg)); + } else { + // If the taskFragment is not organized, just dump error message as warning logs. + Slog.w(TAG, errMsg); } - WindowContainer<?> parent = task.getParent(); - // If the Activity is going to launch on top of embedded Task in the same TaskFragment, - // we don't need to reparent the Task. Otherwise, the embedded Task should reparent to - // another TaskFragment. - return parent.asTaskFragment() != mInTaskFragment; } private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance, diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index baa31a073dd2..927604eaa32f 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -26,7 +26,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -249,8 +251,25 @@ public class SafeActivityOptions { } // Check if the caller is allowed to launch on the specified display area. final WindowContainerToken daToken = options.getLaunchTaskDisplayArea(); - final TaskDisplayArea taskDisplayArea = daToken != null + TaskDisplayArea taskDisplayArea = daToken != null ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null; + + // If we do not have a task display area token, check if the launch task display area + // feature id is specified. + if (taskDisplayArea == null) { + final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId(); + if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) { + final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY + ? DEFAULT_DISPLAY : options.getLaunchDisplayId(); + final DisplayContent dc = supervisor.mRootWindowContainer + .getDisplayContent(launchDisplayId); + if (dc != null) { + taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda -> + tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null); + } + } + } + if (aInfo != null && taskDisplayArea != null && !supervisor.isCallerAllowedToLaunchOnTaskDisplayArea(callingPid, callingUid, taskDisplayArea, aInfo)) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index a1c22d69774b..3e6546ebb647 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -140,6 +140,45 @@ class TaskFragment extends WindowContainer<WindowContainer> { static final boolean SHOW_APP_STARTING_PREVIEW = true; /** + * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or + * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: + * indicate that an Activity can be embedded successfully. + */ + static final int EMBEDDING_ALLOWED = 0; + /** + * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or + * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: + * indicate that an Activity can't be embedded because either the Activity does not allow + * untrusted embedding, and the embedding host app is not trusted. + */ + static final int EMBEDDING_DISALLOWED_UNTRUSTED_HOST = 1; + /** + * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or + * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: + * indicate that an Activity can't be embedded because this taskFragment's bounds are + * {@link #smallerThanMinDimension(ActivityRecord)}. + */ + static final int EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION = 2; + /** + * An embedding check result of + * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: + * indicate that an Activity can't be embedded because the Activity is started on a new task. + */ + static final int EMBEDDING_DISALLOWED_NEW_TASK = 3; + + /** + * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or + * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}. + */ + @IntDef(prefix = {"EMBEDDING_"}, value = { + EMBEDDING_ALLOWED, + EMBEDDING_DISALLOWED_UNTRUSTED_HOST, + EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION, + EMBEDDING_DISALLOWED_NEW_TASK, + }) + @interface EmbeddingCheckResult {} + + /** * Indicate that the minimal width/height should use the default value. * * @see #mMinWidth @@ -509,20 +548,29 @@ class TaskFragment extends WindowContainer<WindowContainer> { return false; } - boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) { + @EmbeddingCheckResult + int isAllowedToEmbedActivity(@NonNull ActivityRecord a) { return isAllowedToEmbedActivity(a, mTaskFragmentOrganizerUid); } /** * Checks if the organized task fragment is allowed to have the specified activity, which is - * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be - * enabled. - * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) + * allowed if an activity allows embedding in untrusted mode, if the trusted mode can be + * enabled, or if the organized task fragment bounds are not + * {@link #smallerThanMinDimension(ActivityRecord)}. + * * @param uid uid of the TaskFragment organizer. + * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) */ - boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) { - return isAllowedToEmbedActivityInUntrustedMode(a) - || isAllowedToEmbedActivityInTrustedMode(a, uid); + @EmbeddingCheckResult + int isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) { + if (!isAllowedToEmbedActivityInUntrustedMode(a) + && !isAllowedToEmbedActivityInTrustedMode(a, uid)) { + return EMBEDDING_DISALLOWED_UNTRUSTED_HOST; + } else if (smallerThanMinDimension(a)) { + return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; + } + return EMBEDDING_ALLOWED; } boolean smallerThanMinDimension(@NonNull ActivityRecord activity) { @@ -539,9 +587,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { } final int minWidth = minDimensions.x; final int minHeight = minDimensions.y; - final boolean smaller = taskFragBounds.width() < minWidth + return taskFragBounds.width() < minWidth || taskFragBounds.height() < minHeight; - return smaller; } /** @@ -598,7 +645,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { // The system is trusted to embed other apps securely and for all users. return UserHandle.getAppId(uid) == SYSTEM_UID // Activities from the same UID can be embedded freely by the host. - || uid == a.getUid(); + || a.isUid(uid); } /** diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 9aff23ddde14..392d4c2f772b 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.window.TaskFragmentOrganizer.putExceptionInBundle; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; +import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; import android.annotation.IntDef; @@ -235,7 +236,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr + " is not in a task belong to the organizer app."); return; } - if (!task.isAllowedToEmbedActivity(activity, mOrganizerUid)) { + if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) { Slog.d(TAG, "Reparent activity=" + activity.token + " is not allowed to be embedded."); return; diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index 9bb02710a5bc..4141156f9caf 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -35,6 +35,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT; import static android.util.DisplayMetrics.DENSITY_DEFAULT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import static com.android.server.wm.ActivityStarter.Request; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; @@ -307,7 +308,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { TaskDisplayArea taskDisplayArea = suggestedDisplayArea; // If launch task display area is set in options we should just use it. We assume the // suggestedDisplayArea has the right one in this case. - if (options == null || options.getLaunchTaskDisplayArea() == null) { + if (options == null || (options.getLaunchTaskDisplayArea() == null + && options.getLaunchTaskDisplayAreaFeatureId() == FEATURE_UNDEFINED)) { final int activityType = mSupervisor.mRootWindowContainer.resolveActivityType(root, options, task); display.forAllTaskDisplayAreas(displayArea -> { @@ -391,7 +393,22 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (optionLaunchTaskDisplayAreaToken != null) { taskDisplayArea = (TaskDisplayArea) WindowContainer.fromBinder( optionLaunchTaskDisplayAreaToken.asBinder()); - if (DEBUG) appendLog("display-area-from-option=" + taskDisplayArea); + if (DEBUG) appendLog("display-area-token-from-option=" + taskDisplayArea); + } + + if (taskDisplayArea == null && options != null) { + final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId(); + if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) { + final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY + ? DEFAULT_DISPLAY : options.getLaunchDisplayId(); + final DisplayContent dc = mSupervisor.mRootWindowContainer + .getDisplayContent(launchDisplayId); + if (dc != null) { + taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda -> + tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null); + if (DEBUG) appendLog("display-area-feature-from-option=" + taskDisplayArea); + } + } } // If task display area is not specified in options - try display id diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 9da78e1aea3f..27d181fddb89 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -36,6 +36,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Display; @@ -56,7 +57,6 @@ import com.android.internal.util.ArrayUtils; import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.WeakHashMap; @@ -94,7 +94,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { * lifecycle order since we may be updating the visibility of task surface controls in a pending * transaction before they are presented to the task org. */ - private class TaskOrganizerCallbacks { + private static class TaskOrganizerCallbacks { final ITaskOrganizer mTaskOrganizer; final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer; @@ -123,7 +123,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } - void onTaskVanished(Task task) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task vanished taskId=%d", task.mTaskId); final RunningTaskInfo taskInfo = task.getTaskInfo(); @@ -173,11 +172,160 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } + /** + * Maintains a list of all the pending events for a given {@link android.window.TaskOrganizer} + */ + static final class TaskOrganizerPendingEventsQueue { + private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>(); + private final TaskOrganizerState mOrganizerState; + private RunningTaskInfo mTmpTaskInfo; + // Pending task events due to layout deferred. + private final ArrayList<PendingTaskEvent> mPendingTaskEvents = new ArrayList<>(); + + TaskOrganizerPendingEventsQueue(TaskOrganizerState taskOrganizerState) { + mOrganizerState = taskOrganizerState; + } + + @VisibleForTesting + public ArrayList<PendingTaskEvent> getPendingEventList() { + return mPendingTaskEvents; + } + + int numPendingTaskEvents() { + return mPendingTaskEvents.size(); + } + + void clearPendingTaskEvents() { + mPendingTaskEvents.clear(); + } + + void addPendingTaskEvent(PendingTaskEvent event) { + mPendingTaskEvents.add(event); + } + + void removePendingTaskEvent(PendingTaskEvent event) { + mPendingTaskEvents.remove(event); + } + + /** + * Removes all the pending task events for the given {@code task}. + * + * @param task + * @return true if a {@link PendingTaskEvent#EVENT_APPEARED} is still pending for the given + * {code task}. + */ + boolean removePendingTaskEvents(Task task) { + boolean foundPendingAppearedEvents = false; + for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) { + PendingTaskEvent entry = mPendingTaskEvents.get(i); + if (task.mTaskId == entry.mTask.mTaskId) { + // This task is vanished so remove all pending event of it. + mPendingTaskEvents.remove(i); + + if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) { + foundPendingAppearedEvents = true; + } + } + } + return foundPendingAppearedEvents; + } + + @Nullable + private PendingTaskEvent getPendingTaskEvent(Task task, int type) { + for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) { + PendingTaskEvent entry = mPendingTaskEvents.get(i); + if (task.mTaskId == entry.mTask.mTaskId && type == entry.mEventType) { + return entry; + } + } + return null; + } + + @VisibleForTesting + @Nullable + PendingTaskEvent getPendingLifecycleTaskEvent(Task task) { + for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) { + PendingTaskEvent entry = mPendingTaskEvents.get(i); + if (task.mTaskId == entry.mTask.mTaskId && entry.isLifecycleEvent()) { + return entry; + } + } + return null; + } + + void dispatchPendingEvents() { + if (mPendingTaskEvents.isEmpty()) { + return; + } + for (int i = 0, n = mPendingTaskEvents.size(); i < n; i++) { + dispatchPendingEvent(mPendingTaskEvents.get(i)); + } + mPendingTaskEvents.clear(); + } + + private void dispatchPendingEvent(PendingTaskEvent event) { + final Task task = event.mTask; + switch (event.mEventType) { + case PendingTaskEvent.EVENT_APPEARED: + if (task.taskAppearedReady()) { + mOrganizerState.mOrganizer.onTaskAppeared(task); + } + break; + case PendingTaskEvent.EVENT_VANISHED: + mOrganizerState.mOrganizer.onTaskVanished(task); + mLastSentTaskInfos.remove(task); + break; + case PendingTaskEvent.EVENT_INFO_CHANGED: + dispatchTaskInfoChanged(event.mTask, event.mForce); + break; + case PendingTaskEvent.EVENT_ROOT_BACK_PRESSED: + mOrganizerState.mOrganizer.onBackPressedOnTaskRoot(task); + break; + } + } + + private void dispatchTaskInfoChanged(Task task, boolean force) { + RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task); + if (mTmpTaskInfo == null) { + mTmpTaskInfo = new RunningTaskInfo(); + } + mTmpTaskInfo.configuration.unset(); + task.fillTaskInfo(mTmpTaskInfo); + + boolean changed = !mTmpTaskInfo + .equalsForTaskOrganizer(lastInfo) + || !configurationsAreEqualForOrganizer( + mTmpTaskInfo.configuration, + lastInfo.configuration); + if (!(changed || force)) { + // mTmpTaskInfo will be reused next time. + return; + } + final RunningTaskInfo newInfo = mTmpTaskInfo; + mLastSentTaskInfos.put(task, + mTmpTaskInfo); + // Since we've stored this, clean up the reference so a new one will be created next + // time. + // Transferring it this way means we only have to construct new RunningTaskInfos when + // they change. + mTmpTaskInfo = null; + + if (task.isOrganized()) { + // Because we defer sending taskAppeared() until the app has drawn, we may receive a + // configuration change before the state actually has the task registered. As such + // we should ignore these change events to the organizer until taskAppeared(). If + // the task was created by the organizer, then we always send the info change. + mOrganizerState.mOrganizer.onTaskInfoChanged(task, newInfo); + } + } + } + @VisibleForTesting class TaskOrganizerState { private final TaskOrganizerCallbacks mOrganizer; private final DeathRecipient mDeathRecipient; private final ArrayList<Task> mOrganizedTasks = new ArrayList<>(); + private final TaskOrganizerPendingEventsQueue mPendingEventsQueue; private final int mUid; TaskOrganizerState(ITaskOrganizer organizer, int uid) { @@ -187,6 +335,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { : mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable; mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer); mDeathRecipient = new DeathRecipient(organizer); + mPendingEventsQueue = new TaskOrganizerPendingEventsQueue(this); try { organizer.asBinder().linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { @@ -200,6 +349,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return mDeathRecipient; } + @VisibleForTesting + TaskOrganizerPendingEventsQueue getPendingEventsQueue() { + return mPendingEventsQueue; + } + /** * Register this task with this state, but doesn't trigger the task appeared callback to * the organizer. @@ -263,8 +417,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { // updateTaskOrganizerState should remove the task from the list, but still // check it again to avoid while-loop isn't terminate. if (removeTask(t, t.mRemoveWithTaskOrganizer)) { - TaskOrganizerController.this.onTaskVanishedInternal( - mOrganizer.mTaskOrganizer, t); + TaskOrganizerController.this.onTaskVanishedInternal(this, t); } } if (mService.getTransitionController().isShellTransitionsEnabled()) { @@ -278,8 +431,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } - // Remove organizer state after removing tasks so we get a chance to send - // onTaskVanished. + // Pending events queue for this organizer need to be cleared because this organizer + // has either died or unregistered itself. + mPendingEventsQueue.clearPendingTaskEvents(); mTaskOrganizerStates.remove(mOrganizer.getBinder()); } @@ -320,14 +474,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { // List of task organizers by priority private final ArrayDeque<ITaskOrganizer> mTaskOrganizers = new ArrayDeque<>(); - private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>(); - private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>(); - // Pending task events due to layout deferred. - private final ArrayList<PendingTaskEvent> mPendingTaskEvents = new ArrayList<>(); + private final ArrayMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new ArrayMap<>(); // Set of organized tasks (by taskId) that dispatch back pressed to their organizers private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet<>(); - private RunningTaskInfo mTmpTaskInfo; private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer; TaskOrganizerController(ActivityTaskManagerService atm) { @@ -354,11 +504,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mDeferTaskOrgCallbacksConsumer = consumer; } - @VisibleForTesting - ArrayList<PendingTaskEvent> getPendingEventList() { - return mPendingTaskEvents; - } - /** * Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode. */ @@ -592,10 +737,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { void onTaskAppeared(ITaskOrganizer organizer, Task task) { final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); if (state != null && state.addTask(task)) { - PendingTaskEvent pending = getPendingTaskEvent(task, PendingTaskEvent.EVENT_APPEARED); + final TaskOrganizerPendingEventsQueue pendingEvents = + state.mPendingEventsQueue; + PendingTaskEvent pending = pendingEvents.getPendingTaskEvent(task, + PendingTaskEvent.EVENT_APPEARED); if (pending == null) { - pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_APPEARED); - mPendingTaskEvents.add(pending); + pendingEvents.addPendingTaskEvent(new PendingTaskEvent(task, + PendingTaskEvent.EVENT_APPEARED)); } } } @@ -603,26 +751,25 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { void onTaskVanished(ITaskOrganizer organizer, Task task) { final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); if (state != null && state.removeTask(task, task.mRemoveWithTaskOrganizer)) { - onTaskVanishedInternal(organizer, task); + onTaskVanishedInternal(state, task); } } - private void onTaskVanishedInternal(ITaskOrganizer organizer, Task task) { - for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) { - PendingTaskEvent entry = mPendingTaskEvents.get(i); - if (task.mTaskId == entry.mTask.mTaskId && entry.mTaskOrg == organizer) { - // This task is vanished so remove all pending event of it. - mPendingTaskEvents.remove(i); - if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) { - // If task appeared callback still pend, ignore this callback too. - return; - } - } + private void onTaskVanishedInternal(TaskOrganizerState organizerState, Task task) { + if (organizerState == null) { + Slog.i(TAG, "cannot send onTaskVanished because organizer state is not " + + "present for this organizer"); + return; } - - PendingTaskEvent pending = - new PendingTaskEvent(task, organizer, PendingTaskEvent.EVENT_VANISHED); - mPendingTaskEvents.add(pending); + TaskOrganizerPendingEventsQueue pendingEventsQueue = + organizerState.mPendingEventsQueue; + boolean hadPendingAppearedEvents = + pendingEventsQueue.removePendingTaskEvents(task); + if (hadPendingAppearedEvents) { + return; + } + pendingEventsQueue.addPendingTaskEvent(new PendingTaskEvent(task, + organizerState.mOrganizer.mTaskOrganizer, PendingTaskEvent.EVENT_VANISHED)); } @Override @@ -690,48 +837,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } void dispatchPendingEvents() { - if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred() - || mPendingTaskEvents.isEmpty()) { + if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) { return; } - - for (int i = 0, n = mPendingTaskEvents.size(); i < n; i++) { - PendingTaskEvent event = mPendingTaskEvents.get(i); - final Task task = event.mTask; - final TaskOrganizerState state; - switch (event.mEventType) { - case PendingTaskEvent.EVENT_APPEARED: - state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder()); - if (state != null && task.taskAppearedReady()) { - state.mOrganizer.onTaskAppeared(task); - } - break; - case PendingTaskEvent.EVENT_VANISHED: - // TaskOrganizerState cannot be used here because it might have already been - // removed. - // The state is removed when an organizer dies or is unregistered. In order to - // send the pending vanished task events, the mTaskOrg from event is used. - // These events should not ideally be sent and will be removed as part of - // b/224812558. - try { - event.mTaskOrg.onTaskVanished(task.getTaskInfo()); - } catch (RemoteException ex) { - Slog.e(TAG, "Exception sending onTaskVanished callback", ex); - } - mLastSentTaskInfos.remove(task); - break; - case PendingTaskEvent.EVENT_INFO_CHANGED: - dispatchTaskInfoChanged(event.mTask, event.mForce); - break; - case PendingTaskEvent.EVENT_ROOT_BACK_PRESSED: - state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder()); - if (state != null) { - state.mOrganizer.onBackPressedOnTaskRoot(task); - } - break; - } + for (int taskOrgIdx = 0; taskOrgIdx < mTaskOrganizerStates.size(); taskOrgIdx++) { + TaskOrganizerState taskOrganizerState = mTaskOrganizerStates.valueAt(taskOrgIdx); + taskOrganizerState.mPendingEventsQueue.dispatchPendingEvents(); } - mPendingTaskEvents.clear(); } void reportImeDrawnOnTask(Task task) { @@ -750,20 +862,30 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { // Skip if task still not appeared. return; } - if (force && mPendingTaskEvents.isEmpty()) { + final TaskOrganizerState taskOrganizerState = + mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder()); + final TaskOrganizerPendingEventsQueue pendingEventsQueue = + taskOrganizerState.mPendingEventsQueue; + if (pendingEventsQueue == null) { + Slog.i(TAG, "cannot send onTaskInfoChanged because pending events queue is not " + + "present for this organizer"); + return; + } + if (force && pendingEventsQueue.numPendingTaskEvents() == 0) { // There are task-info changed events do not result in // - RootWindowContainer#performSurfacePlacementNoTrace OR // - WindowAnimator#animate // For instance, when an app requesting aspect ratio change when in PiP mode. // To solve this, we directly dispatch the pending event if there are no events queued ( // otherwise, all pending events should be dispatched on next drawn). - dispatchTaskInfoChanged(task, true /* force */); + pendingEventsQueue.dispatchTaskInfoChanged(task, true /* force */); return; } // Defer task info reporting while layout is deferred. This is because layout defer // blocks tend to do lots of re-ordering which can mess up animations in receivers. - PendingTaskEvent pending = getPendingLifecycleTaskEvent(task); + PendingTaskEvent pending = pendingEventsQueue + .getPendingLifecycleTaskEvent(task); if (pending == null) { pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_INFO_CHANGED); } else { @@ -774,45 +896,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return; } // Remove and add for re-ordering. - mPendingTaskEvents.remove(pending); + pendingEventsQueue.removePendingTaskEvent(pending); } pending.mForce |= force; - mPendingTaskEvents.add(pending); - } - - private void dispatchTaskInfoChanged(Task task, boolean force) { - RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task); - if (mTmpTaskInfo == null) { - mTmpTaskInfo = new RunningTaskInfo(); - } - mTmpTaskInfo.configuration.unset(); - task.fillTaskInfo(mTmpTaskInfo); - - boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo) - || !configurationsAreEqualForOrganizer( - mTmpTaskInfo.configuration, lastInfo.configuration); - if (!(changed || force)) { - // mTmpTaskInfo will be reused next time. - return; - } - final RunningTaskInfo newInfo = mTmpTaskInfo; - mLastSentTaskInfos.put(task, mTmpTaskInfo); - // Since we've stored this, clean up the reference so a new one will be created next time. - // Transferring it this way means we only have to construct new RunningTaskInfos when they - // change. - mTmpTaskInfo = null; - - if (task.isOrganized()) { - // Because we defer sending taskAppeared() until the app has drawn, we may receive a - // configuration change before the state actually has the task registered. As such we - // should ignore these change events to the organizer until taskAppeared(). If the task - // was created by the organizer, then we always send the info change. - final TaskOrganizerState state = mTaskOrganizerStates.get( - task.mTaskOrganizer.asBinder()); - if (state != null) { - state.mOrganizer.onTaskInfoChanged(task, newInfo); - } - } + pendingEventsQueue.addPendingTaskEvent(pending); } @Override @@ -1018,50 +1105,36 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { || !mInterceptBackPressedOnRootTasks.contains(task.mTaskId)) { return false; } + final TaskOrganizerPendingEventsQueue pendingEventsQueue = + mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder()) + .mPendingEventsQueue; + if (pendingEventsQueue == null) { + Slog.w(TAG, "cannot get handle BackPressedOnTaskRoot because organizerState is " + + "not present"); + return false; + } PendingTaskEvent pendingVanished = - getPendingTaskEvent(task, PendingTaskEvent.EVENT_VANISHED); + pendingEventsQueue.getPendingTaskEvent(task, + PendingTaskEvent.EVENT_VANISHED); if (pendingVanished != null) { // This task will vanish before this callback so just ignore. return false; } - PendingTaskEvent pending = getPendingTaskEvent( + PendingTaskEvent pending = pendingEventsQueue.getPendingTaskEvent( task, PendingTaskEvent.EVENT_ROOT_BACK_PRESSED); if (pending == null) { pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_ROOT_BACK_PRESSED); } else { // Pending already exist, remove and add for re-ordering. - mPendingTaskEvents.remove(pending); + pendingEventsQueue.removePendingTaskEvent(pending); } - mPendingTaskEvents.add(pending); + pendingEventsQueue.addPendingTaskEvent(pending); mService.mWindowManager.mWindowPlacerLocked.requestTraversal(); return true; } - @Nullable - private PendingTaskEvent getPendingTaskEvent(Task task, int type) { - for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) { - PendingTaskEvent entry = mPendingTaskEvents.get(i); - if (task.mTaskId == entry.mTask.mTaskId && type == entry.mEventType) { - return entry; - } - } - return null; - } - - @VisibleForTesting - @Nullable - PendingTaskEvent getPendingLifecycleTaskEvent(Task task) { - for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) { - PendingTaskEvent entry = mPendingTaskEvents.get(i); - if (task.mTaskId == entry.mTask.mTaskId && entry.isLifecycleEvent()) { - return entry; - } - } - return null; - } - public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.print(prefix); pw.println("TaskOrganizerController:"); @@ -1084,4 +1157,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { TaskOrganizerState getTaskOrganizerState(IBinder taskOrganizer) { return mTaskOrganizerStates.get(taskOrganizer); } + + @VisibleForTesting + TaskOrganizerPendingEventsQueue getTaskOrganizerPendingEvents(IBinder taskOrganizer) { + return mTaskOrganizerStates.get(taskOrganizer).mPendingEventsQueue; + } } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index c27a3d34a02f..ae61f24a7a54 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -673,7 +673,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe SurfaceControl.Transaction inputSinkTransaction = null; for (int i = 0; i < mParticipants.size(); ++i) { final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); - if (ar == null || !ar.isVisible()) continue; + if (ar == null || !ar.isVisible() || ar.getParent() == null) continue; if (inputSinkTransaction == null) { inputSinkTransaction = new SurfaceControl.Transaction(); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 7f1096b5bf35..00ee94af0b1c 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2818,6 +2818,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * snapshot from {@link #getFreezeSnapshotTarget()}. */ void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) { + if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { + // TODO(b/207070762): request shell transition for activityEmbedding change. + return; + } mDisplayContent.prepareAppTransition(TRANSIT_CHANGE); mDisplayContent.mChangingContainers.add(this); // Calculate the relative position in parent container. diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 1d93c8922d21..97dcb7574e3c 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -46,6 +46,7 @@ import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CON import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; +import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -814,7 +815,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } - if (!parent.isAllowedToEmbedActivity(activity)) { + if (parent.isAllowedToEmbedActivity(activity) != EMBEDDING_ALLOWED) { final Throwable exception = new SecurityException( "The task fragment is not trusted to embed the given activity."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); @@ -1057,7 +1058,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } /** A helper method to send minimum dimension violation error to the client. */ - void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions, + private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions, IBinder errorCallbackToken, String reason) { if (taskFragment == null || taskFragment.getTaskFragmentOrganizer() == null) { return; @@ -1672,7 +1673,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // We are reparenting activities to a new embedded TaskFragment, this operation is only // allowed if the new parent is trusted by all reparent activities. final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity -> - !newParentTF.isAllowedToEmbedActivity(activity)); + newParentTF.isAllowedToEmbedActivity(activity) == EMBEDDING_ALLOWED); if (isEmbeddingDisallowed) { final Throwable exception = new SecurityException( "The new parent is not trusted to embed the activities."); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt index 69584e067de4..7ccd6d993f00 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt @@ -70,13 +70,12 @@ class DeletePackageHelperTest { @Test fun deleteSystemPackageFailsIfNotAdminAndNotProfile() { val ps = mPms.mSettings.getPackageLPr("a.data.package") - whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) + whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true) whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0)) whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1) val dph = DeletePackageHelper(mPms) - val result = dph.deletePackageX("a.data.package", 1L, 1, - PackageManager.DELETE_SYSTEM_APP, false) + val result = dph.deletePackageX("a.data.package", 1L, 1, 0, false) assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED) } @@ -86,7 +85,7 @@ class DeletePackageHelperTest { val userId = 1 val parentId = 5 val ps = mPms.mSettings.getPackageLPr("a.data.package") - whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) + whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true) whenever(mUserManagerInternal.getUserInfo(userId)).thenReturn( UserInfo(userId, "test", UserInfo.FLAG_PROFILE)) whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId) @@ -94,8 +93,7 @@ class DeletePackageHelperTest { UserInfo(userId, "testparent", 0)) val dph = DeletePackageHelper(mPms) - val result = dph.deletePackageX("a.data.package", 1L, userId, - PackageManager.DELETE_SYSTEM_APP, false) + val result = dph.deletePackageX("a.data.package", 1L, userId, 0, false) assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED) } @@ -132,4 +130,18 @@ class DeletePackageHelperTest { assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED) } -}
\ No newline at end of file + + @Test + fun deleteSystemPackageSucceedsIfNotAdminButDeleteSystemAppSpecified() { + val ps = mPms.mSettings.getPackageLPr("a.data.package") + whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true) + whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0)) + whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1) + + val dph = DeletePackageHelper(mPms) + val result = dph.deletePackageX("a.data.package", 1L, 1, + PackageManager.DELETE_SYSTEM_APP, false) + + assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED) + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java index 8b314cd88878..c2519caabfee 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java @@ -295,10 +295,10 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_1 = new byte[]{ - 0x01, 0x18, 0x4A, - 0x02, 0x64, 0x5A, - 0x03, 0x4B, 0x00, - 0x04, 0x20, 0x0A}; + 0x0F, 0x18, 0x4A, + 0x17, 0x64, 0x5A, + 0x1F, 0x4B, 0x00, + 0x27, 0x20, 0x0A}; HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1); assertThat(mNativeWrapper.getResultMessages()).contains(expected1); @@ -310,10 +310,10 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_2 = new byte[]{ - 0x05, 0x18, 0x4A, - 0x06, 0x64, 0x5A, - 0x07, 0x4B, 0x00, - 0x08, 0x20, 0x0A}; + 0x2F, 0x18, 0x4A, + 0x37, 0x64, 0x5A, + 0x3F, 0x4B, 0x00, + 0x47, 0x20, 0x0A}; HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2); assertThat(mNativeWrapper.getResultMessages()).contains(expected2); @@ -325,10 +325,10 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_3 = new byte[]{ - 0x09, 0x18, 0x4A, - 0x0A, 0x64, 0x5A, - 0x0B, 0x4B, 0x00, - 0x0C, 0x20, 0x0A}; + 0x4F, 0x18, 0x4A, + 0x57, 0x64, 0x5A, + 0x5F, 0x4B, 0x00, + 0x67, 0x20, 0x0A}; HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3); assertThat(mNativeWrapper.getResultMessages()).contains(expected3); @@ -340,9 +340,9 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_4 = new byte[]{ - 0x0D, 0x18, 0x4A, - 0x0E, 0x64, 0x5A, - 0x0F, 0x4B, 0x00}; + 0x6F, 0x18, 0x4A, + 0x77, 0x64, 0x5A, + 0x7F, 0x4B, 0x00}; HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4); assertThat(mNativeWrapper.getResultMessages()).contains(expected4); @@ -371,9 +371,9 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_1 = new byte[]{ - 0x01, 0x18, 0x4A, - 0x03, 0x4B, 0x00, - 0x04, 0x20, 0x0A}; + 0x0F, 0x18, 0x4A, + 0x1F, 0x4B, 0x00, + 0x27, 0x20, 0x0A}; HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1); assertThat(mNativeWrapper.getResultMessages()).contains(expected1); @@ -385,7 +385,7 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_2 = new byte[]{ - 0x08, 0x20, 0x0A}; + 0x2F, 0x20, 0x0A}; HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2); assertThat(mNativeWrapper.getResultMessages()).contains(expected2); @@ -397,10 +397,10 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_3 = new byte[]{ - 0x09, 0x18, 0x4A, - 0x0A, 0x64, 0x5A, - 0x0B, 0x4B, 0x00, - 0x0C, 0x20, 0x0A}; + 0x4F, 0x18, 0x4A, + 0x57, 0x64, 0x5A, + 0x5F, 0x4B, 0x00, + 0x67, 0x20, 0x0A}; HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3); assertThat(mNativeWrapper.getResultMessages()).contains(expected3); @@ -412,7 +412,7 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_4 = new byte[]{ - 0x0F, 0x4B, 0x00}; + 0x7F, 0x4B, 0x00}; HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4); assertThat(mNativeWrapper.getResultMessages()).contains(expected4); @@ -440,11 +440,12 @@ public class RequestSadActionTest { HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor( mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray()); + // Negative values require explicit casting byte[] sadsToRespond_1 = new byte[]{ - 0x20, 0x18, 0x4A, - 0x21, 0x64, 0x5A, - 0x22, 0x4B, 0x00, - 0x23, 0x20, 0x0A}; + (byte) 0x80, 0x18, 0x4A, + (byte) 0x82, 0x64, 0x5A, + (byte) 0x87, 0x4B, 0x00, + (byte) 0x8F, 0x20, 0x0A}; HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1); assertThat(mNativeWrapper.getResultMessages()).contains(expected1); @@ -456,10 +457,10 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_2 = new byte[]{ - 0x24, 0x18, 0x4A, - 0x25, 0x64, 0x5A, - 0x26, 0x4B, 0x00, - 0x27, 0x20, 0x0A}; + (byte) 0x92, 0x18, 0x4A, + (byte) 0x98, 0x64, 0x5A, + (byte) 0xA1, 0x4B, 0x00, + (byte) 0xA4, 0x20, 0x0A}; HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2); assertThat(mNativeWrapper.getResultMessages()).contains(expected2); @@ -471,10 +472,10 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_3 = new byte[]{ - 0x28, 0x18, 0x4A, - 0x29, 0x64, 0x5A, - 0x2A, 0x4B, 0x00, - 0x2B, 0x20, 0x0A}; + (byte) 0xB3, 0x18, 0x4A, + (byte) 0xBA, 0x64, 0x5A, + (byte) 0xCB, 0x4B, 0x00, + (byte) 0xCF, 0x20, 0x0A}; HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3); assertThat(mNativeWrapper.getResultMessages()).contains(expected3); @@ -486,9 +487,9 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_4 = new byte[]{ - 0x2C, 0x18, 0x4A, - 0x2D, 0x64, 0x5A, - 0x2E, 0x4B, 0x00}; + (byte) 0xF0, 0x18, 0x4A, + (byte) 0xF1, 0x64, 0x5A, + (byte) 0xF3, 0x4B, 0x00}; HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4); assertThat(mNativeWrapper.getResultMessages()).contains(expected4); @@ -510,10 +511,10 @@ public class RequestSadActionTest { mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM, CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray()); byte[] sadsToRespond_1 = new byte[]{ - 0x01, 0x18, - 0x02, 0x64, 0x5A, - 0x03, 0x4B, 0x00, - 0x04, 0x20, 0x0A}; + 0x0F, 0x18, + 0x17, 0x64, 0x5A, + 0x1F, 0x4B, 0x00, + 0x27, 0x20, 0x0A}; HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1); assertThat(mNativeWrapper.getResultMessages()).contains(expected1); @@ -580,10 +581,10 @@ public class RequestSadActionTest { new int[]{Constants.AUDIO_CODEC_LPCM, Constants.AUDIO_CODEC_DD, Constants.AUDIO_CODEC_MP3, Constants.AUDIO_CODEC_MPEG2}); byte[] sadsToRespond_1 = new byte[]{ - 0x01, 0x18, 0x4A, - 0x02, 0x64, 0x5A, - 0x04, 0x20, 0x0A, - 0x05, 0x18, 0x4A}; + 0x0F, 0x18, 0x4A, + 0x17, 0x64, 0x5A, + 0x27, 0x20, 0x0A, + 0x2F, 0x18, 0x4A}; HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1); assertThat(mNativeWrapper.getResultMessages()).contains(expected1); @@ -596,10 +597,10 @@ public class RequestSadActionTest { new int[]{Constants.AUDIO_CODEC_AAC, Constants.AUDIO_CODEC_DTS, Constants.AUDIO_CODEC_ATRAC, Constants.AUDIO_CODEC_DDP}); byte[] sadsToRespond_2 = new byte[]{ - 0x06, 0x64, 0x5A, - 0x07, 0x4B, 0x00, - 0x08, 0x20, 0x0A, - 0x09, 0x18, 0x4A}; + 0x37, 0x64, 0x5A, + 0x3F, 0x4B, 0x00, + 0x47, 0x20, 0x0A, + 0x4F, 0x18, 0x4A}; HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2); assertThat(mNativeWrapper.getResultMessages()).contains(expected2); @@ -612,10 +613,10 @@ public class RequestSadActionTest { new int[]{Constants.AUDIO_CODEC_DTSHD, Constants.AUDIO_CODEC_TRUEHD, Constants.AUDIO_CODEC_DST, Constants.AUDIO_CODEC_MAX}); byte[] sadsToRespond_3 = new byte[]{ - 0x0B, 0x4B, 0x00, - 0x0C, 0x20, 0x0A, - 0x0D, 0x18, 0x4A, - 0x0F, 0x4B, 0x00}; + 0x5F, 0x4B, 0x00, + 0x67, 0x20, 0x0A, + 0x6F, 0x18, 0x4A, + 0x7F, 0x4B, 0x00}; HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor( Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3); assertThat(mNativeWrapper.getResultMessages()).contains(expected3); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 5be1ecef0360..fe3c26a3b409 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -37,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; +import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; @@ -52,6 +53,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.ActivityStarter.canEmbedActivity; +import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; +import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; +import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; +import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -59,6 +65,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -87,6 +94,7 @@ import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.service.voice.IVoiceInteractionSession; import android.util.Pair; +import android.util.Size; import android.view.Gravity; import android.window.TaskFragmentOrganizerToken; @@ -1171,6 +1179,7 @@ public class ActivityStarterTests extends WindowTestsBase { null /* inTask */, taskFragment); assertFalse(taskFragment.hasChild()); + assertNotNull("Target record must be started on Task.", targetRecord.getParent().asTask()); } @Test @@ -1341,6 +1350,58 @@ public class ActivityStarterTests extends WindowTestsBase { any()); } + @Test + public void testCanEmbedActivity() { + final Size minDimensions = new Size(1000, 1000); + final WindowLayout windowLayout = new WindowLayout(0, 0, 0, 0, 0, + minDimensions.getWidth(), minDimensions.getHeight()); + final ActivityRecord starting = new ActivityBuilder(mAtm) + .setUid(UNIMPORTANT_UID) + .setWindowLayout(windowLayout) + .build(); + + // Task fragment hasn't attached to a task yet. Start activity to a new task. + TaskFragment taskFragment = new TaskFragmentBuilder(mAtm).build(); + final Task task = new TaskBuilder(mSupervisor).build(); + + assertEquals(EMBEDDING_DISALLOWED_NEW_TASK, + canEmbedActivity(taskFragment, starting, task)); + + // Starting activity is going to be started on a task different from task fragment's parent + // task. Start activity to a new task. + task.addChild(taskFragment, POSITION_TOP); + final Task newTask = new TaskBuilder(mSupervisor).build(); + + assertEquals(EMBEDDING_DISALLOWED_NEW_TASK, + canEmbedActivity(taskFragment, starting, newTask)); + + // Make task fragment bounds exceed task bounds. + final Rect taskBounds = task.getBounds(); + taskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.right + 1, + taskBounds.bottom + 1); + + assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST, + canEmbedActivity(taskFragment, starting, task)); + + taskFragment.setBounds(taskBounds); + starting.info.flags |= FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; + + assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task)); + + starting.info.flags &= ~FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; + // Set task fragment's uid as the same as starting activity's uid. + taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class), + UNIMPORTANT_UID, "test"); + + assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task)); + + // Make task fragment bounds smaller than starting activity's minimum dimensions + taskFragment.setBounds(0, 0, minDimensions.getWidth() - 1, minDimensions.getHeight() - 1); + + assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION, + canEmbedActivity(taskFragment, starting, task)); + } + private static void startActivityInner(ActivityStarter starter, ActivityRecord target, ActivityRecord source, ActivityOptions options, Task inTask, TaskFragment inTaskFragment) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 1c3b869e02d0..e47bcc98b908 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.testing.Assert.assertThrows; @@ -530,7 +531,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mWindowOrganizerController.mLaunchTaskFragments .put(mFragmentToken, mTaskFragment); mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token); - doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity); + doReturn(EMBEDDING_ALLOWED).when(mTaskFragment).isAllowedToEmbedActivity(activity); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); @@ -920,7 +921,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .setOrganizer(mOrganizer) .setBounds(mTaskFragBounds) .build(); - doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity); mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); // Reparent activity to mTaskFragment, which is smaller than activity's @@ -956,7 +956,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .setOrganizer(mOrganizer) .setBounds(mTaskFragBounds) .build(); - doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity); mWindowOrganizerController.mLaunchTaskFragments.put(oldFragToken, oldTaskFrag); mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index c0759c110039..22101c268a9c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -196,6 +196,28 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test + public void testUsesOptionsDisplayAreaFeatureIdIfSet() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + final TestDisplayContent fullscreenDisplay = createNewDisplayContent( + WINDOWING_MODE_FULLSCREEN); + + mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea(); + ActivityRecord source = createSourceActivity(freeformDisplay); + + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(fullscreenDisplay.mDisplayId); + options.setLaunchTaskDisplayAreaFeatureId( + fullscreenDisplay.getDefaultTaskDisplayArea().mFeatureId); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setSource(source).setOptions(options).calculate()); + + assertEquals(fullscreenDisplay.getDefaultTaskDisplayArea(), + mResult.mPreferredTaskDisplayArea); + } + + @Test public void testUsesSourcesDisplayAreaIdPriorToTaskIfSet() { final TestDisplayContent freeformDisplay = createNewDisplayContent( WINDOWING_MODE_FREEFORM); @@ -453,7 +475,7 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test - public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayArea() { + public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayAreaToken() { final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay, mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN, @@ -475,6 +497,52 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test + public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayAreaFeatureId() { + final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay, + mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); + final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); + launchRoot.mCreatedByOrganizer = true; + + secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FULLSCREEN }, + new int[] { ACTIVITY_TYPE_STANDARD }); + + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchTaskDisplayAreaFeatureId( + mDefaultDisplay.getDefaultTaskDisplayArea().mFeatureId); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setOptions(options).calculate()); + + assertEquals( + mDefaultDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea); + } + + @Test + public void testUsesOptionsDisplayAreaFeatureIdDisplayIdNotSet() { + final TestDisplayContent secondaryDisplay = createNewDisplayContent( + WINDOWING_MODE_FULLSCREEN); + final TaskDisplayArea tdaOnSecondaryDisplay = createTaskDisplayArea(secondaryDisplay, + mWm, "TestTaskDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); + + final TaskDisplayArea tdaOnDefaultDisplay = createTaskDisplayArea(mDefaultDisplay, + mWm, "TestTaskDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); + + mCurrent.mPreferredTaskDisplayArea = tdaOnSecondaryDisplay; + ActivityRecord source = createSourceActivity(tdaOnSecondaryDisplay, + WINDOWING_MODE_FULLSCREEN); + + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchTaskDisplayAreaFeatureId(tdaOnSecondaryDisplay.mFeatureId); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setSource(source).setOptions(options).calculate()); + // Display id wasn't specified in ActivityOptions - the activity should be placed on the + // default display, into the TaskDisplayArea with the same feature id. + assertEquals(tdaOnDefaultDisplay, mResult.mPreferredTaskDisplayArea); + } + + @Test public void testRecalculateFreeformInitialBoundsWithOverrideDisplayArea() { final TestDisplayContent freeformDisplay = createNewDisplayContent( WINDOWING_MODE_FREEFORM); @@ -1822,6 +1890,13 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { return new ActivityBuilder(mAtm).setTask(rootTask).build(); } + private ActivityRecord createSourceActivity(TaskDisplayArea taskDisplayArea, + int windowingMode) { + final Task rootTask = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD, + true); + return new ActivityBuilder(mAtm).setTask(rootTask).build(); + } + private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) { final Task rootTask = display.getDefaultTaskDisplayArea() .createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 08bad70a1411..9c2aac066084 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -223,7 +223,7 @@ public class WindowOrganizerTests extends WindowTestsBase { mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); - assertTaskVanished(organizer, false /* expectVanished */, rootTask); + verify(organizer, times(0)).onTaskVanished(any()); assertFalse(rootTask.isOrganized()); } @@ -297,7 +297,7 @@ public class WindowOrganizerTests extends WindowTestsBase { // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); - assertTaskVanished(organizer, true /* expectVanished */, rootTask); + verify(organizer, times(0)).onTaskVanished(any()); assertFalse(rootTask.isOrganized()); } @@ -341,7 +341,7 @@ public class WindowOrganizerTests extends WindowTestsBase { mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, times(3)) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3); + verify(organizer2, times(0)).onTaskVanished(any()); } @Test @@ -395,6 +395,7 @@ public class WindowOrganizerTests extends WindowTestsBase { assertFalse(task2.isAttached()); // Normal task should keep. assertTrue(task.isAttached()); + verify(organizer2, times(0)).onTaskVanished(any()); } @Test @@ -439,7 +440,7 @@ public class WindowOrganizerTests extends WindowTestsBase { mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, times(3)) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3); + verify(organizer2, times(0)).onTaskVanished(any()); } @Test @@ -1264,7 +1265,7 @@ public class WindowOrganizerTests extends WindowTestsBase { record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription")); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_APPEARED, pendingEvents.get(0).mEventType); assertEquals("TestDescription", @@ -1284,7 +1285,7 @@ public class WindowOrganizerTests extends WindowTestsBase { rootTask.removeImmediately(); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask); assertEquals(0, pendingEvents.size()); } @@ -1302,7 +1303,7 @@ public class WindowOrganizerTests extends WindowTestsBase { record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription")); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType); assertEquals("TestDescription", @@ -1311,7 +1312,7 @@ public class WindowOrganizerTests extends WindowTestsBase { record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription2")); waitUntilHandlersIdle(); - pendingEvents = getTaskPendingEvent(rootTask); + pendingEvents = getTaskPendingEvent(organizer, rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType); assertEquals("TestDescription2", @@ -1334,7 +1335,7 @@ public class WindowOrganizerTests extends WindowTestsBase { rootTask.removeImmediately(); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType); assertEquals("TestDescription", @@ -1355,7 +1356,7 @@ public class WindowOrganizerTests extends WindowTestsBase { rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType); } @@ -1375,14 +1376,16 @@ public class WindowOrganizerTests extends WindowTestsBase { new IRequestFinishCallback.Default()); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType); } - private ArrayList<PendingTaskEvent> getTaskPendingEvent(Task task) { + private ArrayList<PendingTaskEvent> getTaskPendingEvent(ITaskOrganizer organizer, Task task) { ArrayList<PendingTaskEvent> total = - mWm.mAtmService.mTaskOrganizerController.getPendingEventList(); + mWm.mAtmService.mTaskOrganizerController + .getTaskOrganizerPendingEvents(organizer.asBinder()) + .getPendingEventList(); ArrayList<PendingTaskEvent> result = new ArrayList(); for (int i = 0; i < total.size(); i++) { |