1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.wm; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 21 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 22 23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 24 25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 26 27 import static com.google.common.truth.Truth.assertThat; 28 29 import static org.junit.Assert.assertEquals; 30 import static org.junit.Assert.assertNotEquals; 31 import static org.junit.Assert.assertNotNull; 32 import static org.junit.Assert.assertNull; 33 import static org.junit.Assert.assertTrue; 34 import static org.mockito.ArgumentMatchers.any; 35 import static org.mockito.ArgumentMatchers.anyInt; 36 import static org.mockito.Mockito.atLeastOnce; 37 import static org.mockito.Mockito.verify; 38 39 import android.app.Activity; 40 import android.app.ActivityManager.RunningTaskInfo; 41 import android.app.ActivityOptions; 42 import android.app.Instrumentation; 43 import android.app.Instrumentation.ActivityMonitor; 44 import android.app.PictureInPictureParams; 45 import android.content.ComponentName; 46 import android.content.Context; 47 import android.content.Intent; 48 import android.os.Binder; 49 import android.os.Bundle; 50 import android.os.IBinder; 51 import android.os.IRemoteCallback; 52 import android.platform.test.annotations.Presubmit; 53 import android.util.Log; 54 import android.util.Rational; 55 import android.view.SurfaceControl; 56 import android.window.TaskOrganizer; 57 58 import androidx.test.filters.MediumTest; 59 60 import com.android.server.wm.utils.CommonUtils; 61 62 import org.junit.Test; 63 import org.mockito.ArgumentCaptor; 64 65 import java.util.ArrayList; 66 import java.util.List; 67 import java.util.concurrent.CountDownLatch; 68 import java.util.concurrent.TimeUnit; 69 import java.util.concurrent.atomic.AtomicBoolean; 70 71 /** 72 * Build/Install/Run: 73 * atest WmTests:ActivityOptionsTest 74 */ 75 @MediumTest 76 @Presubmit 77 public class ActivityOptionsTest { 78 79 @Test testMerge_NoClobber()80 public void testMerge_NoClobber() { 81 // Construct some options with set values 82 ActivityOptions opts = ActivityOptions.makeBasic(); 83 opts.setLaunchDisplayId(Integer.MAX_VALUE); 84 opts.setLaunchActivityType(ACTIVITY_TYPE_STANDARD); 85 opts.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); 86 opts.setAvoidMoveToFront(); 87 opts.setLaunchTaskId(Integer.MAX_VALUE); 88 opts.setLockTaskEnabled(true); 89 opts.setRotationAnimationHint(ROTATION_ANIMATION_ROTATE); 90 opts.setTaskAlwaysOnTop(true); 91 opts.setTaskOverlay(true, true); 92 Bundle optsBundle = opts.toBundle(); 93 94 // Try and merge the constructed options with a new set of options 95 optsBundle.putAll(ActivityOptions.makeBasic().toBundle()); 96 97 // Ensure the set values are not clobbered 98 ActivityOptions restoredOpts = ActivityOptions.fromBundle(optsBundle); 99 assertEquals(Integer.MAX_VALUE, restoredOpts.getLaunchDisplayId()); 100 assertEquals(ACTIVITY_TYPE_STANDARD, restoredOpts.getLaunchActivityType()); 101 assertEquals(WINDOWING_MODE_FULLSCREEN, restoredOpts.getLaunchWindowingMode()); 102 assertTrue(restoredOpts.getAvoidMoveToFront()); 103 assertEquals(Integer.MAX_VALUE, restoredOpts.getLaunchTaskId()); 104 assertTrue(restoredOpts.getLockTaskMode()); 105 assertEquals(ROTATION_ANIMATION_ROTATE, restoredOpts.getRotationAnimationHint()); 106 assertTrue(restoredOpts.getTaskAlwaysOnTop()); 107 assertTrue(restoredOpts.getTaskOverlay()); 108 assertTrue(restoredOpts.canTaskOverlayResume()); 109 } 110 111 @Test testMakeLaunchIntoPip()112 public void testMakeLaunchIntoPip() { 113 // Construct some params with set values 114 PictureInPictureParams params = new PictureInPictureParams.Builder() 115 .setAspectRatio(new Rational(1, 1)) 116 .build(); 117 // Construct ActivityOptions via makeLaunchIntoPip 118 ActivityOptions opts = ActivityOptions.makeLaunchIntoPip(params); 119 120 // Verify the params in ActivityOptions has the right flag being turned on 121 assertNotNull(opts.getLaunchIntoPipParams()); 122 assertTrue(opts.isLaunchIntoPip()); 123 } 124 125 @Test testAbortListenerCalled()126 public void testAbortListenerCalled() { 127 AtomicBoolean callbackCalled = new AtomicBoolean(false); 128 129 ActivityOptions options = ActivityOptions.makeBasic(); 130 options.setOnAnimationAbortListener(new IRemoteCallback.Stub() { 131 @Override 132 public void sendResult(Bundle data) { 133 callbackCalled.set(true); 134 } 135 }); 136 137 // Verify that the callback is called on abort 138 options.abort(); 139 assertTrue(callbackCalled.get()); 140 141 // Verify that the callback survives saving to bundle 142 ActivityOptions optionsCopy = ActivityOptions.fromBundle(options.toBundle()); 143 callbackCalled.set(false); 144 optionsCopy.abort(); 145 assertTrue(callbackCalled.get()); 146 } 147 148 @Test testTransferLaunchCookie()149 public void testTransferLaunchCookie() { 150 final Binder cookie = new Binder(); 151 final ActivityOptions options = ActivityOptions.makeBasic(); 152 options.setLaunchCookie(cookie); 153 final Instrumentation instrumentation = getInstrumentation(); 154 final Context context = instrumentation.getContext(); 155 final ComponentName trampoline = new ComponentName(context, TrampolineActivity.class); 156 final ComponentName main = new ComponentName(context, MainActivity.class); 157 final Intent intent = new Intent().setComponent(trampoline) 158 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 159 final ActivityMonitor monitor = new ActivityMonitor(main.getClassName(), 160 null /* result */, false /* block */); 161 instrumentation.addMonitor(monitor); 162 final CountDownLatch mainLatch = new CountDownLatch(1); 163 final IBinder[] appearedCookies = new IBinder[2]; 164 final TaskOrganizer organizer = new TaskOrganizer() { 165 @Override 166 public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { 167 try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) { 168 t.show(leash).apply(); 169 } 170 int cookieIndex = -1; 171 if (trampoline.equals(taskInfo.baseActivity)) { 172 cookieIndex = 0; 173 } else if (main.equals(taskInfo.baseActivity)) { 174 cookieIndex = 1; 175 } 176 if (cookieIndex >= 0) { 177 appearedCookies[cookieIndex] = taskInfo.launchCookies.isEmpty() 178 ? null : taskInfo.launchCookies.get(0); 179 if (cookieIndex == 1) { 180 mainLatch.countDown(); 181 } 182 } 183 } 184 }; 185 Activity mainActivity = null; 186 try { 187 organizer.registerOrganizer(); 188 context.startActivity(intent, options.toBundle()); 189 try { 190 mainLatch.await(10, TimeUnit.SECONDS); 191 } catch (InterruptedException ignored) { 192 } 193 mainActivity = monitor.getLastActivity(); 194 195 assertNotNull(mainActivity); 196 assertNotEquals(TrampolineActivity.sTaskId, mainActivity.getTaskId()); 197 assertNull("Trampoline task must not have cookie", appearedCookies[0]); 198 assertEquals("Main task must get the same cookie", cookie, appearedCookies[1]); 199 } finally { 200 organizer.unregisterOrganizer(); 201 instrumentation.removeMonitor(monitor); 202 if (mainActivity != null) { 203 mainActivity.finish(); 204 CommonUtils.waitUntilActivityRemoved(mainActivity); 205 } 206 } 207 } 208 209 /** 210 * Tests if any unknown key is being used in the ActivityOptions bundle. If so, please review 211 * if the newly added bundle should be protected with permissions to avoid malicious attacks. 212 * 213 * @see SafeActivityOptionsTest#test_getOptions 214 */ 215 @Test testActivityOptionsFromBundle()216 public void testActivityOptionsFromBundle() { 217 // Spy on a bundle that is generated from a basic ActivityOptions. 218 final ActivityOptions options = ActivityOptions.makeBasic(); 219 Bundle bundle = options.toBundle(); 220 spyOn(bundle); 221 222 // Create a new ActivityOptions from the bundle 223 new ActivityOptions(bundle); 224 225 // Verify the keys that are being used. 226 final ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class); 227 verify(bundle, atLeastOnce()).getString(stringCaptor.capture()); 228 verify(bundle, atLeastOnce()).getBoolean(stringCaptor.capture()); 229 verify(bundle, atLeastOnce()).getParcelable(stringCaptor.capture(), any()); 230 verify(bundle, atLeastOnce()).getInt(stringCaptor.capture(), anyInt()); 231 verify(bundle, atLeastOnce()).getBinder(stringCaptor.capture()); 232 verify(bundle, atLeastOnce()).getBundle(stringCaptor.capture()); 233 final List<String> keys = stringCaptor.getAllValues(); 234 final List<String> unknownKeys = new ArrayList<>(); 235 for (String key : keys) { 236 switch (key) { 237 case ActivityOptions.KEY_PACKAGE_NAME: 238 case ActivityOptions.KEY_LAUNCH_BOUNDS: 239 case ActivityOptions.KEY_ANIM_TYPE: 240 case ActivityOptions.KEY_ANIM_ENTER_RES_ID: 241 case ActivityOptions.KEY_ANIM_EXIT_RES_ID: 242 case ActivityOptions.KEY_ANIM_IN_PLACE_RES_ID: 243 case ActivityOptions.KEY_ANIM_BACKGROUND_COLOR: 244 case ActivityOptions.KEY_ANIM_THUMBNAIL: 245 case ActivityOptions.KEY_ANIM_START_X: 246 case ActivityOptions.KEY_ANIM_START_Y: 247 case ActivityOptions.KEY_ANIM_WIDTH: 248 case ActivityOptions.KEY_ANIM_HEIGHT: 249 case ActivityOptions.KEY_ANIM_START_LISTENER: 250 case ActivityOptions.KEY_SPLASH_SCREEN_THEME: 251 case ActivityOptions.KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE: 252 case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN: 253 case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN: 254 case ActivityOptions.KEY_TRANSIENT_LAUNCH: 255 case ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED: 256 case "android:activity.animationFinishedListener": 257 // KEY_ANIMATION_FINISHED_LISTENER 258 case "android:activity.animSpecs": // KEY_ANIM_SPECS 259 case "android:activity.lockTaskMode": // KEY_LOCK_TASK_MODE 260 case "android:activity.shareIdentity": // KEY_SHARE_IDENTITY 261 case "android.activity.launchDisplayId": // KEY_LAUNCH_DISPLAY_ID 262 case "android.activity.callerDisplayId": // KEY_CALLER_DISPLAY_ID 263 case "android.activity.launchTaskDisplayAreaToken": 264 // KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN 265 case "android.activity.launchTaskDisplayAreaFeatureId": 266 // KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID 267 case "android.activity.windowingMode": // KEY_LAUNCH_WINDOWING_MODE 268 case "android.activity.activityType": // KEY_LAUNCH_ACTIVITY_TYPE 269 case "android.activity.launchTaskId": // KEY_LAUNCH_TASK_ID 270 case "android.activity.disableStarting": // KEY_DISABLE_STARTING_WINDOW 271 case "android.activity.pendingIntentLaunchFlags": 272 // KEY_PENDING_INTENT_LAUNCH_FLAGS 273 case "android.activity.alwaysOnTop": // KEY_TASK_ALWAYS_ON_TOP 274 case "android.activity.taskOverlay": // KEY_TASK_OVERLAY 275 case "android.activity.taskOverlayCanResume": // KEY_TASK_OVERLAY_CAN_RESUME 276 case "android.activity.avoidMoveToFront": // KEY_AVOID_MOVE_TO_FRONT 277 case "android.activity.freezeRecentTasksReordering": 278 // KEY_FREEZE_RECENT_TASKS_REORDERING 279 case "android:activity.disallowEnterPictureInPictureWhileLaunching": 280 // KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING 281 case "android:activity.applyActivityFlagsForBubbles": 282 // KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES 283 case "android:activity.applyMultipleTaskFlagForShortcut": 284 // KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT 285 case "android:activity.applyNoUserActionFlagForShortcut": 286 // KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT 287 case "android:activity.transitionCompleteListener": 288 // KEY_TRANSITION_COMPLETE_LISTENER 289 case "android:activity.transitionIsReturning": // KEY_TRANSITION_IS_RETURNING 290 case "android:activity.sharedElementNames": // KEY_TRANSITION_SHARED_ELEMENTS 291 case "android:activity.resultData": // KEY_RESULT_DATA 292 case "android:activity.resultCode": // KEY_RESULT_CODE 293 case "android:activity.exitCoordinatorIndex": // KEY_EXIT_COORDINATOR_INDEX 294 case "android.activity.sourceInfo": // KEY_SOURCE_INFO 295 case "android:activity.usageTimeReport": // KEY_USAGE_TIME_REPORT 296 case "android:activity.rotationAnimationHint": // KEY_ROTATION_ANIMATION_HINT 297 case "android:instantapps.installerbundle": // KEY_INSTANT_APP_VERIFICATION_BUNDLE 298 case "android:activity.specsFuture": // KEY_SPECS_FUTURE 299 case "android:activity.remoteAnimationAdapter": // KEY_REMOTE_ANIMATION_ADAPTER 300 case "android:activity.remoteTransition": // KEY_REMOTE_TRANSITION 301 case "android:activity.overrideTaskTransition": // KEY_OVERRIDE_TASK_TRANSITION 302 case "android.activity.removeWithTaskOrganizer": // KEY_REMOVE_WITH_TASK_ORGANIZER 303 case "android.activity.launchTypeBubble": // KEY_LAUNCHED_FROM_BUBBLE 304 case "android.activity.splashScreenStyle": // KEY_SPLASH_SCREEN_STYLE 305 case "android.activity.launchIntoPipParams": // KEY_LAUNCH_INTO_PIP_PARAMS 306 case "android.activity.dismissKeyguardIfInsecure": // KEY_DISMISS_KEYGUARD_IF_INSECURE 307 case "android.activity.pendingIntentCreatorBackgroundActivityStartMode": 308 // KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE 309 case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE 310 case "android:activity.animAbortListener": // KEY_ANIM_ABORT_LISTENER 311 case "android.activity.allowPassThroughOnTouchOutside": 312 // KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE 313 // Existing keys 314 315 break; 316 default: 317 unknownKeys.add(key); 318 break; 319 } 320 } 321 322 // Report if any unknown key exists. 323 for (String key : unknownKeys) { 324 Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. " 325 + "Please review if the given bundle should be protected with permissions."); 326 } 327 assertThat(unknownKeys).isEmpty(); 328 } 329 330 public static class TrampolineActivity extends Activity { 331 static int sTaskId; 332 333 @Override onCreate(Bundle savedInstanceState)334 protected void onCreate(Bundle savedInstanceState) { 335 super.onCreate(savedInstanceState); 336 sTaskId = getTaskId(); 337 startActivity(new Intent(this, MainActivity.class) 338 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 339 finish(); 340 } 341 } 342 343 public static class MainActivity extends Activity { 344 } 345 } 346