summaryrefslogtreecommitdiff
path: root/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentTrustedModeTest.java
blob: c13c4eec7f8f1595dc2d6bcffdce1562a311966c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.server.wm;

import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static android.server.wm.jetpack.second.Components.SECOND_UNTRUSTED_EMBEDDING_ACTIVITY;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.assumeActivityEmbeddingSupportedDevice;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.server.wm.WindowManagerState.Task;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;

import androidx.annotation.NonNull;

import org.junit.Before;
import org.junit.Test;

/**
 * Tests that verifies the behaviors of embedding activities in different trusted modes.
 *
 * Build/Install/Run:
 *     atest CtsWindowManagerDeviceTestCases:TaskFragmentTrustedModeTest
 */
public class TaskFragmentTrustedModeTest extends TaskFragmentOrganizerTestBase {

    private final ComponentName mTranslucentActivity = new ComponentName(mContext,
            TranslucentActivity.class);

    @Before
    @Override
    public void setUp() throws Exception {
        super.setUp();
        assumeActivityEmbeddingSupportedDevice();
    }

    /**
     * Verifies the visibility of a task fragment that has overlays on top of activities embedded
     * in untrusted mode when there is an overlay over the task fragment.
     */
    @Test
    public void testUntrustedModeTaskFragmentVisibility_overlayTaskFragment() {
        // Create a task fragment with activity in untrusted mode.
        final TaskFragmentInfo tf = createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY);

        // Start a translucent activity over the TaskFragment.
        createTaskFragment(mTranslucentActivity, partialOverlayBounds(tf));
        waitAndAssertResumedActivity(mTranslucentActivity, "Translucent activity must be resumed.");

