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.ActivityManager.ASSIST_CONTEXT_FULL; 20import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS; 21import static android.app.AppOpsManager.MODE_ALLOWED; 22import static android.app.AppOpsManager.OP_NONE; 23 24import android.app.AppOpsManager; 25import android.app.IActivityManager; 26import android.app.IAssistDataReceiver; 27import android.content.Context; 28import android.graphics.Bitmap; 29import android.os.Bundle; 30import android.os.IBinder; 31import android.os.RemoteException; 32import android.view.IWindowManager; 33 34import com.android.internal.annotations.GuardedBy; 35import com.android.internal.logging.MetricsLogger; 36 37import java.io.PrintWriter; 38import java.util.ArrayList; 39import java.util.List; 40 41/** 42 * Helper class to asynchronously fetch the assist data and screenshot from the current running 43 * activities. It manages received data and calls back to the owner when the owner is ready to 44 * receive the data itself. 45 */ 46public class AssistDataRequester extends IAssistDataReceiver.Stub { 47 48 public static final String KEY_RECEIVER_EXTRA_COUNT = "count"; 49 public static final String KEY_RECEIVER_EXTRA_INDEX = "index"; 50 51 private IActivityManager mService; 52 private IWindowManager mWindowManager; 53 private Context mContext; 54 private AppOpsManager mAppOpsManager; 55 56 private AssistDataRequesterCallbacks mCallbacks; 57 private Object mCallbacksLock; 58 59 private int mRequestStructureAppOps; 60 private int mRequestScreenshotAppOps; 61 private boolean mCanceled; 62 private int mPendingDataCount; 63 private int mPendingScreenshotCount; 64 private final ArrayList<Bundle> mAssistData = new ArrayList<>(); 65 private final ArrayList<Bitmap> mAssistScreenshot = new ArrayList<>(); 66 67 68 /** 69 * Interface to handle the events from the fetcher. 70 */ 71 public interface AssistDataRequesterCallbacks { 72 /** 73 * @return whether the currently received assist data can be handled by the callbacks. 74 */ 75 @GuardedBy("mCallbacksLock") 76 boolean canHandleReceivedAssistDataLocked(); 77 78 /** 79 * Called when we receive asynchronous assist data. This call is only made if the 80 * {@param fetchData} argument to requestAssistData() is true, and if the current activity 81 * allows assist data to be fetched. In addition, the callback will be made with the 82 * {@param mCallbacksLock} held, and only if {@link #canHandleReceivedAssistDataLocked()} 83 * is true. 84 */ 85 @GuardedBy("mCallbacksLock") 86 void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount); 87 88 /** 89 * Called when we receive asynchronous assist screenshot. This call is only made if 90 * {@param fetchScreenshot} argument to requestAssistData() is true, and if the current 91 * activity allows assist data to be fetched. In addition, the callback will be made with 92 * the {@param mCallbacksLock} held, and only if 93 * {@link #canHandleReceivedAssistDataLocked()} is true. 94 */ 95 @GuardedBy("mCallbacksLock") 96 void onAssistScreenshotReceivedLocked(Bitmap screenshot); 97 98 /** 99 * Called when there is no more pending assist data or screenshots for the last request. 100 * If the request was canceled, then this callback will not be made. In addition, the 101 * callback will be made with the {@param mCallbacksLock} held, and only if 102 * {@link #canHandleReceivedAssistDataLocked()} is true. 103 */ 104 @GuardedBy("mCallbacksLock") 105 default void onAssistRequestCompleted() { 106 // Do nothing 107 } 108 } 109 110 /** 111 * @param callbacks The callbacks to handle the asynchronous reply with the assist data. 112 * @param callbacksLock The lock for the requester to hold when calling any of the 113 * {@param callbacks}. The owner should also take care in locking 114 * appropriately when calling into this requester. 115 * @param requestStructureAppOps The app ops to check before requesting the assist structure 116 * @param requestScreenshotAppOps The app ops to check before requesting the assist screenshot. 117 * This can be {@link AppOpsManager#OP_NONE} to indicate that 118 * screenshots should never be fetched. 119 */ 120 public AssistDataRequester(Context context, IActivityManager service, 121 IWindowManager windowManager, AppOpsManager appOpsManager, 122 AssistDataRequesterCallbacks callbacks, Object callbacksLock, 123 int requestStructureAppOps, int requestScreenshotAppOps) { 124 mCallbacks = callbacks; 125 mCallbacksLock = callbacksLock; 126 mWindowManager = windowManager; 127 mService = service; 128 mContext = context; 129 mAppOpsManager = appOpsManager; 130 mRequestStructureAppOps = requestStructureAppOps; 131 mRequestScreenshotAppOps = requestScreenshotAppOps; 132 } 133 134 /** 135 * Request that assist data be loaded asynchronously. The resulting data will be provided 136 * through the {@link AssistDataRequesterCallbacks}. 137 * 138 * @param activityTokens the list of visible activities 139 * @param fetchData whether or not to fetch the assist data, only applies if the caller is 140 * allowed to fetch the assist data, and the current activity allows assist data to be 141 * fetched from it 142 * @param fetchScreenshot whether or not to fetch the screenshot, only applies if fetchData is 143 * true, the caller is allowed to fetch the assist data, and the current activity allows 144 * assist data to be fetched from it 145 * @param allowFetchData to be joined with other checks, determines whether or not the requester 146 * is allowed to fetch the assist data 147 * @param allowFetchScreenshot to be joined with other checks, determines whether or not the 148 * requester is allowed to fetch the assist screenshot 149 */ 150 public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData, 151 final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot, 152 int callingUid, String callingPackage) { 153 // TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity, 154 // then no assist data is requested for any of the other activities 155 156 // Early exit if there are no activity to fetch for 157 if (activityTokens.isEmpty()) { 158 // No activities, just dispatch request-complete 159 tryDispatchRequestComplete(); 160 return; 161 } 162 163 // Ensure that the current activity supports assist data 164 boolean isAssistDataAllowed = false; 165 try { 166 isAssistDataAllowed = mService.isAssistDataAllowedOnCurrentActivity(); 167 } catch (RemoteException e) { 168 // Should never happen 169 } 170 allowFetchData &= isAssistDataAllowed; 171 allowFetchScreenshot &= fetchData && isAssistDataAllowed 172 && (mRequestScreenshotAppOps != OP_NONE); 173 174 mCanceled = false; 175 mPendingDataCount = 0; 176 mPendingScreenshotCount = 0; 177 mAssistData.clear(); 178 mAssistScreenshot.clear(); 179 180 if (fetchData) { 181 if (mAppOpsManager.checkOpNoThrow(mRequestStructureAppOps, callingUid, callingPackage) 182 == MODE_ALLOWED && allowFetchData) { 183 final int numActivities = activityTokens.size(); 184 for (int i = 0; i < numActivities; i++) { 185 IBinder topActivity = activityTokens.get(i); 186 try { 187 MetricsLogger.count(mContext, "assist_with_context", 1); 188 Bundle receiverExtras = new Bundle(); 189 receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i); 190 receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, numActivities); 191 if (mService.requestAssistContextExtras(ASSIST_CONTEXT_FULL, this, 192 receiverExtras, topActivity, /* focused= */ i == 0, 193 /* newSessionId= */ i == 0)) { 194 mPendingDataCount++; 195 } else if (i == 0) { 196 // Wasn't allowed... given that, let's not do the screenshot either. 197 if (mCallbacks.canHandleReceivedAssistDataLocked()) { 198 dispatchAssistDataReceived(null); 199 } else { 200 mAssistData.add(null); 201 } 202 allowFetchScreenshot = false; 203 break; 204 } 205 } catch (RemoteException e) { 206 // Can't happen 207 } 208 } 209 } else { 210 // Wasn't allowed... given that, let's not do the screenshot either. 211 if (mCallbacks.canHandleReceivedAssistDataLocked()) { 212 dispatchAssistDataReceived(null); 213 } else { 214 mAssistData.add(null); 215 } 216 allowFetchScreenshot = false; 217 } 218 } 219 220 if (fetchScreenshot) { 221 if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage) 222 == MODE_ALLOWED && allowFetchScreenshot) { 223 try { 224 MetricsLogger.count(mContext, "assist_with_screen", 1); 225 mPendingScreenshotCount++; 226 mWindowManager.requestAssistScreenshot(this); 227 } catch (RemoteException e) { 228 // Can't happen 229 } 230 } else { 231 if (mCallbacks.canHandleReceivedAssistDataLocked()) { 232 dispatchAssistScreenshotReceived(null); 233 } else { 234 mAssistScreenshot.add(null); 235 } 236 } 237 } 238 // For the cases where we dispatch null data/screenshot due to permissions, just dispatch 239 // request-complete after those are made 240 tryDispatchRequestComplete(); 241 } 242 243 /** 244 * This call should only be made when the callbacks are capable of handling the received assist 245 * data. The owner is also responsible for locking before calling this method. 246 */ 247 public void processPendingAssistData() { 248 flushPendingAssistData(); 249 tryDispatchRequestComplete(); 250 } 251 252 private void flushPendingAssistData() { 253 final int dataCount = mAssistData.size(); 254 for (int i = 0; i < dataCount; i++) { 255 dispatchAssistDataReceived(mAssistData.get(i)); 256 } 257 mAssistData.clear(); 258 final int screenshotsCount = mAssistScreenshot.size(); 259 for (int i = 0; i < screenshotsCount; i++) { 260 dispatchAssistScreenshotReceived(mAssistScreenshot.get(i)); 261 } 262 mAssistScreenshot.clear(); 263 } 264 265 public int getPendingDataCount() { 266 return mPendingDataCount; 267 } 268 269 public int getPendingScreenshotCount() { 270 return mPendingScreenshotCount; 271 } 272 273 /** 274 * Cancels the current request for the assist data. 275 */ 276 public void cancel() { 277 // Reset the pending data count, if we receive new assist data after this point, it will 278 // be ignored 279 mCanceled = true; 280 mPendingDataCount = 0; 281 mPendingScreenshotCount = 0; 282 mAssistData.clear(); 283 mAssistScreenshot.clear(); 284 } 285 286 @Override 287 public void onHandleAssistData(Bundle data) { 288 synchronized (mCallbacksLock) { 289 if (mCanceled) { 290 return; 291 } 292 mPendingDataCount--; 293 294 if (mCallbacks.canHandleReceivedAssistDataLocked()) { 295 // Process any pending data and dispatch the new data as well 296 flushPendingAssistData(); 297 dispatchAssistDataReceived(data); 298 tryDispatchRequestComplete(); 299 } else { 300 // Queue up the data for processing later 301 mAssistData.add(data); 302 } 303 } 304 } 305 306 @Override 307 public void onHandleAssistScreenshot(Bitmap screenshot) { 308 synchronized (mCallbacksLock) { 309 if (mCanceled) { 310 return; 311 } 312 mPendingScreenshotCount--; 313 314 if (mCallbacks.canHandleReceivedAssistDataLocked()) { 315 // Process any pending data and dispatch the new data as well 316 flushPendingAssistData(); 317 dispatchAssistScreenshotReceived(screenshot); 318 tryDispatchRequestComplete(); 319 } else { 320 // Queue up the data for processing later 321 mAssistScreenshot.add(screenshot); 322 } 323 } 324 } 325 326 private void dispatchAssistDataReceived(Bundle data) { 327 int activityIndex = 0; 328 int activityCount = 0; 329 final Bundle receiverExtras = data != null 330 ? data.getBundle(ASSIST_KEY_RECEIVER_EXTRAS) : null; 331 if (receiverExtras != null) { 332 activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX); 333 activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT); 334 } 335 mCallbacks.onAssistDataReceivedLocked(data, activityIndex, activityCount); 336 } 337 338 private void dispatchAssistScreenshotReceived(Bitmap screenshot) { 339 mCallbacks.onAssistScreenshotReceivedLocked(screenshot); 340 } 341 342 private void tryDispatchRequestComplete() { 343 if (mPendingDataCount == 0 && mPendingScreenshotCount == 0 && 344 mAssistData.isEmpty() && mAssistScreenshot.isEmpty()) { 345 mCallbacks.onAssistRequestCompleted(); 346 } 347 } 348 349 public void dump(String prefix, PrintWriter pw) { 350 pw.print(prefix); pw.print("mPendingDataCount="); pw.println(mPendingDataCount); 351 pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData); 352 pw.print(prefix); pw.print("mPendingScreenshotCount="); pw.println(mPendingScreenshotCount); 353 pw.print(prefix); pw.print("mAssistScreenshot="); pw.println(mAssistScreenshot); 354 } 355}