blob: d8d6528621d2a4d70110e7e9d1f2e89881c98f81 [file] [log] [blame]
/*
* 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.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_NONE;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.IActivityTaskManager;
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.annotations.VisibleForTesting;
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 IWindowManager mWindowManager;
@VisibleForTesting
public IActivityTaskManager mActivityTaskManager;
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<Bundle> mAssistData = new ArrayList<>();
private final ArrayList<Bitmap> 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")
default void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
// Do nothing
}
/**
* 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")
default void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
// Do nothing
}
/**
* 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,
IWindowManager windowManager, AppOpsManager appOpsManager,
AssistDataRequesterCallbacks callbacks, Object callbacksLock,
int requestStructureAppOps, int requestScreenshotAppOps) {
mCallbacks = callbacks;
mCallbacksLock = callbacksLock;
mWindowManager = windowManager;
mActivityTaskManager = ActivityTaskManager.getService();
mContext = context;
mAppOpsManager = appOpsManager;
mRequestStructureAppOps = requestStructureAppOps;
mRequestScreenshotAppOps = requestScreenshotAppOps;
}
/**
* Request that autofill data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
* See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
*/
public void requestAutofillData(List<IBinder> activityTokens, int callingUid,
String callingPackage) {
requestData(activityTokens, true /* requestAutofillData */,
true /* fetchData */, false /* fetchScreenshot */,
true /* allowFetchData */, false /* allowFetchScreenshot */,
callingUid, callingPackage);
}
/**
* Request that assist data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
* See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
*/
public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
int callingUid, String callingPackage) {
requestData(activityTokens, false /* requestAutofillData */, fetchData, fetchScreenshot,
allowFetchData, allowFetchScreenshot, callingUid, callingPackage);
}
/**
* 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 requestAutofillData if true, will fetch the autofill data, otherwise, will fetch the
* assist context data
* @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
*/
private void requestData(List<IBinder> activityTokens, final boolean requestAutofillData,
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 = mActivityTaskManager.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);
boolean result = requestAutofillData
? mActivityTaskManager.requestAutofillData(this,
receiverExtras, topActivity, 0 /* flags */)
: mActivityTaskManager.requestAssistContextExtras(
ASSIST_CONTEXT_FULL, this, receiverExtras, topActivity,
/* focused= */ i == 0, /* newSessionId= */ i == 0);
if (result) {
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);
}
}