        // The task fragment must be made invisible when there is an overlay activity in it.
        final String overlayMessage = "Activities embedded in untrusted mode should be made "
                + "invisible in a task fragment with overlay";
        waitAndAssertStoppedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, overlayMessage);
        assertFalse(overlayMessage, mWmState.getTaskFragmentByActivity(
                SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).isVisible());

        // The activity that appeared on top would stay resumed
        assertTrue(overlayMessage, mWmState.hasActivityState(mTranslucentActivity, STATE_RESUMED));
        assertTrue(overlayMessage, mWmState.isActivityVisible(mTranslucentActivity));
        assertTrue(overlayMessage, mWmState.getTaskFragmentByActivity(
                mTranslucentActivity).isVisible());
    }

    /**
     * Verifies the visibility of a task fragment that has overlays on top of activities embedded
     * in untrusted mode when an activity from another process is started on top.
     */
    @Test
    public void testUntrustedModeTaskFragmentVisibility_startActivityInTaskFragment() {
        // Create a task fragment with activity in untrusted mode.
        final TaskFragmentInfo taskFragmentInfo = createTaskFragment(
                SECOND_UNTRUSTED_EMBEDDING_ACTIVITY);

        // Start an activity with a different UID in the TaskFragment.
        final WindowContainerTransaction wct = new WindowContainerTransaction()
                .startActivityInTaskFragment(taskFragmentInfo.getFragmentToken(), mOwnerToken,
                        new Intent().setComponent(mTranslucentActivity),
                        null /* activityOptions */);
        mTaskFragmentOrganizer.applyTransaction(wct);
        waitAndAssertResumedActivity(mTranslucentActivity, "Translucent activity must be resumed.");

        // Some activities in the task fragment must be made invisible when there is an overlay.
        final String overlayMessage = "Activities embedded in untrusted mode should be made "
                + "invisible in a task fragment with overlay";
        waitAndAssertStoppedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, overlayMessage);

        // The activity that appeared on top would stay resumed, and the task fragment is still
        // visible.
        assertTrue(overlayMessage, mWmState.hasActivityState(mTranslucentActivity, STATE_RESUMED));
        assertTrue(overlayMessage, mWmState.isActivityVisible(mTranslucentActivity));
        assertTrue(overlayMessage, mWmState.getTaskFragmentByActivity(
                SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).isVisible());
    }

    /**
     * Verifies the visibility of a task fragment that has overlays on top of activities embedded
     * in untrusted mode when an activity from another process is reparented on top.
     */
    @Test
    public void testUntrustedModeTaskFragmentVisibility_reparentActivityInTaskFragment() {
        final Activity translucentActivity = startActivity(TranslucentActivity.class);

        // Create a task fragment with activity in untrusted mode.
        final TaskFragmentInfo taskFragmentInfo = createTaskFragment(
                SECOND_UNTRUSTED_EMBEDDING_ACTIVITY);

        // Reparent a translucent activity with a different UID to the TaskFragment.
        final IBinder embeddedActivityToken = getActivityToken(translucentActivity);
        final WindowContainerTransaction wct = new WindowContainerTransaction()
                .reparentActivityToTaskFragment(taskFragmentInfo.getFragmentToken(),
                        embeddedActivityToken);
        mTaskFragmentOrganizer.applyTransaction(wct);
        waitAndAssertResumedActivity(mTranslucentActivity, "Translucent activity must be resumed.");

        // Some activities in the task fragment must be made invisible when there is an overlay.
        final String overlayMessage = "Activities embedded in untrusted mode should be made "
                + "invisible in a task fragment with overlay";
        waitAndAssertStoppedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, overlayMessage);

        // The activity that appeared on top would stay resumed, and the task fragment is still
        // visible
        assertTrue(overlayMessage, mWmState.hasActivityState(mTranslucentActivity, STATE_RESUMED));
        assertTrue(overlayMessage, mWmState.isActivityVisible(mTranslucentActivity));
        assertTrue(overlayMessage, mWmState.getTaskFragmentByActivity(
                SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).isVisible());

        // Finishing the overlay activity must make TaskFragment visible again.
        translucentActivity.finish();
        waitAndAssertResumedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY,
                "Activity must be resumed without overlays");
        assertTrue("Activity must be visible without overlays",
                mWmState.isActivityVisible(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY));
    }

    /**
     * Verifies that when the TaskFragment has embedded activities in untrusted mode, it is
     * disallowed to set bounds that is outside of its parent bounds.
     */
    @Test
    public void testUntrustedModeTaskFragment_setBoundsOutsideOfParentBounds() {
        final Task parentTask = mWmState.getRootTask(mOwnerTaskId);
        final Rect parentBounds = new Rect(parentTask.getBounds());
        // Create a TaskFragment with activity embedded in untrusted mode.
        final TaskFragmentInfo info = createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY);

        // Try to set bounds that is outside of its parent bounds.
        mTaskFragmentOrganizer.resetLatch();
        final Rect taskFragBounds = new Rect(parentBounds);
        taskFragBounds.right++;
        final WindowContainerTransaction wct = new WindowContainerTransaction()
                .setBounds(info.getToken(), taskFragBounds);

        // It is disallowed to set TaskFragment bounds to outside of its parent bounds.
        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
    }

    /**
     * Verifies that when the TaskFragment has embedded activities in untrusted mode, it is
     * disallowed to set app bounds that is outside of its parent app bounds.
     */
    @Test
    public void testUntrustedModeTaskFragment_setAppBoundsOutsideOfParentAppBounds() {
        final Task parentTask = mWmState.getRootTask(mOwnerTaskId);
        final Rect parentAppBounds =
                new Rect(parentTask.mFullConfiguration.windowConfiguration.getAppBounds());
        // Create a TaskFragment with activity embedded in untrusted mode.
        final TaskFragmentInfo info = createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY);

        // Try to set app bounds that is outside of its parent app bounds.
        mTaskFragmentOrganizer.resetLatch();
        final Rect taskFragAppBounds = new Rect(parentAppBounds);
        taskFragAppBounds.right++;
        final WindowContainerTransaction wct = new WindowContainerTransaction()
                .setAppBounds(info.getToken(), taskFragAppBounds);

        // It is disallowed to set TaskFragment app bounds to outside of its parent app bounds.
        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
    }

    /**
     * Verifies that when the TaskFragment has embedded activities in untrusted mode, it is
     * disallowed to set screenWidthDp/screenHeightDp/smallestScreenWidthDp greater than parent's.
     */
    @Test
    public void testUntrustedModeTaskFragment_setSetScreenWidthHeightGreaterThanParent() {
        final Task parentTask = mWmState.getRootTask(mOwnerTaskId);
        final int screenWidthDp = parentTask.mFullConfiguration.screenWidthDp;
        final int screenHeightDp = parentTask.mFullConfiguration.screenHeightDp;
        final int smallestScreenWidthDp = parentTask.mFullConfiguration.smallestScreenWidthDp;
        // Create a TaskFragment with activity embedded in untrusted mode.
        final TaskFragmentInfo info = createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY);

        // Try to set screenWidthDp greater than parent's.
        mTaskFragmentOrganizer.resetLatch();
        final WindowContainerTransaction wct0 = new WindowContainerTransaction()
                .setScreenSizeDp(info.getToken(), screenWidthDp + 1, screenHeightDp);

        // It is disallowed to set TaskFragment screenWidthDp to be greater than parent's.
        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct0));

        // Try to set screenHeightDp greater than parent's.
        mTaskFragmentOrganizer.resetLatch();
        final WindowContainerTransaction wct1 = new WindowContainerTransaction()
                .setScreenSizeDp(info.getToken(), screenWidthDp, screenHeightDp + 1);

        // It is disallowed to set TaskFragment screenHeightDp to be greater than parent's.
        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct1));

        // Try to set smallestScreenWidthDp greater than parent's.
        mTaskFragmentOrganizer.resetLatch();
        final WindowContainerTransaction wct2 = new WindowContainerTransaction()
                .setSmallestScreenWidthDp(info.getToken(), smallestScreenWidthDp + 1);

        // It is disallowed to set TaskFragment smallestScreenWidthDp to be greater than parent's.
        assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct2));
    }

    /**
     * Verifies that when the TaskFragment bounds is outside of its parent bounds, it is disallowed
     * to start activity in untrusted mode.
     */
    @Test
    public void testUntrustedModeTaskFragment_startActivityInTaskFragmentOutsideOfParentBounds() {
        final Task parentTask = mWmState.getRootTask(mOwnerTaskId);
        final Rect parentBounds = new Rect(parentTask.getBounds());
        final IBinder errorCallbackToken = new Binder();
        final WindowContainerTransaction wct = new WindowContainerTransaction()
                .setErrorCallbackToken(errorCallbackToken);

        // We check if the TaskFragment bounds is in its parent bounds before launching activity in
        // untrusted mode.
        final Rect taskFragBounds = new Rect(parentBounds);
        taskFragBounds.right++;
        createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, taskFragBounds, wct);

        // It is disallowed to start activity to TaskFragment with bounds outside of its parent
        // in untrusted mode.
        assertTaskFragmentError(errorCallbackToken, SecurityException.class);
        mWmState.waitForAppTransitionIdleOnDisplay(mOwnerActivity.getDisplayId());
        mWmState.assertNotExist(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY);
    }

    /**
     * Verifies that when the TaskFragment bounds is outside of its parent bounds, it is disallowed
     * to reparent children of a TaskFragment to another in untrusted mode.
     */
    @Test
    public void testUntrustedModeTaskFragment_reparentChildrenOutsideOfParentBounds() {
        // Create a TaskFragment with activity in trusted mode with bounds outside of its parent.
        final Task parentTask = mWmState.getRootTask(mOwnerTaskId);
        final Rect parentBounds = new Rect(parentTask.getBounds());
        final Rect taskFragBounds = new Rect(parentBounds);
        taskFragBounds.right++;
        final TaskFragmentCreationParams params1 = generateTaskFragCreationParams(
                taskFragBounds);
        final IBinder taskFragToken = params1.getFragmentToken();
        final WindowContainerTransaction wct1 = new WindowContainerTransaction()
                .createTaskFragment(params1)
                .reparentActivityToTaskFragment(taskFragToken, mOwnerToken);
        mTaskFragmentOrganizer.applyTransaction(wct1);
        mTaskFragmentOrganizer.waitForTaskFragmentCreated();
        final TaskFragmentInfo info1 = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken);

        // Create a TaskFragment with activity in untrusted mode.
        mTaskFragmentOrganizer.resetLatch();
        final TaskFragmentInfo info2 = createTaskFragment(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY);
        waitAndAssertResumedActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY,
                "Untrusted embedding activity must be resumed.");
        final Rect activityBounds = new Rect(mWmState
                .getActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).getBounds());

        // Reparent children of the untrusted TaskFragment to the TaskFragment with larger bounds.
        mTaskFragmentOrganizer.resetLatch();
        final IBinder errorCallbackToken = new Binder();
        final WindowContainerTransaction wct2 = new WindowContainerTransaction()
                .setErrorCallbackToken(errorCallbackToken)
                .reparentChildren(info2.getToken(), info1.getToken());
        mTaskFragmentOrganizer.applyTransaction(wct2);

        // It is disallowed to reparent children to TaskFragment with bounds outside of its parent
        // in untrusted mode.
        assertTaskFragmentError(errorCallbackToken, SecurityException.class);
        mWmState.waitForAppTransitionIdleOnDisplay(mOwnerActivity.getDisplayId());
        assertEquals(activityBounds,
                mWmState.getActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY).getBounds());
    }

    /**
     * Creates bounds for a container that would appear on top and partially occlude the provided
     * one.
     */
    @NonNull
    private Rect partialOverlayBounds(@NonNull TaskFragmentInfo info) {
        final Rect baseBounds = info.getConfiguration().windowConfiguration.getBounds();
        final Rect result = new Rect(baseBounds);
        result.inset(50 /* left */, 50 /* top */, 50 /* right */, 50 /* bottom */);
        return result;
    }

    /** Asserts that the organizer received an error callback. */
    private void assertTaskFragmentError(@NonNull IBinder errorCallbackToken,
            @NonNull Class<? extends Throwable> exceptionClass) {
        mTaskFragmentOrganizer.waitForTaskFragmentError();
        assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf(exceptionClass);
        assertThat(mTaskFragmentOrganizer.getErrorCallbackToken()).isEqualTo(errorCallbackToken);
    }

    public static class TranslucentActivity extends FocusableActivity {}
}