1 /* 2 * Copyright (C) 2022 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 android.server.wm.taskfragment; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 20 import static android.server.wm.WindowManagerState.STATE_RESUMED; 21 import static android.server.wm.jetpack.second.Components.SECOND_UNTRUSTED_EMBEDDING_ACTIVITY; 22 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.assumeActivityEmbeddingSupportedDevice; 23 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE; 24 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; 25 26 import static com.google.common.truth.Truth.assertThat; 27 28 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertFalse; 30 import static org.junit.Assert.assertNotNull; 31 import static org.junit.Assert.assertTrue; 32 33 import android.app.Activity; 34 import android.content.ComponentName; 35 import android.content.Intent; 36 import android.graphics.Rect; 37 import android.os.Binder; 38 import android.os.IBinder; 39 import android.platform.test.annotations.Presubmit; 40 import android.server.wm.WindowManagerState; 41 import android.server.wm.WindowManagerState.Task; 42 import android.window.TaskFragmentInfo; 43 import android.window.WindowContainerTransaction; 44 45 import androidx.annotation.NonNull; 46 47 import org.junit.Before; 48 import org.junit.Test; 49 50 /** 51 * Tests that verifies the behaviors of embedding activities in different trusted modes. 52 * 53 * Build/Install/Run: 54 * atest CtsWindowManagerDeviceTaskFragment:TaskFragmentTrustedModeTest 55 */ 56 @Presubmit 57 @android.server.wm.annotation.Group2 58 public class TaskFragmentTrustedModeTest extends TaskFragmentOrganizerTestBase { 59 60 private final ComponentName mTranslucentActivity = new ComponentName(mContext, 61 TranslucentActivity.class); 62 63 @Before 64 @Override setUp()65 public void setUp() throws Exception { 66 super.setUp(); 67 assumeActivityEmbeddingSupportedDevice(); 68 } 69 70 /** 71 * Verifies the visibility of a task fragment that has overlays on top of activities embedded 72 * in untrusted mode when there is an overlay over the task fragment. 73 */ 74 @Test testUntrustedModeTaskFragmentVisibility_overlayTaskFragment()75 public void testUntrustedModeTaskFragmentVisibility_overlayTaskFragment() { 76 // Create a task fragment with activity in untrusted mode. 77 final Rect baseActivityBounds = 78 mOwnerActivity.getResources().getConfiguration().windowConfiguration.getBounds(); 79 final TaskFragmentInfo tf = createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, 80 partialOverlayRelativeBounds(baseActivityBounds)); 81 82 // Start a translucent activity over the TaskFragment. 83 createTaskFragment(mTranslucentActivity, partialOverlayRelativeBounds( 84 tf.getConfiguration().windowConfiguration.getBounds())); 85 waitAndAssertResumedActivity(mTranslucentActivity, "Translucent activity must be resumed."); 86 87 // The task fragment must be made invisible when there is an overlay activity in it. 88 final String overlayMessage = "Activities embedded in untrusted mode should be made " 89 + "invisible in a task fragment with overlay"; 90 waitAndAssertStoppedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, overlayMessage); 91 assertFalse(overlayMessage, mWmState.getTaskFragmentByActivity( 92 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).isVisible()); 93 94 // The activity that appeared on top would stay resumed 95 assertTrue(overlayMessage, mWmState.hasActivityState(mTranslucentActivity, STATE_RESUMED)); 96 assertTrue(overlayMessage, mWmState.isActivityVisible(mTranslucentActivity)); 97 assertTrue(overlayMessage, mWmState.getTaskFragmentByActivity( 98 mTranslucentActivity).isVisible()); 99 } 100 101 /** 102 * Verifies the visibility of a task fragment that has overlays on top of activities embedded 103 * in untrusted mode when an activity from another process is started on top. 104 */ 105 @Test testUntrustedModeTaskFragmentVisibility_startActivityInTaskFragment()106 public void testUntrustedModeTaskFragmentVisibility_startActivityInTaskFragment() { 107 // Create a task fragment with activity in untrusted mode. 108 final Rect baseActivityBounds = 109 mOwnerActivity.getResources().getConfiguration().windowConfiguration.getBounds(); 110 final TaskFragmentInfo taskFragmentInfo = createTaskFragment( 111 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, 112 partialOverlayRelativeBounds(baseActivityBounds)); 113 114 // Start an activity with a different UID in the TaskFragment. 115 final WindowContainerTransaction wct = new WindowContainerTransaction() 116 .startActivityInTaskFragment(taskFragmentInfo.getFragmentToken(), mOwnerToken, 117 new Intent().setComponent(mTranslucentActivity), 118 null /* activityOptions */); 119 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, 120 false /* shouldApplyIndependently */); 121 waitAndAssertResumedActivity(mTranslucentActivity, "Translucent activity must be resumed."); 122 123 // Some activities in the task fragment must be made invisible when there is an overlay. 124 final String overlayMessage = "Activities embedded in untrusted mode should be made " 125 + "invisible in a task fragment with overlay"; 126 waitAndAssertStoppedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, overlayMessage); 127 128 // The activity that appeared on top would stay resumed, and the task fragment is still 129 // visible. 130 assertTrue(overlayMessage, mWmState.hasActivityState(mTranslucentActivity, STATE_RESUMED)); 131 assertTrue(overlayMessage, mWmState.isActivityVisible(mTranslucentActivity)); 132 assertTrue(overlayMessage, mWmState.getTaskFragmentByActivity( 133 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).isVisible()); 134 } 135 136 /** 137 * Verifies the visibility of a task fragment that has overlays on top of activities embedded 138 * in untrusted mode when an activity from another process is reparented on top. 139 */ 140 @Test testUntrustedModeTaskFragmentVisibility_reparentActivityInTaskFragment()141 public void testUntrustedModeTaskFragmentVisibility_reparentActivityInTaskFragment() { 142 final Activity translucentActivity = startActivity(TranslucentActivity.class); 143 144 // Create a task fragment with activity in untrusted mode. 145 final TaskFragmentInfo taskFragmentInfo = createTaskFragment( 146 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY); 147 148 // Reparent a translucent activity with a different UID to the TaskFragment. 149 final IBinder embeddedActivityToken = getActivityToken(translucentActivity); 150 final WindowContainerTransaction wct = new WindowContainerTransaction() 151 .reparentActivityToTaskFragment(taskFragmentInfo.getFragmentToken(), 152 embeddedActivityToken); 153 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, 154 false /* shouldApplyIndependently */); 155 waitAndAssertResumedActivity(mTranslucentActivity, "Translucent activity must be resumed."); 156 157 // Some activities in the task fragment must be made invisible when there is an overlay. 158 final String overlayMessage = "Activities embedded in untrusted mode should be made " 159 + "invisible in a task fragment with overlay"; 160 waitAndAssertStoppedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, overlayMessage); 161 162 // The activity that appeared on top would stay resumed, and the task fragment is still 163 // visible 164 assertTrue(overlayMessage, mWmState.hasActivityState(mTranslucentActivity, STATE_RESUMED)); 165 assertTrue(overlayMessage, mWmState.isActivityVisible(mTranslucentActivity)); 166 assertTrue(overlayMessage, mWmState.getTaskFragmentByActivity( 167 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).isVisible()); 168 169 // Finishing the overlay activity must make TaskFragment visible again. 170 translucentActivity.finish(); 171 waitAndAssertResumedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, 172 "Activity must be resumed without overlays"); 173 assertTrue("Activity must be visible without overlays", 174 mWmState.isActivityVisible(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY)); 175 } 176 177 /** 178 * Verifies that when the TaskFragment has embedded activities in untrusted mode, set relative 179 * bounds outside of its parent bounds will still set the TaskFragment bounds within its parent. 180 */ 181 @Test testUntrustedModeTaskFragment_setRelativeBoundsOutsideOfParentBounds()182 public void testUntrustedModeTaskFragment_setRelativeBoundsOutsideOfParentBounds() { 183 final Task parentTask = mWmState.getRootTask(mOwnerTaskId); 184 final Rect parentBounds = new Rect(parentTask.getBounds()); 185 // Create a TaskFragment with activity embedded in untrusted mode. 186 final TaskFragmentInfo info = createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY); 187 188 // Try to set relative bounds that is larger than its parent bounds. 189 mTaskFragmentOrganizer.resetLatch(); 190 final Rect taskFragBounds = new Rect(parentBounds); 191 taskFragBounds.right++; 192 final WindowContainerTransaction wct = new WindowContainerTransaction() 193 .setRelativeBounds(info.getToken(), taskFragBounds) 194 .setWindowingMode(info.getToken(), WINDOWING_MODE_MULTI_WINDOW); 195 196 // It is allowed to set TaskFragment bounds to outside of its parent bounds. 197 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, 198 false /* shouldApplyIndependently */); 199 200 // Update the windowing mode to make sure the WindowContainerTransaction has been applied. 201 mWmState.waitForWithAmState(amState -> { 202 final WindowManagerState.TaskFragment tf = amState.getTaskFragmentByActivity( 203 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY); 204 return tf != null && tf.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW; 205 }, "TaskFragment should have WINDOWING_MODE_MULTI_WINDOW"); 206 207 // The TaskFragment bounds should remain in its parent bounds. 208 final WindowManagerState.TaskFragment tf = mWmState.getTaskFragmentByActivity( 209 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY); 210 assertNotNull(tf); 211 assertEquals(WINDOWING_MODE_MULTI_WINDOW, tf.getWindowingMode()); 212 assertEquals(parentBounds, tf.getFullConfiguration().windowConfiguration.getBounds()); 213 } 214 215 /** 216 * Verifies that when the TaskFragment bounds is outside of its parent bounds, it is disallowed 217 * to start activity in untrusted mode. 218 */ 219 @Test testUntrustedModeTaskFragment_startActivityInTaskFragmentOutsideOfParentBounds()220 public void testUntrustedModeTaskFragment_startActivityInTaskFragmentOutsideOfParentBounds() { 221 final Task parentTask = mWmState.getRootTask(mOwnerTaskId); 222 final Rect parentBounds = new Rect(parentTask.getBounds()); 223 final IBinder errorCallbackToken = new Binder(); 224 final WindowContainerTransaction wct = new WindowContainerTransaction() 225 .setErrorCallbackToken(errorCallbackToken); 226 227 // We check if the TaskFragment bounds is in its parent bounds before launching activity in 228 // untrusted mode. 229 final Rect taskFragRelativeBounds = new Rect(parentBounds); 230 taskFragRelativeBounds.offsetTo(0, 0); 231 taskFragRelativeBounds.right++; 232 createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, taskFragRelativeBounds, wct); 233 234 // It is disallowed to start activity to TaskFragment with bounds outside of its parent 235 // in untrusted mode. 236 assertTaskFragmentError(errorCallbackToken, SecurityException.class); 237 } 238 239 /** 240 * Creates relative bounds for a container that would appear on top and partially occlude the 241 * provided one. 242 */ 243 @NonNull partialOverlayRelativeBounds(@onNull Rect baseBounds)244 private Rect partialOverlayRelativeBounds(@NonNull Rect baseBounds) { 245 final Rect result = new Rect(baseBounds); 246 result.offsetTo(0, 0); 247 result.inset(50 /* left */, 50 /* top */, 50 /* right */, 50 /* bottom */); 248 return result; 249 } 250 251 /** Asserts that the organizer received an error callback. */ assertTaskFragmentError(@onNull IBinder errorCallbackToken, @NonNull Class<? extends Throwable> exceptionClass)252 private void assertTaskFragmentError(@NonNull IBinder errorCallbackToken, 253 @NonNull Class<? extends Throwable> exceptionClass) { 254 mTaskFragmentOrganizer.waitForTaskFragmentError(); 255 assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf(exceptionClass); 256 assertThat(mTaskFragmentOrganizer.getErrorCallbackToken()).isEqualTo(errorCallbackToken); 257 } 258 259 public static class TranslucentActivity extends FocusableActivity {} 260 } 261