Adds userId to onTopActivityChanged callback in VirtualDeviceManager

Adding the userId will allow onTopActivityChanged to be associated
with a userId when the top activity changes in a stream via using
the VirtualDeviceManager and allows the API user to know which
profile is active. While the userId can be found through other
means, the indirect path can cause a race condition with poor user
experience.

Bug: 264474826
Test: atest CtsVirtualDevicesTestCases
Change-Id: Ib00e1589a2c7b545d499d65e1d7ff9c12f0b4654
This commit is contained in:
Shaun Corkran
2023-01-18 22:05:30 +00:00
parent 1c1503180e
commit d849e0006f
8 changed files with 62 additions and 16 deletions

View File

@@ -3004,7 +3004,8 @@ package android.companion.virtual {
public static interface VirtualDeviceManager.ActivityListener {
method public void onDisplayEmpty(int);
method public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int);
}
public static interface VirtualDeviceManager.IntentInterceptorCallback {

View File

@@ -30,8 +30,9 @@ oneway interface IVirtualDeviceActivityListener {
*
* @param displayId The display ID on which the activity change happened.
* @param topActivity The component name of the top activity.
* @param userId The user ID associated with the top activity.
*/
void onTopActivityChanged(int displayId, in ComponentName topActivity);
void onTopActivityChanged(int displayId, in ComponentName topActivity, in int userId);
/**
* Called when the display becomes empty (e.g. if the user hits back on the last

View File

@@ -27,6 +27,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.virtual.audio.VirtualAudioDevice;
@@ -378,13 +379,16 @@ public final class VirtualDeviceManager {
new IVirtualDeviceActivityListener.Stub() {
@Override
public void onTopActivityChanged(int displayId, ComponentName topActivity) {
public void onTopActivityChanged(int displayId, ComponentName topActivity,
@UserIdInt int userId) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mActivityListenersLock) {
for (int i = 0; i < mActivityListeners.size(); i++) {
mActivityListeners.valueAt(i)
.onTopActivityChanged(displayId, topActivity);
mActivityListeners.valueAt(i)
.onTopActivityChanged(displayId, topActivity, userId);
}
}
} finally {
@@ -1087,9 +1091,24 @@ public final class VirtualDeviceManager {
*
* @param displayId The display ID on which the activity change happened.
* @param topActivity The component name of the top activity.
* @deprecated Use {@link #onTopActivityChanged(int, ComponentName, int)} instead
*/
void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity);
/**
* Called when the top activity is changed.
*
* <p>Note: When there are no activities running on the virtual display, the
* {@link #onDisplayEmpty(int)} will be called. If the value topActivity is cached, it
* should be cleared when {@link #onDisplayEmpty(int)} is called.
*
* @param displayId The display ID on which the activity change happened.
* @param topActivity The component name of the top activity.
* @param userId The user ID associated with the top activity.
*/
default void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
@UserIdInt int userId) {}
/**
* Called when the display becomes empty (e.g. if the user hits back on the last
* activity of the root task).
@@ -1115,6 +1134,12 @@ public final class VirtualDeviceManager {
mExecutor.execute(() -> mActivityListener.onTopActivityChanged(displayId, topActivity));
}
public void onTopActivityChanged(int displayId, ComponentName topActivity,
@UserIdInt int userId) {
mExecutor.execute(() ->
mActivityListener.onTopActivityChanged(displayId, topActivity, userId));
}
public void onDisplayEmpty(int displayId) {
mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId));
}

View File

@@ -19,6 +19,7 @@ package android.window;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Intent;
@@ -137,7 +138,7 @@ public abstract class DisplayWindowPolicyController {
/**
* This is called when the top activity of the display is changed.
*/
public void onTopActivityChanged(ComponentName topActivity, int uid) {}
public void onTopActivityChanged(ComponentName topActivity, int uid, @UserIdInt int userId) {}
/**
* This is called when the apps that contains running activities on the display has changed.

View File

@@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
@@ -302,14 +303,14 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
}
@Override
public void onTopActivityChanged(ComponentName topActivity, int uid) {
public void onTopActivityChanged(ComponentName topActivity, int uid, @UserIdInt int userId) {
// Don't send onTopActivityChanged() callback when topActivity is null because it's defined
// as @NonNull in ActivityListener interface. Sends onDisplayEmpty() callback instead when
// there is no activity running on virtual display.
if (mActivityListener != null && topActivity != null) {
// Post callback on the main thread so it doesn't block activity launching
mHandler.post(() ->
mActivityListener.onTopActivityChanged(mDisplayId, topActivity));
mActivityListener.onTopActivityChanged(mDisplayId, topActivity, userId));
}
}

View File

@@ -26,6 +26,7 @@ import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.PendingIntent;
@@ -138,7 +139,18 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override
public void onTopActivityChanged(int displayId, ComponentName topActivity) {
try {
mActivityListener.onTopActivityChanged(displayId, topActivity);
mActivityListener.onTopActivityChanged(displayId, topActivity,
UserHandle.USER_NULL);
} catch (RemoteException e) {
Slog.w(TAG, "Unable to call mActivityListener", e);
}
}
@Override
public void onTopActivityChanged(int displayId, ComponentName topActivity,
@UserIdInt int userId) {
try {
mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
} catch (RemoteException e) {
Slog.w(TAG, "Unable to call mActivityListener", e);
}

View File

@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Process;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
@@ -138,10 +139,14 @@ class DisplayWindowPolicyControllerHelper {
true /* includeOverlays */);
if (topActivity != mTopRunningActivity) {
mTopRunningActivity = topActivity;
mDisplayWindowPolicyController.onTopActivityChanged(
topActivity == null ? null : topActivity.info.getComponentName(),
topActivity == null
? UserHandle.USER_NULL : topActivity.info.applicationInfo.uid);
if (topActivity == null) {
mDisplayWindowPolicyController.onTopActivityChanged(null, Process.INVALID_UID,
UserHandle.USER_NULL);
} else {
mDisplayWindowPolicyController.onTopActivityChanged(
topActivity.info.getComponentName(), topActivity.info.applicationInfo.uid,
topActivity.mUserId);
}
}
// Update running uid.

