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