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