1/*
2 * Copyright (C) 2017 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
17package com.android.server.am;
18
19import static android.app.AppOpsManager.MODE_ALLOWED;
20import static android.app.AppOpsManager.MODE_ERRORED;
21import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
22import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
23import static android.graphics.Bitmap.Config.ARGB_8888;
24
25import static org.junit.Assert.assertFalse;
26import static org.junit.Assert.assertTrue;
27import static org.mockito.ArgumentMatchers.any;
28import static org.mockito.ArgumentMatchers.anyBoolean;
29import static org.mockito.ArgumentMatchers.anyInt;
30import static org.mockito.ArgumentMatchers.anyString;
31import static org.mockito.ArgumentMatchers.eq;
32import static org.mockito.Mockito.doAnswer;
33import static org.mockito.Mockito.doReturn;
34import static org.mockito.Mockito.mock;
35
36import android.app.AppOpsManager;
37import android.app.IActivityManager;
38import android.content.Context;
39import android.graphics.Bitmap;
40import android.os.Bundle;
41import android.os.Handler;
42import android.os.IBinder;
43import android.os.Looper;
44import android.support.test.InstrumentationRegistry;
45import android.support.test.filters.MediumTest;
46import android.support.test.runner.AndroidJUnit4;
47import android.util.Log;
48import android.view.IWindowManager;
49
50import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
51
52import org.junit.Before;
53import org.junit.Test;
54import org.junit.runner.RunWith;
55
56import java.util.ArrayList;
57import java.util.List;
58import java.util.concurrent.CountDownLatch;
59import java.util.concurrent.TimeUnit;
60
61/**
62 * Note: Currently, we only support fetching the screenshot for the current application, so the
63 * screenshot checks are hardcoded accordingly.
64 *
65 * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
66 */
67@MediumTest
68@RunWith(AndroidJUnit4.class)
69public class AssistDataRequesterTest extends ActivityTestsBase {
70
71    private static final String TAG = AssistDataRequesterTest.class.getSimpleName();
72
73    private static final boolean CURRENT_ACTIVITY_ASSIST_ALLOWED = true;
74    private static final boolean CALLER_ASSIST_STRUCTURE_ALLOWED = true;
75    private static final boolean CALLER_ASSIST_SCREENSHOT_ALLOWED = true;
76    private static final boolean FETCH_DATA = true;
77    private static final boolean FETCH_SCREENSHOTS = true;
78    private static final boolean ALLOW_FETCH_DATA = true;
79    private static final boolean ALLOW_FETCH_SCREENSHOTS = true;
80
81    private static final int TEST_UID = 0;
82    private static final String TEST_PACKAGE = "";
83
84    private Context mContext;
85    private AssistDataRequester mDataRequester;
86    private Callbacks mCallbacks;
87    private Object mCallbacksLock;
88    private Handler mHandler;
89    private IActivityManager mAm;
90    private IWindowManager mWm;
91    private AppOpsManager mAppOpsManager;
92
93    /**
94     * The requests to fetch assist data are done incrementally from the text thread, and we
95     * immediately post onto the main thread handler below, which would immediately make the
96     * callback and decrement the pending counts. In order to assert the pending counts, we defer
97     * the callbacks on the test-side until after we flip the gate, after which we can drain the
98     * main thread handler and make assertions on the actual callbacks
99     */
100    private CountDownLatch mGate;
101
102    @Before
103    @Override
104    public void setUp() throws Exception {
105        super.setUp();
106        mAm = mock(IActivityManager.class);
107        mWm = mock(IWindowManager.class);
108        mAppOpsManager = mock(AppOpsManager.class);
109        mContext =  InstrumentationRegistry.getContext();
110        mHandler = new Handler(Looper.getMainLooper());
111        mCallbacksLock = new Object();
112        mCallbacks = new Callbacks();
113        mDataRequester = new AssistDataRequester(mContext, mAm, mWm, mAppOpsManager, mCallbacks,
114                mCallbacksLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
115
116        // Gate the continuation of the assist data callbacks until we are ready within the tests
117        mGate = new CountDownLatch(1);
118        doAnswer(invocation -> {
119            mHandler.post(() -> {
120                try {
121                    mGate.await(10, TimeUnit.SECONDS);
122                    mDataRequester.onHandleAssistData(new Bundle());
123                } catch (InterruptedException e) {
124                    Log.e(TAG, "Failed to wait", e);
125                }
126            });
127            return true;
128        }).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), anyBoolean(),
129                anyBoolean());
130        doAnswer(invocation -> {
131            mHandler.post(() -> {
132                try {
133                    mGate.await(10, TimeUnit.SECONDS);
134                    mDataRequester.onHandleAssistScreenshot(Bitmap.createBitmap(1, 1, ARGB_8888));
135                } catch (InterruptedException e) {
136                    Log.e(TAG, "Failed to wait", e);
137                }
138            });
139            return true;
140        }).when(mWm).requestAssistScreenshot(any());
141    }
142
143    private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
144            boolean assistScreenshotAllowed) throws Exception {
145        doReturn(currentActivityAssistAllowed).when(mAm).isAssistDataAllowedOnCurrentActivity();
146        doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
147                .checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString());
148        doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
149                .checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString());
150    }
151
152    @Test
153    public void testRequestData() throws Exception {
154        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
155                CALLER_ASSIST_SCREENSHOT_ALLOWED);
156
157        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
158                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
159        assertReceivedDataCount(5, 5, 1, 1);
160    }
161
162    @Test
163    public void testEmptyActivities_expectNoCallbacks() throws Exception {
164        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
165                CALLER_ASSIST_SCREENSHOT_ALLOWED);
166
167        mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS,
168                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
169        assertReceivedDataCount(0, 0, 0, 0);
170    }
171
172    @Test
173    public void testCurrentAppDisallow_expectNullCallbacks() throws Exception {
174        setupMocks(!CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
175                CALLER_ASSIST_SCREENSHOT_ALLOWED);
176
177        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
178                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
179        assertReceivedDataCount(0, 1, 0, 1);
180    }
181
182    @Test
183    public void testProcessPendingData() throws Exception {
184        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
185                CALLER_ASSIST_SCREENSHOT_ALLOWED);
186
187        mCallbacks.canHandleReceivedData = false;
188        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
189                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
190        assertTrue(mDataRequester.getPendingDataCount() == 5);
191        assertTrue(mDataRequester.getPendingScreenshotCount() == 1);
192        mGate.countDown();
193        waitForIdle(mHandler);
194
195        // Callbacks still not ready to receive, but all pending data is received
196        assertTrue(mDataRequester.getPendingDataCount() == 0);
197        assertTrue(mDataRequester.getPendingScreenshotCount() == 0);
198        assertTrue(mCallbacks.receivedData.isEmpty());
199        assertTrue(mCallbacks.receivedScreenshots.isEmpty());
200        assertFalse(mCallbacks.requestCompleted);
201
202        mCallbacks.canHandleReceivedData = true;
203        mDataRequester.processPendingAssistData();
204        // Since we are posting the callback for the request-complete, flush the handler as well
205        mGate.countDown();
206        waitForIdle(mHandler);
207        assertTrue(mCallbacks.receivedData.size() == 5);
208        assertTrue(mCallbacks.receivedScreenshots.size() == 1);
209        assertTrue(mCallbacks.requestCompleted);
210
211        // Clear the state and ensure that we only process pending data once
212        mCallbacks.reset();
213        mDataRequester.processPendingAssistData();
214        assertTrue(mCallbacks.receivedData.isEmpty());
215        assertTrue(mCallbacks.receivedScreenshots.isEmpty());
216    }
217
218    @Test
219    public void testNoFetchData_expectNoDataCallbacks() throws Exception {
220        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
221                CALLER_ASSIST_SCREENSHOT_ALLOWED);
222
223        mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS,
224                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
225        assertReceivedDataCount(0, 0, 0, 1);
226    }
227
228    @Test
229    public void testDisallowAssistStructure_expectNullDataCallbacks() throws Exception {
230        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED,
231                CALLER_ASSIST_SCREENSHOT_ALLOWED);
232
233        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
234                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
235        // Expect a single null data when the appops is denied
236        assertReceivedDataCount(0, 1, 0, 1);
237    }
238
239    @Test
240    public void testDisallowAssistContextExtras_expectNullDataCallbacks() throws Exception {
241        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
242                CALLER_ASSIST_SCREENSHOT_ALLOWED);
243        doReturn(false).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(),
244                anyBoolean(), anyBoolean());
245
246        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
247                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
248        // Expect a single null data when requestAssistContextExtras() fails
249        assertReceivedDataCount(0, 1, 0, 1);
250    }
251
252    @Test
253    public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception {
254        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
255                CALLER_ASSIST_SCREENSHOT_ALLOWED);
256
257        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS,
258                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
259        assertReceivedDataCount(5, 5, 0, 0);
260    }
261
262    @Test
263    public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception {
264        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
265                !CALLER_ASSIST_SCREENSHOT_ALLOWED);
266
267        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
268                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
269        // Expect a single null screenshot when the appops is denied
270        assertReceivedDataCount(5, 5, 0, 1);
271    }
272
273    @Test
274    public void testCanNotHandleReceivedData_expectNoCallbacks() throws Exception {
275        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED,
276                !CALLER_ASSIST_SCREENSHOT_ALLOWED);
277
278        mCallbacks.canHandleReceivedData = false;
279        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
280                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
281        mGate.countDown();
282        waitForIdle(mHandler);
283        assertTrue(mCallbacks.receivedData.isEmpty());
284        assertTrue(mCallbacks.receivedScreenshots.isEmpty());
285    }
286
287    @Test
288    public void testRequestDataNoneAllowed_expectNullCallbacks() throws Exception {
289        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
290                CALLER_ASSIST_SCREENSHOT_ALLOWED);
291
292        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
293                !ALLOW_FETCH_DATA, !ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
294        assertReceivedDataCount(0, 1, 0, 1);
295    }
296
297    private void assertReceivedDataCount(int numPendingData, int numReceivedData,
298            int numPendingScreenshots, int numReceivedScreenshots) throws Exception {
299        assertTrue("Expected " + numPendingData + " pending data, got "
300                        + mDataRequester.getPendingDataCount(),
301                mDataRequester.getPendingDataCount() == numPendingData);
302        assertTrue("Expected " + numPendingScreenshots + " pending screenshots, got "
303                        + mDataRequester.getPendingScreenshotCount(),
304                mDataRequester.getPendingScreenshotCount() == numPendingScreenshots);
305        assertFalse("Expected request NOT completed", mCallbacks.requestCompleted);
306        mGate.countDown();
307        waitForIdle(mHandler);
308        assertTrue("Expected " + numReceivedData + " data, received "
309                        + mCallbacks.receivedData.size(),
310                mCallbacks.receivedData.size() == numReceivedData);
311        assertTrue("Expected " + numReceivedScreenshots + " screenshots, received "
312                        + mCallbacks.receivedScreenshots.size(),
313                mCallbacks.receivedScreenshots.size() == numReceivedScreenshots);
314        assertTrue("Expected request completed", mCallbacks.requestCompleted);
315    }
316
317    private List<IBinder> createActivityList(int size) {
318        ArrayList<IBinder> activities = new ArrayList<>();
319        for (int i = 0; i < size; i++) {
320            activities.add(mock(IBinder.class));
321        }
322        return activities;
323    }
324
325    public void waitForIdle(Handler h) throws Exception {
326        if (Looper.myLooper() == h.getLooper()) {
327            throw new RuntimeException("This method can not be called from the waiting looper");
328        }
329        CountDownLatch latch = new CountDownLatch(1);
330        h.post(() -> latch.countDown());
331        latch.await(2, TimeUnit.SECONDS);
332    }
333
334    private class Callbacks implements AssistDataRequesterCallbacks {
335
336        boolean canHandleReceivedData = true;
337        boolean requestCompleted = false;
338        ArrayList<Bundle> receivedData = new ArrayList<>();
339        ArrayList<Bitmap> receivedScreenshots = new ArrayList<>();
340
341        void reset() {
342            canHandleReceivedData = true;
343            receivedData.clear();
344            receivedScreenshots.clear();
345        }
346
347        @Override
348        public boolean canHandleReceivedAssistDataLocked() {
349            return canHandleReceivedData;
350        }
351
352        @Override
353        public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
354            receivedData.add(data);
355        }
356
357        @Override
358        public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
359            receivedScreenshots.add(screenshot);
360        }
361
362        @Override
363        public void onAssistRequestCompleted() {
364            mHandler.post(() -> {
365                try {
366                    mGate.await(10, TimeUnit.SECONDS);
367                    requestCompleted = true;
368                } catch (InterruptedException e) {
369                    Log.e(TAG, "Failed to wait", e);
370                }
371            });
372        }
373    }
374}