• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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