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; 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.Task; 41 import android.window.TaskFragmentInfo; 42 import android.window.WindowContainerTransaction; 43 44 import androidx.annotation.NonNull; 45 46 import org.junit.Before; 47 import org.junit.Test; 48 49 /** 50 * Tests that verifies the behaviors of embedding activities in different trusted modes. 51 * 52 * Build/Install/Run: 53 * atest CtsWindowManagerDeviceTestCases:TaskFragmentTrustedModeTest 54 */ 55 @Presubmit 56 @android.server.wm.annotation.Group2 57 public class TaskFragmentTrustedModeTest extends TaskFragmentOrganizerTestBase { 58 59 private final ComponentName mTranslucentActivity = new ComponentName(mContext, 60 TranslucentActivity.class); 61 62 @Before 63 @Override setUp()64 public void setUp() throws Exception { 65 super.setUp(); 66 assumeActivityEmbeddingSupportedDevice(); 67 } 68 69 /** 70 * Verifies the visibility of a task fragment that has overlays on top of activities embedded 71 * in untrusted mode when there is an overlay over the task fragment. 72 */ 73 @Test testUntrustedModeTaskFragmentVisibility_overlayTaskFragment()74 public void testUntrustedModeTaskFragmentVisibility_overlayTaskFragment() { 75 // Create a task fragment with activity in untrusted mode. 76 final Rect baseActivityBounds = 77 mOwnerActivity.getResources().getConfiguration().windowConfiguration.getBounds(); 78 final TaskFragmentInfo tf = createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, 79 partialOverlayRelativeBounds(baseActivityBounds)); 80 81 // Start a translucent activity over the TaskFragment. 82 createTaskFragment(mTranslucentActivity, partialOverlayRelativeBounds( 83 tf.getConfiguration().windowConfiguration.getBounds())); 84 waitAndAssertResumedActivity(mTranslucentActivity, "Translucent activity must be resumed."); 85 86 // The task fragment must be made invisible when there is an overlay activity in it. 87 final String overlayMessage = "Activities embedded in untrusted mode should be made " 88 + "invisible in a task fragment with overlay"; 89 waitAndAssertStoppedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, overlayMessage); 90 assertFalse(overlayMessage, mWmState.getTaskFragmentByActivity( 91 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).isVisible()); 92 93 // The activity that appeared on top would stay resumed 94 assertTrue(overlayMessage, mWmState.hasActivityState(mTranslucentActivity, STATE_RESUMED)); 95 assertTrue(overlayMessage, mWmState.isActivityVisible(mTranslucentActivity)); 96 assertTrue(overlayMessage, mWmState.getTaskFragmentByActivity( 97 mTranslucentActivity).isVisible()); 98 } 99 100 /** 101 * Verifies the visibility of a task fragment that has overlays on top of activities embedded 102 * in untrusted mode when an activity from another process is started on top. 103 */ 104 @Test testUntrustedModeTaskFragmentVisibility_startActivityInTaskFragment()105 public void testUntrustedModeTaskFragmentVisibility_startActivityInTaskFragment() { 106 // Create a task fragment with activity in untrusted mode. 107 final Rect baseActivityBounds = 108 mOwnerActivity.getResources().getConfiguration().windowConfiguration.getBounds(); 109 final TaskFragmentInfo taskFragmentInfo = createTaskFragment( 110 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, 111 partialOverlayRelativeBounds(baseActivityBounds)); 112 113 // Start an activity with a different UID in the TaskFragment. 114 final WindowContainerTransaction wct = new WindowContainerTransaction() 115 .startActivityInTaskFragment(taskFragmentInfo.getFragmentToken(), mOwnerToken, 116 new Intent().setComponent(mTranslucentActivity), 117 null /* activityOptions */); 118 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, 119 false /* shouldApplyIndependently */); 120 waitAndAssertResumedActivity(mTranslucentActivity, "Translucent activity must be resumed."); 121 122 // Some activities in the task fragment must be made invisible when there is an overlay. 123 final String overlayMessage = "Activities embedded in untrusted mode should be made " 124 + "invisible in a task fragment with overlay"; 125 waitAndAssertStoppedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, overlayMessage); 126 127 // The activity that appeared on top would stay resumed, and the task fragment is still 128 // visible. 129 assertTrue(overlayMessage, mWmState.hasActivityState(mTranslucentActivity, STATE_RESUMED)); 130 assertTrue(overlayMessage, mWmState.isActivityVisible(mTranslucentActivity)); 131 assertTrue(overlayMessage, mWmState.getTaskFragmentByActivity( 132 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).isVisible()); 133 } 134 135 /** 136 * Verifies the visibility of a task fragment that has overlays on top of activities embedded 137 * in untrusted mode when an activity from another process is reparented on top. 138 */ 139 @Test testUntrustedModeTaskFragmentVisibility_reparentActivityInTaskFragment()140 public void testUntrustedModeTaskFragmentVisibility_reparentActivityInTaskFragment() { 141 final Activity translucentActivity = startActivity(TranslucentActivity.class); 142 143 // Create a task fragment with activity in untrusted mode. 144 final TaskFragmentInfo taskFragmentInfo = createTaskFragment( 145 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY); 146 147 // Reparent a translucent activity with a different UID to the TaskFragment. 148 final IBinder embeddedActivityToken = getActivityToken(translucentActivity); 149 final WindowContainerTransaction wct = new WindowContainerTransaction() 150 .reparentActivityToTaskFragment(taskFragmentInfo.getFragmentToken(), 151 embeddedActivityToken); 152 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, 153 false /* shouldApplyIndependently */); 154 waitAndAssertResumedActivity(mTranslucentActivity, "Translucent activity must be resumed."); 155 156 // Some activities in the task fragment must be made invisible when there is an overlay. 157 final String overlayMessage = "Activities embedded in untrusted mode should be made " 158 + "invisible in a task fragment with overlay"; 159 waitAndAssertStoppedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, overlayMessage); 160 161 // The activity that appeared on top would stay resumed, and the task fragment is still 162 // visible 163 assertTrue(overlayMessage, mWmState.hasActivityState(mTranslucentActivity, STATE_RESUMED)); 164 assertTrue(overlayMessage, mWmState.isActivityVisible(mTranslucentActivity)); 165 assertTrue(overlayMessage, mWmState.getTaskFragmentByActivity( 166 SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).isVisible()); 167 168 // Finishing the overlay activity must make TaskFragment visible again. 169 translucentActivity.finish(); 170 waitAndAssertResumedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, 171 "Activity must be resumed without overlays"); 172 assertTrue("Activity must be visible without overlays", 173 mWmState.isActivityVisible(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY)); 174 } 175 176 /** 177 * Verifies that when the TaskFragment has embedded activities in untrusted mode, set relative 178 * bounds outside of its parent bounds will still set the TaskFragment bounds within its parent. 179 */ 180 @Test testUntrustedModeTaskFragment_setRelativeBoundsOutsideOfParentBounds()181 public void testUntrustedModeTaskFragment_setRelativeBoundsOutsideOfParentBounds() { 182 final Task parentTask = mWmState.getTaskByActivity(mOwnerActivityName); 183 final Rect parentBounds = new Rect(parentTask.getBounds()); 184 // Create a TaskFragment with activity embedded in untrusted mode. 185 final TaskFragmentInfo info = createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY); 186 187 // Try to set relative bounds that is larger than its parent bounds. 188 mTaskFragmentOrganizer.resetLatch(); 189 final Rect taskFragRelativeBounds = new Rect(parentBounds); 190 taskFragRelativeBounds.offsetTo(0, 0); 191 taskFragRelativeBounds.right++; 192 final WindowContainerTransaction wct = new WindowContainerTransaction() 193 .setRelativeBounds(info.getToken(), taskFragRelativeBounds) 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.mFullConfiguration.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.getTaskByActivity(mOwnerActivityName); 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