View File

@@ -33,7 +33,7 @@ import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.UserHandle;
import android.os.Process;
import android.util.ArraySet;
import android.view.Display;
import android.window.DisplayWindowPolicyController;
@@ -101,7 +101,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase {
int uidAmount = (expectedUid0 && expectedUid1) ? 2 : (expectedUid0 || expectedUid1) ? 1 : 0;
assertEquals(expectedTopActivity == null ? null :
expectedTopActivity.info.getComponentName(), mDwpc.mTopActivity);
assertEquals(expectedTopActivity == null ? UserHandle.USER_NULL :
assertEquals(expectedTopActivity == null ? Process.INVALID_UID :
expectedTopActivity.info.applicationInfo.uid, mDwpc.mTopActivityUid);
assertEquals(uidAmount, mDwpc.mRunningUids.size());
assertTrue(mDwpc.mRunningUids.contains(TEST_USER_0_ID) == expectedUid0);
@@ -224,7 +224,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase {
new ComponentName("fake.package", "DisallowedActivity");
ComponentName mTopActivity = null;
int mTopActivityUid = UserHandle.USER_NULL;
int mTopActivityUid = Process.INVALID_UID;
ArraySet<Integer> mRunningUids = new ArraySet<>();
@Override
@@ -254,8 +254,8 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase {
}
@Override
public void onTopActivityChanged(ComponentName topActivity, int uid) {
super.onTopActivityChanged(topActivity, uid);
public void onTopActivityChanged(ComponentName topActivity, int uid, int userId) {
super.onTopActivityChanged(topActivity, uid, userId);
mTopActivity = topActivity;
mTopActivityUid = uid;
}