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