Home | History | Annotate | Download | only in am
      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_FULL;
     20 import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
     21 import static android.app.AppOpsManager.MODE_ALLOWED;
     22 import static android.app.AppOpsManager.OP_NONE;
     23 
     24 import android.app.AppOpsManager;
     25 import android.app.IActivityManager;
     26 import android.app.IAssistDataReceiver;
     27 import android.content.Context;
     28 import android.graphics.Bitmap;
     29 import android.os.Bundle;
     30 import android.os.IBinder;
     31 import android.os.RemoteException;
     32 import android.view.IWindowManager;
     33 
     34 import com.android.internal.annotations.GuardedBy;
     35 import com.android.internal.logging.MetricsLogger;
     36 
     37 import java.io.PrintWriter;
     38 import java.util.ArrayList;
     39 import 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  */
     46 public 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 }