Loading core/java/android/app/ActivityThread.java +63 −2 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL; import android.annotation.NonNull; Loading Loading @@ -116,6 +117,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.HardwareRenderer; import android.graphics.Typeface; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.media.MediaFrameworkInitializer; import android.media.MediaFrameworkPlatformInitializer; Loading Loading @@ -863,7 +865,8 @@ public final class ActivityThread extends ClientTransactionHandler } } static final class ReceiverData extends BroadcastReceiver.PendingResult { @VisibleForTesting(visibility = PACKAGE) public static final class ReceiverData extends BroadcastReceiver.PendingResult { public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras, boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token, int sendingUser, int sendingUid, String sendingPackage) { Loading @@ -871,6 +874,11 @@ public final class ActivityThread extends ClientTransactionHandler assumeDelivered, token, sendingUser, intent.getFlags(), sendingUid, sendingPackage); this.intent = intent; if (com.android.window.flags.Flags.supportWidgetIntentsOnConnectedDisplay()) { mOptions = ActivityOptions.fromBundle(resultExtras); } else { mOptions = null; } } @UnsupportedAppUsage Loading @@ -879,12 +887,16 @@ public final class ActivityThread extends ClientTransactionHandler ActivityInfo info; @UnsupportedAppUsage CompatibilityInfo compatInfo; @Nullable final ActivityOptions mOptions; public String toString() { return "ReceiverData{intent=" + intent + " packageName=" + info.packageName + " resultCode=" + getResultCode() + " resultData=" + getResultData() + " resultExtras=" + getResultExtras(false) + " sentFromUid=" + getSentFromUid() + " sentFromPackage=" + getSentFromPackage() + "}"; + getSentFromUid() + " sentFromPackage=" + getSentFromPackage() + " mOptions=" + mOptions + "}"; } } Loading Loading @@ -4985,6 +4997,7 @@ public final class ActivityThread extends ClientTransactionHandler final String attributionTag = data.info.attributionTags[0]; context = (ContextImpl) context.createAttributionContext(attributionTag); } context = (ContextImpl) createDisplayContextIfNeeded(context, data); java.lang.ClassLoader cl = context.getClassLoader(); data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess( Loading Loading @@ -5033,6 +5046,54 @@ public final class ActivityThread extends ClientTransactionHandler } } /** * Creates a display context if the broadcast was initiated with a launch display ID. * * <p>When a broadcast initiates from a widget on a secondary display, the originating * display ID is included as an extra in the intent. This is accomplished by * {@link PendingIntentRecord#createSafeActivityOptionsBundle}, which transfers the launch * display ID from ActivityOptions into the intent's extras bundle. This method checks for * the presence of that extra and creates a display context associated with the initiated * display if it exists. This ensures that when the {@link BroadcastReceiver} invokes * {@link Context#startActivity(Intent)}, the activity is launched on the correct display. * * @param context The original context of the receiver. * @param data The {@link ReceiverData} containing optional display information. * @return A display context if applicable; otherwise the original context. */ @NonNull @VisibleForTesting(visibility = PRIVATE) public Context createDisplayContextIfNeeded(@NonNull Context context, @NonNull ReceiverData data) { if (!com.android.window.flags.Flags.supportWidgetIntentsOnConnectedDisplay()) { return context; } final ActivityOptions options = data.mOptions; if (options == null) { return context; } final int launchDisplayId = options.getLaunchDisplayId(); if (launchDisplayId == INVALID_DISPLAY) { return context; } final DisplayManager dm = context.getSystemService(DisplayManager.class); if (dm == null) { return context; } final Display display = dm.getDisplay(launchDisplayId); if (display == null) { Slog.w(TAG, "Unable to create a display context for nonexistent display " + launchDisplayId); return context; } return context.createDisplayContext(display); } // Instantiate a BackupAgent and tell it that it's alive private void handleCreateBackupAgent(CreateBackupAgentData data) { if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data); Loading core/java/android/window/flags/windowing_sdk.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -152,3 +152,14 @@ flag { purpose: PURPOSE_BUGFIX } } flag { namespace: "windowing_sdk" name: "support_widget_intents_on_connected_display" description: "Launch widget intents on originating display" bug: "358368849" is_fixed_read_only: true metadata { purpose: PURPOSE_BUGFIX } } core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +100 −4 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.android.window.flags.Flags.FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; Loading @@ -39,11 +41,15 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.ActivityThread.ActivityClientRecord; import android.app.ActivityThread.ReceiverData; import android.app.Application; import android.app.IApplicationThread; import android.app.PictureInPictureParams; Loading Loading @@ -158,10 +164,7 @@ public class ActivityThreadTest { @After public void tearDown() { if (mCreatedVirtualDisplays != null) { mCreatedVirtualDisplays.forEach(VirtualDisplay::release); mCreatedVirtualDisplays = null; } tearDownVirtualDisplays(); WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); ClientTransactionListenerController.getInstance() .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); Loading Loading @@ -1007,6 +1010,92 @@ public class ActivityThreadTest { .that(systemContext.getApplicationInfo()).isSameInstanceAs(newAppInfo); } @Test @RequiresFlagsEnabled(FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY) public void tesScheduleReceiver_withLaunchDisplayId_receivesDisplayContext() { final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final Display virtualDisplay = createVirtualDisplay(context, 100 /* w */, 100 /* h */); final int virtualDisplayId = virtualDisplay.getDisplayId(); final ActivityOptions activityOptions = ActivityOptions.makeBasic().setLaunchDisplayId(virtualDisplayId); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final ReceiverData data = createReceiverData(activityOptions.toBundle()); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); final Display resultDisplay = resultContext.getDisplayNoVerify(); assertThat(resultDisplay).isNotNull(); assertThat(resultDisplay.getDisplayId()).isEqualTo(virtualDisplayId); assertThat(resultContext.getAssociatedDisplayId()).isEqualTo(virtualDisplayId); } @Test @RequiresFlagsEnabled(FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY) public void tesScheduleReceiver_withNotExistDisplayId_receivesNoneUiContext() { final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final Display virtualDisplay = createVirtualDisplay(context, 100 /* w */, 100 /* h */); final int virtualDisplayId = virtualDisplay.getDisplayId(); final ActivityOptions activityOptions = ActivityOptions.makeBasic().setLaunchDisplayId(virtualDisplayId); final ActivityThread activityThread = ActivityThread.currentActivityThread(); tearDownVirtualDisplays(); final ReceiverData data = createReceiverData(activityOptions.toBundle()); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); assertThat(resultContext).isEqualTo(context); } @Test @RequiresFlagsEnabled(FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY) public void tesScheduleReceiver_withInvalidDisplay_receivesNoneUiContext() { final Context context = mock(Context.class); final ActivityOptions activityOptions = ActivityOptions.makeBasic().setLaunchDisplayId(INVALID_DISPLAY); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final ReceiverData data = createReceiverData(activityOptions.toBundle()); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); verify(context, never()).createDisplayContext(any()); assertThat(resultContext).isEqualTo(context); } @Test @RequiresFlagsEnabled(FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY) public void tesScheduleReceiver_withoutDisplayManagerService_receivesNoneUiContext() { final Context context = mock(Context.class); when(context.getSystemService(DisplayManager.class)).thenReturn(null); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final ReceiverData data = createReceiverData(null /* resultExtras */); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); verify(context, never()).createDisplayContext(any()); assertThat(resultContext).isEqualTo(context); } @Test public void tesScheduleReceiver_withoutActivityOptions_receivesNoneUiContext() { final Context context = mock(Context.class); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final ReceiverData data = createReceiverData(null /* resultExtras */); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); verify(context, never()).createDisplayContext(any()); assertThat(resultContext).isEqualTo(context); } @NonNull private ReceiverData createReceiverData(@Nullable Bundle resultExtras) { return new ReceiverData(new Intent("test.action.WIDGET_ITEM_CLICK"), 0 /* resultCode */, null /* resultData */, resultExtras, false /* ordered */, false /* sticky */, false /* assumeDelivered */, null /* token */, 0 /* sendingUser */, -1 /* sendingUid */, null /* sendingPackage */); } /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the Loading Loading @@ -1056,6 +1145,13 @@ public class ActivityThreadTest { return virtualDisplay.getDisplay(); } private void tearDownVirtualDisplays() { if (mCreatedVirtualDisplays != null) { mCreatedVirtualDisplays.forEach(VirtualDisplay::release); mCreatedVirtualDisplays = null; } } private static ActivityClientRecord getActivityClientRecord(Activity activity) { final ActivityThread thread = activity.getActivityThread(); final IBinder token = activity.getActivityToken(); Loading services/core/java/com/android/server/am/PendingIntentRecord.java +28 −1 Original line number Diff line number Diff line Loading @@ -668,12 +668,13 @@ public final class PendingIntentRecord extends IIntentSender.Stub { getBackgroundStartPrivilegesForActivitySender( mAllowBgActivityStartsForBroadcastSender, allowlistToken, options, callingUid); final Bundle extras = createSafeActivityOptionsBundle(options); // If a completion callback has been requested, require // that the broadcast be delivered synchronously int sent = controller.mAmInternal.broadcastIntentInPackage(key.packageName, key.featureId, uid, callingUid, callingPid, finalIntent, resolvedType, finishedReceiverThread, finishedReceiver, code, null, null, requiredPermission, options, (finishedReceiver != null), extras, requiredPermission, options, (finishedReceiver != null), false, userId, backgroundStartPrivileges, null /* broadcastAllowList */); if (sent == ActivityManager.BROADCAST_SUCCESS) { Loading Loading @@ -716,6 +717,32 @@ public final class PendingIntentRecord extends IIntentSender.Stub { return res; } /** * Creates a safe ActivityOptions bundle with only the launchDisplayId set. * * <p>This prevents unintended data from being sent to the app process. The resulting bundle * is then used by {@link ActivityThread#createDisplayContextIfNeeded} to create a display * context for the {@link BroadcastReceiver}, ensuring that activities launched from the * receiver's context are started on the correct display. * * @param optionsBundle The original ActivityOptions bundle. * @return A new bundle containing only the launchDisplayId from the original options, or null * if the original bundle is null. */ @Nullable private Bundle createSafeActivityOptionsBundle(@Nullable Bundle optionsBundle) { if (!com.android.window.flags.Flags.supportWidgetIntentsOnConnectedDisplay()) { return null; } if (optionsBundle == null) { return null; } final ActivityOptions options = ActivityOptions.fromBundle(optionsBundle); return ActivityOptions.makeBasic() .setLaunchDisplayId(options.getLaunchDisplayId()) .toBundle(); } @VisibleForTesting BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( IBinder allowlistToken) { return mAllowBgActivityStartsForActivitySender.contains(allowlistToken) Loading services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +62 −2 Original line number Diff line number Diff line Loading @@ -34,25 +34,32 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.cancelReasonToString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.BackgroundStartPrivileges; import android.app.PendingIntent; import android.content.Intent; import android.content.pm.IPackageManager; import android.os.Binder; import android.os.Bundle; import android.os.Looper; import android.os.UserHandle; import androidx.test.runner.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; Loading Loading @@ -108,7 +115,8 @@ public class PendingIntentControllerTest { mPendingIntentController.onActivityManagerInternalAdded(); } private PendingIntentRecord createPendingIntentRecord(int flags) { @NonNull private PendingIntentRecord createPendingIntentRecord(@PendingIntent.Flags int flags) { return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST, TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, TEST_USER_ID, null, null, 0, TEST_INTENTS, null, flags, null); Loading Loading @@ -219,6 +227,58 @@ public class PendingIntentControllerTest { allowlistDurationLocked.type); } @Test public void testSendWithBundleExtras() { final PendingIntentRecord pir = createPendingIntentRecord(0); final ActivityOptions activityOptions = ActivityOptions.makeBasic(); activityOptions.setLaunchDisplayId(2); activityOptions.setLaunchTaskId(123); final Bundle options = activityOptions.toBundle(); options.putString("testKey", "testValue"); pir.send(0, null, null, null, null, null, options); final ArgumentCaptor<Bundle> resultExtrasCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mActivityManagerInternal).broadcastIntentInPackage( eq(TEST_PACKAGE_NAME), eq(TEST_FEATURE_ID), eq(TEST_CALLING_UID), eq(TEST_CALLING_UID), // realCallingUid anyInt(), // realCallingPid any(), // intent any(), // resolvedType any(), // resultToThread any(), // resultTo anyInt(), // resultCode any(), // resultData resultExtrasCaptor.capture(), // resultExtras any(), // requiredPermission eq(options), // bOptions anyBoolean(), // serialized anyBoolean(), // sticky anyInt(), // userId any(), // backgroundStartPrivileges any() // broadcastAllowList ); final Bundle result = resultExtrasCaptor.getValue(); if (com.android.window.flags.Flags.supportWidgetIntentsOnConnectedDisplay()) { // Check that only launchDisplayId in ActivityOptions is passed via resultExtras. final ActivityOptions expected = ActivityOptions.makeBasic().setLaunchDisplayId(2); assertBundleEquals(expected.toBundle(), result); // Check that launchTaskId is dropped in resultExtras. assertNotEquals(123, ActivityOptions.fromBundle(result).getLaunchTaskId()); } else { assertNull(result); } } private void assertBundleEquals(@NonNull Bundle expected, @NonNull Bundle observed) { assertEquals(expected.size(), observed.size()); for (String key : expected.keySet()) { assertEquals(expected.get(key), observed.get(key)); } } private void assertCancelReason(int expectedReason, int actualReason) { final String errMsg = "Expected: " + cancelReasonToString(expectedReason) + "; Actual: " + cancelReasonToString(actualReason); Loading Loading
core/java/android/app/ActivityThread.java +63 −2 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL; import android.annotation.NonNull; Loading Loading @@ -116,6 +117,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.HardwareRenderer; import android.graphics.Typeface; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.media.MediaFrameworkInitializer; import android.media.MediaFrameworkPlatformInitializer; Loading Loading @@ -863,7 +865,8 @@ public final class ActivityThread extends ClientTransactionHandler } } static final class ReceiverData extends BroadcastReceiver.PendingResult { @VisibleForTesting(visibility = PACKAGE) public static final class ReceiverData extends BroadcastReceiver.PendingResult { public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras, boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token, int sendingUser, int sendingUid, String sendingPackage) { Loading @@ -871,6 +874,11 @@ public final class ActivityThread extends ClientTransactionHandler assumeDelivered, token, sendingUser, intent.getFlags(), sendingUid, sendingPackage); this.intent = intent; if (com.android.window.flags.Flags.supportWidgetIntentsOnConnectedDisplay()) { mOptions = ActivityOptions.fromBundle(resultExtras); } else { mOptions = null; } } @UnsupportedAppUsage Loading @@ -879,12 +887,16 @@ public final class ActivityThread extends ClientTransactionHandler ActivityInfo info; @UnsupportedAppUsage CompatibilityInfo compatInfo; @Nullable final ActivityOptions mOptions; public String toString() { return "ReceiverData{intent=" + intent + " packageName=" + info.packageName + " resultCode=" + getResultCode() + " resultData=" + getResultData() + " resultExtras=" + getResultExtras(false) + " sentFromUid=" + getSentFromUid() + " sentFromPackage=" + getSentFromPackage() + "}"; + getSentFromUid() + " sentFromPackage=" + getSentFromPackage() + " mOptions=" + mOptions + "}"; } } Loading Loading @@ -4985,6 +4997,7 @@ public final class ActivityThread extends ClientTransactionHandler final String attributionTag = data.info.attributionTags[0]; context = (ContextImpl) context.createAttributionContext(attributionTag); } context = (ContextImpl) createDisplayContextIfNeeded(context, data); java.lang.ClassLoader cl = context.getClassLoader(); data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess( Loading Loading @@ -5033,6 +5046,54 @@ public final class ActivityThread extends ClientTransactionHandler } } /** * Creates a display context if the broadcast was initiated with a launch display ID. * * <p>When a broadcast initiates from a widget on a secondary display, the originating * display ID is included as an extra in the intent. This is accomplished by * {@link PendingIntentRecord#createSafeActivityOptionsBundle}, which transfers the launch * display ID from ActivityOptions into the intent's extras bundle. This method checks for * the presence of that extra and creates a display context associated with the initiated * display if it exists. This ensures that when the {@link BroadcastReceiver} invokes * {@link Context#startActivity(Intent)}, the activity is launched on the correct display. * * @param context The original context of the receiver. * @param data The {@link ReceiverData} containing optional display information. * @return A display context if applicable; otherwise the original context. */ @NonNull @VisibleForTesting(visibility = PRIVATE) public Context createDisplayContextIfNeeded(@NonNull Context context, @NonNull ReceiverData data) { if (!com.android.window.flags.Flags.supportWidgetIntentsOnConnectedDisplay()) { return context; } final ActivityOptions options = data.mOptions; if (options == null) { return context; } final int launchDisplayId = options.getLaunchDisplayId(); if (launchDisplayId == INVALID_DISPLAY) { return context; } final DisplayManager dm = context.getSystemService(DisplayManager.class); if (dm == null) { return context; } final Display display = dm.getDisplay(launchDisplayId); if (display == null) { Slog.w(TAG, "Unable to create a display context for nonexistent display " + launchDisplayId); return context; } return context.createDisplayContext(display); } // Instantiate a BackupAgent and tell it that it's alive private void handleCreateBackupAgent(CreateBackupAgentData data) { if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data); Loading
core/java/android/window/flags/windowing_sdk.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -152,3 +152,14 @@ flag { purpose: PURPOSE_BUGFIX } } flag { namespace: "windowing_sdk" name: "support_widget_intents_on_connected_display" description: "Launch widget intents on originating display" bug: "358368849" is_fixed_read_only: true metadata { purpose: PURPOSE_BUGFIX } }
core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +100 −4 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.android.window.flags.Flags.FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; Loading @@ -39,11 +41,15 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.ActivityThread.ActivityClientRecord; import android.app.ActivityThread.ReceiverData; import android.app.Application; import android.app.IApplicationThread; import android.app.PictureInPictureParams; Loading Loading @@ -158,10 +164,7 @@ public class ActivityThreadTest { @After public void tearDown() { if (mCreatedVirtualDisplays != null) { mCreatedVirtualDisplays.forEach(VirtualDisplay::release); mCreatedVirtualDisplays = null; } tearDownVirtualDisplays(); WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); ClientTransactionListenerController.getInstance() .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); Loading Loading @@ -1007,6 +1010,92 @@ public class ActivityThreadTest { .that(systemContext.getApplicationInfo()).isSameInstanceAs(newAppInfo); } @Test @RequiresFlagsEnabled(FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY) public void tesScheduleReceiver_withLaunchDisplayId_receivesDisplayContext() { final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final Display virtualDisplay = createVirtualDisplay(context, 100 /* w */, 100 /* h */); final int virtualDisplayId = virtualDisplay.getDisplayId(); final ActivityOptions activityOptions = ActivityOptions.makeBasic().setLaunchDisplayId(virtualDisplayId); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final ReceiverData data = createReceiverData(activityOptions.toBundle()); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); final Display resultDisplay = resultContext.getDisplayNoVerify(); assertThat(resultDisplay).isNotNull(); assertThat(resultDisplay.getDisplayId()).isEqualTo(virtualDisplayId); assertThat(resultContext.getAssociatedDisplayId()).isEqualTo(virtualDisplayId); } @Test @RequiresFlagsEnabled(FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY) public void tesScheduleReceiver_withNotExistDisplayId_receivesNoneUiContext() { final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final Display virtualDisplay = createVirtualDisplay(context, 100 /* w */, 100 /* h */); final int virtualDisplayId = virtualDisplay.getDisplayId(); final ActivityOptions activityOptions = ActivityOptions.makeBasic().setLaunchDisplayId(virtualDisplayId); final ActivityThread activityThread = ActivityThread.currentActivityThread(); tearDownVirtualDisplays(); final ReceiverData data = createReceiverData(activityOptions.toBundle()); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); assertThat(resultContext).isEqualTo(context); } @Test @RequiresFlagsEnabled(FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY) public void tesScheduleReceiver_withInvalidDisplay_receivesNoneUiContext() { final Context context = mock(Context.class); final ActivityOptions activityOptions = ActivityOptions.makeBasic().setLaunchDisplayId(INVALID_DISPLAY); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final ReceiverData data = createReceiverData(activityOptions.toBundle()); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); verify(context, never()).createDisplayContext(any()); assertThat(resultContext).isEqualTo(context); } @Test @RequiresFlagsEnabled(FLAG_SUPPORT_WIDGET_INTENTS_ON_CONNECTED_DISPLAY) public void tesScheduleReceiver_withoutDisplayManagerService_receivesNoneUiContext() { final Context context = mock(Context.class); when(context.getSystemService(DisplayManager.class)).thenReturn(null); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final ReceiverData data = createReceiverData(null /* resultExtras */); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); verify(context, never()).createDisplayContext(any()); assertThat(resultContext).isEqualTo(context); } @Test public void tesScheduleReceiver_withoutActivityOptions_receivesNoneUiContext() { final Context context = mock(Context.class); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final ReceiverData data = createReceiverData(null /* resultExtras */); final Context resultContext = activityThread.createDisplayContextIfNeeded(context, data); verify(context, never()).createDisplayContext(any()); assertThat(resultContext).isEqualTo(context); } @NonNull private ReceiverData createReceiverData(@Nullable Bundle resultExtras) { return new ReceiverData(new Intent("test.action.WIDGET_ITEM_CLICK"), 0 /* resultCode */, null /* resultData */, resultExtras, false /* ordered */, false /* sticky */, false /* assumeDelivered */, null /* token */, 0 /* sendingUser */, -1 /* sendingUid */, null /* sendingPackage */); } /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the Loading Loading @@ -1056,6 +1145,13 @@ public class ActivityThreadTest { return virtualDisplay.getDisplay(); } private void tearDownVirtualDisplays() { if (mCreatedVirtualDisplays != null) { mCreatedVirtualDisplays.forEach(VirtualDisplay::release); mCreatedVirtualDisplays = null; } } private static ActivityClientRecord getActivityClientRecord(Activity activity) { final ActivityThread thread = activity.getActivityThread(); final IBinder token = activity.getActivityToken(); Loading
services/core/java/com/android/server/am/PendingIntentRecord.java +28 −1 Original line number Diff line number Diff line Loading @@ -668,12 +668,13 @@ public final class PendingIntentRecord extends IIntentSender.Stub { getBackgroundStartPrivilegesForActivitySender( mAllowBgActivityStartsForBroadcastSender, allowlistToken, options, callingUid); final Bundle extras = createSafeActivityOptionsBundle(options); // If a completion callback has been requested, require // that the broadcast be delivered synchronously int sent = controller.mAmInternal.broadcastIntentInPackage(key.packageName, key.featureId, uid, callingUid, callingPid, finalIntent, resolvedType, finishedReceiverThread, finishedReceiver, code, null, null, requiredPermission, options, (finishedReceiver != null), extras, requiredPermission, options, (finishedReceiver != null), false, userId, backgroundStartPrivileges, null /* broadcastAllowList */); if (sent == ActivityManager.BROADCAST_SUCCESS) { Loading Loading @@ -716,6 +717,32 @@ public final class PendingIntentRecord extends IIntentSender.Stub { return res; } /** * Creates a safe ActivityOptions bundle with only the launchDisplayId set. * * <p>This prevents unintended data from being sent to the app process. The resulting bundle * is then used by {@link ActivityThread#createDisplayContextIfNeeded} to create a display * context for the {@link BroadcastReceiver}, ensuring that activities launched from the * receiver's context are started on the correct display. * * @param optionsBundle The original ActivityOptions bundle. * @return A new bundle containing only the launchDisplayId from the original options, or null * if the original bundle is null. */ @Nullable private Bundle createSafeActivityOptionsBundle(@Nullable Bundle optionsBundle) { if (!com.android.window.flags.Flags.supportWidgetIntentsOnConnectedDisplay()) { return null; } if (optionsBundle == null) { return null; } final ActivityOptions options = ActivityOptions.fromBundle(optionsBundle); return ActivityOptions.makeBasic() .setLaunchDisplayId(options.getLaunchDisplayId()) .toBundle(); } @VisibleForTesting BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( IBinder allowlistToken) { return mAllowBgActivityStartsForActivitySender.contains(allowlistToken) Loading
services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +62 −2 Original line number Diff line number Diff line Loading @@ -34,25 +34,32 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.cancelReasonToString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.BackgroundStartPrivileges; import android.app.PendingIntent; import android.content.Intent; import android.content.pm.IPackageManager; import android.os.Binder; import android.os.Bundle; import android.os.Looper; import android.os.UserHandle; import androidx.test.runner.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; Loading Loading @@ -108,7 +115,8 @@ public class PendingIntentControllerTest { mPendingIntentController.onActivityManagerInternalAdded(); } private PendingIntentRecord createPendingIntentRecord(int flags) { @NonNull private PendingIntentRecord createPendingIntentRecord(@PendingIntent.Flags int flags) { return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST, TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, TEST_USER_ID, null, null, 0, TEST_INTENTS, null, flags, null); Loading Loading @@ -219,6 +227,58 @@ public class PendingIntentControllerTest { allowlistDurationLocked.type); } @Test public void testSendWithBundleExtras() { final PendingIntentRecord pir = createPendingIntentRecord(0); final ActivityOptions activityOptions = ActivityOptions.makeBasic(); activityOptions.setLaunchDisplayId(2); activityOptions.setLaunchTaskId(123); final Bundle options = activityOptions.toBundle(); options.putString("testKey", "testValue"); pir.send(0, null, null, null, null, null, options); final ArgumentCaptor<Bundle> resultExtrasCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mActivityManagerInternal).broadcastIntentInPackage( eq(TEST_PACKAGE_NAME), eq(TEST_FEATURE_ID), eq(TEST_CALLING_UID), eq(TEST_CALLING_UID), // realCallingUid anyInt(), // realCallingPid any(), // intent any(), // resolvedType any(), // resultToThread any(), // resultTo anyInt(), // resultCode any(), // resultData resultExtrasCaptor.capture(), // resultExtras any(), // requiredPermission eq(options), // bOptions anyBoolean(), // serialized anyBoolean(), // sticky anyInt(), // userId any(), // backgroundStartPrivileges any() // broadcastAllowList ); final Bundle result = resultExtrasCaptor.getValue(); if (com.android.window.flags.Flags.supportWidgetIntentsOnConnectedDisplay()) { // Check that only launchDisplayId in ActivityOptions is passed via resultExtras. final ActivityOptions expected = ActivityOptions.makeBasic().setLaunchDisplayId(2); assertBundleEquals(expected.toBundle(), result); // Check that launchTaskId is dropped in resultExtras. assertNotEquals(123, ActivityOptions.fromBundle(result).getLaunchTaskId()); } else { assertNull(result); } } private void assertBundleEquals(@NonNull Bundle expected, @NonNull Bundle observed) { assertEquals(expected.size(), observed.size()); for (String key : expected.keySet()) { assertEquals(expected.get(key), observed.get(key)); } } private void assertCancelReason(int expectedReason, int actualReason) { final String errMsg = "Expected: " + cancelReasonToString(expectedReason) + "; Actual: " + cancelReasonToString(actualReason); Loading