/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.am; import static android.app.ActivityManager.ASSIST_CONTEXT_FULL; import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_NONE; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IAssistDataReceiver; import android.content.Context; import android.graphics.Bitmap; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.view.IWindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** * Helper class to asynchronously fetch the assist data and screenshot from the current running * activities. It manages received data and calls back to the owner when the owner is ready to * receive the data itself. */ public class AssistDataRequester extends IAssistDataReceiver.Stub { public static final String KEY_RECEIVER_EXTRA_COUNT = "count"; public static final String KEY_RECEIVER_EXTRA_INDEX = "index"; private IActivityManager mService; private IWindowManager mWindowManager; private Context mContext; private AppOpsManager mAppOpsManager; private AssistDataRequesterCallbacks mCallbacks; private Object mCallbacksLock; private int mRequestStructureAppOps; private int mRequestScreenshotAppOps; private boolean mCanceled; private int mPendingDataCount; private int mPendingScreenshotCount; private final ArrayList mAssistData = new ArrayList<>(); private final ArrayList mAssistScreenshot = new ArrayList<>(); /** * Interface to handle the events from the fetcher. */ public interface AssistDataRequesterCallbacks { /** * @return whether the currently received assist data can be handled by the callbacks. */ @GuardedBy("mCallbacksLock") boolean canHandleReceivedAssistDataLocked(); /** * Called when we receive asynchronous assist data. This call is only made if the * {@param fetchData} argument to requestAssistData() is true, and if the current activity * allows assist data to be fetched. In addition, the callback will be made with the * {@param mCallbacksLock} held, and only if {@link #canHandleReceivedAssistDataLocked()} * is true. */ @GuardedBy("mCallbacksLock") void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount); /** * Called when we receive asynchronous assist screenshot. This call is only made if * {@param fetchScreenshot} argument to requestAssistData() is true, and if the current * activity allows assist data to be fetched. In addition, the callback will be made with * the {@param mCallbacksLock} held, and only if * {@link #canHandleReceivedAssistDataLocked()} is true. */ @GuardedBy("mCallbacksLock") void onAssistScreenshotReceivedLocked(Bitmap screenshot); /** * Called when there is no more pending assist data or screenshots for the last request. * If the request was canceled, then this callback will not be made. In addition, the * callback will be made with the {@param mCallbacksLock} held, and only if * {@link #canHandleReceivedAssistDataLocked()} is true. */ @GuardedBy("mCallbacksLock") default void onAssistRequestCompleted() { // Do nothing } } /** * @param callbacks The callbacks to handle the asynchronous reply with the assist data. * @param callbacksLock The lock for the requester to hold when calling any of the * {@param callbacks}. The owner should also take care in locking * appropriately when calling into this requester. * @param requestStructureAppOps The app ops to check before requesting the assist structure * @param requestScreenshotAppOps The app ops to check before requesting the assist screenshot. * This can be {@link AppOpsManager#OP_NONE} to indicate that * screenshots should never be fetched. */ public AssistDataRequester(Context context, IActivityManager service, IWindowManager windowManager, AppOpsManager appOpsManager, AssistDataRequesterCallbacks callbacks, Object callbacksLock, int requestStructureAppOps, int requestScreenshotAppOps) { mCallbacks = callbacks; mCallbacksLock = callbacksLock; mWindowManager = windowManager; mService = service; mContext = context; mAppOpsManager = appOpsManager; mRequestStructureAppOps = requestStructureAppOps; mRequestScreenshotAppOps = requestScreenshotAppOps; } /** * Request that assist data be loaded asynchronously. The resulting data will be provided * through the {@link AssistDataRequesterCallbacks}. * * @param activityTokens the list of visible activities * @param fetchData whether or not to fetch the assist data, only applies if the caller is * allowed to fetch the assist data, and the current activity allows assist data to be * fetched from it * @param fetchScreenshot whether or not to fetch the screenshot, only applies if fetchData is * true, the caller is allowed to fetch the assist data, and the current activity allows * assist data to be fetched from it * @param allowFetchData to be joined with other checks, determines whether or not the requester * is allowed to fetch the assist data * @param allowFetchScreenshot to be joined with other checks, determines whether or not the * requester is allowed to fetch the assist screenshot */ public void requestAssistData(List activityTokens, final boolean fetchData, final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot, int callingUid, String callingPackage) { // TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity, // then no assist data is requested for any of the other activities // Early exit if there are no activity to fetch for if (activityTokens.isEmpty()) { // No activities, just dispatch request-complete tryDispatchRequestComplete(); return; } // Ensure that the current activity supports assist data boolean isAssistDataAllowed = false; try { isAssistDataAllowed = mService.isAssistDataAllowedOnCurrentActivity(); } catch (RemoteException e) { // Should never happen } allowFetchData &= isAssistDataAllowed; allowFetchScreenshot &= fetchData && isAssistDataAllowed && (mRequestScreenshotAppOps != OP_NONE); mCanceled = false; mPendingDataCount = 0; mPendingScreenshotCount = 0; mAssistData.clear(); mAssistScreenshot.clear(); if (fetchData) { if (mAppOpsManager.checkOpNoThrow(mRequestStructureAppOps, callingUid, callingPackage) == MODE_ALLOWED && allowFetchData) { final int numActivities = activityTokens.size(); for (int i = 0; i < numActivities; i++) { IBinder topActivity = activityTokens.get(i); try { MetricsLogger.count(mContext, "assist_with_context", 1); Bundle receiverExtras = new Bundle(); receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i); receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, numActivities); if (mService.requestAssistContextExtras(ASSIST_CONTEXT_FULL, this, receiverExtras, topActivity, /* focused= */ i == 0, /* newSessionId= */ i == 0)) { mPendingDataCount++; } else if (i == 0) { // Wasn't allowed... given that, let's not do the screenshot either. if (mCallbacks.canHandleReceivedAssistDataLocked()) { dispatchAssistDataReceived(null); } else { mAssistData.add(null); } allowFetchScreenshot = false; break; } } catch (RemoteException e) { // Can't happen } } } else { // Wasn't allowed... given that, let's not do the screenshot either. if (mCallbacks.canHandleReceivedAssistDataLocked()) { dispatchAssistDataReceived(null); } else { mAssistData.add(null); } allowFetchScreenshot = false; } } if (fetchScreenshot) { if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage) == MODE_ALLOWED && allowFetchScreenshot) { try { MetricsLogger.count(mContext, "assist_with_screen", 1); mPendingScreenshotCount++; mWindowManager.requestAssistScreenshot(this); } catch (RemoteException e) { // Can't happen } } else { if (mCallbacks.canHandleReceivedAssistDataLocked()) { dispatchAssistScreenshotReceived(null); } else { mAssistScreenshot.add(null); } } } // For the cases where we dispatch null data/screenshot due to permissions, just dispatch // request-complete after those are made tryDispatchRequestComplete(); } /** * This call should only be made when the callbacks are capable of handling the received assist * data. The owner is also responsible for locking before calling this method. */ public void processPendingAssistData() { flushPendingAssistData(); tryDispatchRequestComplete(); } private void flushPendingAssistData() { final int dataCount = mAssistData.size(); for (int i = 0; i < dataCount; i++) { dispatchAssistDataReceived(mAssistData.get(i)); } mAssistData.clear(); final int screenshotsCount = mAssistScreenshot.size(); for (int i = 0; i < screenshotsCount; i++) { dispatchAssistScreenshotReceived(mAssistScreenshot.get(i)); } mAssistScreenshot.clear(); } public int getPendingDataCount() { return mPendingDataCount; } public int getPendingScreenshotCount() { return mPendingScreenshotCount; } /** * Cancels the current request for the assist data. */ public void cancel() { // Reset the pending data count, if we receive new assist data after this point, it will // be ignored mCanceled = true; mPendingDataCount = 0; mPendingScreenshotCount = 0; mAssistData.clear(); mAssistScreenshot.clear(); } @Override public void onHandleAssistData(Bundle data) { synchronized (mCallbacksLock) { if (mCanceled) { return; } mPendingDataCount--; if (mCallbacks.canHandleReceivedAssistDataLocked()) { // Process any pending data and dispatch the new data as well flushPendingAssistData(); dispatchAssistDataReceived(data); tryDispatchRequestComplete(); } else { // Queue up the data for processing later mAssistData.add(data); } } } @Override public void onHandleAssistScreenshot(Bitmap screenshot) { synchronized (mCallbacksLock) { if (mCanceled) { return; } mPendingScreenshotCount--; if (mCallbacks.canHandleReceivedAssistDataLocked()) { // Process any pending data and dispatch the new data as well flushPendingAssistData(); dispatchAssistScreenshotReceived(screenshot); tryDispatchRequestComplete(); } else { // Queue up the data for processing later mAssistScreenshot.add(screenshot); } } } private void dispatchAssistDataReceived(Bundle data) { int activityIndex = 0; int activityCount = 0; final Bundle receiverExtras = data != null ? data.getBundle(ASSIST_KEY_RECEIVER_EXTRAS) : null; if (receiverExtras != null) { activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX); activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT); } mCallbacks.onAssistDataReceivedLocked(data, activityIndex, activityCount); } private void dispatchAssistScreenshotReceived(Bitmap screenshot) { mCallbacks.onAssistScreenshotReceivedLocked(screenshot); } private void tryDispatchRequestComplete() { if (mPendingDataCount == 0 && mPendingScreenshotCount == 0 && mAssistData.isEmpty() && mAssistScreenshot.isEmpty()) { mCallbacks.onAssistRequestCompleted(); } } public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("mPendingDataCount="); pw.println(mPendingDataCount); pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData); pw.print(prefix); pw.print("mPendingScreenshotCount="); pw.println(mPendingScreenshotCount); pw.print(prefix); pw.print("mAssistScreenshot="); pw.println(mAssistScreenshot); } }