summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt21
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/ActivityOptions.java32
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java23
-rw-r--r--core/java/android/companion/CompanionDeviceService.java31
-rw-r--r--core/java/android/companion/CompanionException.java5
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl2
-rw-r--r--core/java/android/companion/ICompanionDeviceService.aidl1
-rw-r--r--core/java/android/hardware/camera2/params/Face.java279
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java63
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java38
-rw-r--r--core/java/android/view/ViewRootImpl.java16
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java57
-rw-r--r--core/java/com/android/internal/inputmethod/IInputMethod.aidl25
-rw-r--r--core/tests/companiontests/src/android/companion/SystemDataTransportTest.java180
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt129
-rw-r--r--packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java18
-rw-r--r--services/accessibility/OWNERS4
-rw-r--r--services/companion/Android.bp4
-rw-r--r--services/companion/java/com/android/server/companion/CompanionApplicationController.java23
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java100
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java6
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java36
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java50
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java229
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java172
-rw-r--r--services/companion/java/com/android/server/companion/proto/companion_message.proto57
-rw-r--r--services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java121
-rw-r--r--services/companion/java/com/android/server/companion/securechannel/OWNERS4
-rw-r--r--services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java321
-rw-r--r--services/core/java/com/android/server/VpnManagerService.java44
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java11
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java30
-rw-r--r--services/core/java/com/android/server/audio/RotationHelper.java31
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java16
-rw-r--r--services/core/java/com/android/server/hdmi/Constants.java2
-rw-r--r--services/core/java/com/android/server/hdmi/RequestSadAction.java10
-rw-r--r--services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java27
-rw-r--r--services/core/java/com/android/server/pm/DeletePackageHelper.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java105
-rw-r--r--services/core/java/com/android/server/wm/SafeActivityOptions.java21
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java67
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java3
-rw-r--r--services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java21
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java362
-rw-r--r--services/core/java/com/android/server/wm/Transition.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt26
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java111
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java77
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java29
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++) {