1/ Refactor out logic to fetch assist data asynchronously
- This logic will also be used when the recents component is started to fetch assist data associated with the current activity. Bug: 67864419 Test: com.android.server.am.AssistDataRequesterTest Test: CtsVoiceInteractionTestCases Change-Id: I4cb3c2536a104d7071bc0a1fd6e31dfc2c0ce8d0
This commit is contained in:
@@ -380,7 +380,7 @@ LOCAL_SRC_FILES += \
|
||||
core/java/android/speech/tts/ITextToSpeechService.aidl \
|
||||
core/java/com/android/internal/app/IAppOpsCallback.aidl \
|
||||
core/java/com/android/internal/app/IAppOpsService.aidl \
|
||||
core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl \
|
||||
core/java/com/android/internal/app/IAssistDataReceiver.aidl \
|
||||
core/java/com/android/internal/app/IBatteryStats.aidl \
|
||||
core/java/com/android/internal/app/ISoundTriggerService.aidl \
|
||||
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
|
||||
|
||||
@@ -63,6 +63,7 @@ import android.os.ParcelFileDescriptor;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.StrictMode;
|
||||
import android.service.voice.IVoiceInteractionSession;
|
||||
import com.android.internal.app.IAssistDataReceiver;
|
||||
import com.android.internal.app.IVoiceInteractor;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.internal.policy.IKeyguardDismissCallback;
|
||||
@@ -453,7 +454,7 @@ interface IActivityManager {
|
||||
void notifyCleartextNetwork(int uid, in byte[] firstPacket);
|
||||
int createStackOnDisplay(int displayId);
|
||||
void setTaskResizeable(int taskId, int resizeableMode);
|
||||
boolean requestAssistContextExtras(int requestType, in IResultReceiver receiver,
|
||||
boolean requestAssistContextExtras(int requestType, in IAssistDataReceiver receiver,
|
||||
in Bundle receiverExtras, in IBinder activityToken,
|
||||
boolean focused, boolean newSessionId);
|
||||
void resizeTask(int taskId, in Rect bounds, int resizeMode);
|
||||
@@ -617,7 +618,7 @@ interface IActivityManager {
|
||||
boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
|
||||
void unregisterTaskStackListener(ITaskStackListener listener);
|
||||
void moveStackToDisplay(int stackId, int displayId);
|
||||
boolean requestAutofillData(in IResultReceiver receiver, in Bundle receiverExtras,
|
||||
boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
|
||||
in IBinder activityToken, int flags);
|
||||
void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
|
||||
int restartUserInBackground(int userId);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package android.view;
|
||||
|
||||
import com.android.internal.app.IAssistScreenshotReceiver;
|
||||
import com.android.internal.app.IAssistDataReceiver;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.internal.view.IInputContext;
|
||||
import com.android.internal.view.IInputMethodClient;
|
||||
@@ -272,7 +272,7 @@ interface IWindowManager
|
||||
/**
|
||||
* Used only for assist -- request a screenshot of the current application.
|
||||
*/
|
||||
boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver);
|
||||
boolean requestAssistScreenshot(IAssistDataReceiver receiver);
|
||||
|
||||
/**
|
||||
* Called by the status bar to notify Views of changes to System UI visiblity.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -17,8 +17,10 @@
|
||||
package com.android.internal.app;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
|
||||
/** @hide */
|
||||
oneway interface IAssistScreenshotReceiver {
|
||||
void send(in Bitmap screenshot);
|
||||
oneway interface IAssistDataReceiver {
|
||||
void onHandleAssistData(in Bundle resultData);
|
||||
void onHandleAssistScreenshot(in Bitmap screenshot);
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.metrics.LogMaker;
|
||||
import android.os.Binder;
|
||||
@@ -76,11 +77,11 @@ import android.view.autofill.IAutoFillManagerClient;
|
||||
import android.view.autofill.IAutofillWindowPresenter;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.app.IAssistDataReceiver;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.internal.os.HandlerCaller;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.server.autofill.ui.AutoFillUI;
|
||||
import com.android.server.autofill.ui.PendingUi;
|
||||
@@ -206,9 +207,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
/**
|
||||
* Receiver of assist data from the app's {@link Activity}.
|
||||
*/
|
||||
private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
|
||||
private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
|
||||
@Override
|
||||
public void send(int resultCode, Bundle resultData) throws RemoteException {
|
||||
public void onHandleAssistData(Bundle resultData) throws RemoteException {
|
||||
final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
|
||||
if (structure == null) {
|
||||
Slog.e(TAG, "No assist structure - app might have crashed providing it");
|
||||
@@ -264,6 +265,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
|
||||
mRemoteFillService.onFillRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHandleAssistScreenshot(Bitmap screenshot) {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.server.am;
|
||||
|
||||
import static android.Manifest.permission.BIND_VOICE_INTERACTION;
|
||||
import static android.Manifest.permission.CHANGE_CONFIGURATION;
|
||||
import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
|
||||
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
|
||||
@@ -365,6 +366,7 @@ import com.android.internal.app.AssistUtils;
|
||||
import com.android.internal.app.DumpHeapActivity;
|
||||
import com.android.internal.app.IAppOpsCallback;
|
||||
import com.android.internal.app.IAppOpsService;
|
||||
import com.android.internal.app.IAssistDataReceiver;
|
||||
import com.android.internal.app.IVoiceInteractor;
|
||||
import com.android.internal.app.ProcessMap;
|
||||
import com.android.internal.app.SystemUserHomeActivity;
|
||||
@@ -775,7 +777,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
public final Bundle extras;
|
||||
public final Intent intent;
|
||||
public final String hint;
|
||||
public final IResultReceiver receiver;
|
||||
public final IAssistDataReceiver receiver;
|
||||
public final int userHandle;
|
||||
public boolean haveResult = false;
|
||||
public Bundle result = null;
|
||||
@@ -784,7 +786,8 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
public Bundle receiverExtras;
|
||||
|
||||
public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
|
||||
String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
|
||||
String _hint, IAssistDataReceiver _receiver, Bundle _receiverExtras,
|
||||
int _userHandle) {
|
||||
activity = _activity;
|
||||
extras = _extras;
|
||||
intent = _intent;
|
||||
@@ -4667,15 +4670,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
Intent intent, String resolvedType, IVoiceInteractionSession session,
|
||||
IVoiceInteractor interactor, int startFlags, ProfilerInfo profilerInfo,
|
||||
Bundle bOptions, int userId) {
|
||||
if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
String msg = "Permission Denial: startVoiceActivity() from pid="
|
||||
+ Binder.getCallingPid()
|
||||
+ ", uid=" + Binder.getCallingUid()
|
||||
+ " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION;
|
||||
Slog.w(TAG, msg);
|
||||
throw new SecurityException(msg);
|
||||
}
|
||||
enforceCallingPermission(BIND_VOICE_INTERACTION, "startVoiceActivity()");
|
||||
if (session == null || interactor == null) {
|
||||
throw new NullPointerException("null session or interactor");
|
||||
}
|
||||
@@ -4690,15 +4685,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
@Override
|
||||
public int startAssistantActivity(String callingPackage, int callingPid, int callingUid,
|
||||
Intent intent, String resolvedType, Bundle bOptions, int userId) {
|
||||
if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
final String msg = "Permission Denial: startAssistantActivity() from pid="
|
||||
+ Binder.getCallingPid()
|
||||
+ ", uid=" + Binder.getCallingUid()
|
||||
+ " requires " + Manifest.permission.BIND_VOICE_INTERACTION;
|
||||
Slog.w(TAG, msg);
|
||||
throw new SecurityException(msg);
|
||||
}
|
||||
enforceCallingPermission(BIND_VOICE_INTERACTION, "startAssistantActivity()");
|
||||
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
|
||||
ALLOW_FULL_ONLY, "startAssistantActivity", null);
|
||||
return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent,
|
||||
@@ -12913,7 +12900,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestAssistContextExtras(int requestType, IResultReceiver receiver,
|
||||
public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
|
||||
Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
|
||||
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
|
||||
activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
|
||||
@@ -12921,7 +12908,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestAutofillData(IResultReceiver receiver, Bundle receiverExtras,
|
||||
public boolean requestAutofillData(IAssistDataReceiver receiver, Bundle receiverExtras,
|
||||
IBinder activityToken, int flags) {
|
||||
return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null,
|
||||
receiver, receiverExtras, activityToken, true, true, UserHandle.getCallingUserId(),
|
||||
@@ -12929,7 +12916,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
}
|
||||
|
||||
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
|
||||
IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
|
||||
IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
|
||||
boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
|
||||
int flags) {
|
||||
enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
|
||||
@@ -12997,7 +12984,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
}
|
||||
|
||||
void pendingAssistExtrasTimedOut(PendingAssistExtras pae) {
|
||||
IResultReceiver receiver;
|
||||
IAssistDataReceiver receiver;
|
||||
synchronized (this) {
|
||||
mPendingAssistExtras.remove(pae);
|
||||
receiver = pae.receiver;
|
||||
@@ -13009,7 +12996,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
|
||||
pae.receiverExtras);
|
||||
try {
|
||||
pae.receiver.send(0, sendBundle);
|
||||
pae.receiver.onHandleAssistData(sendBundle);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
@@ -13047,7 +13034,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
}
|
||||
}
|
||||
// We are now ready to launch the assist activity.
|
||||
IResultReceiver sendReceiver = null;
|
||||
IAssistDataReceiver sendReceiver = null;
|
||||
Bundle sendBundle = null;
|
||||
synchronized (this) {
|
||||
buildAssistBundleLocked(pae, extras);
|
||||
@@ -13069,7 +13056,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
}
|
||||
if (sendReceiver != null) {
|
||||
try {
|
||||
sendReceiver.send(0, sendBundle);
|
||||
sendReceiver.onHandleAssistData(sendBundle);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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 android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.IActivityManager;
|
||||
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.app.IAssistDataReceiver;
|
||||
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<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")
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
public void requestAssistData(List<IBinder> activityTokens, boolean fetchData,
|
||||
boolean fetchScreenshot, int callingUid, String callingPackage) {
|
||||
// TODO: Better handle the cancel case if a request can be reused
|
||||
// TODO: 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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the current activity supports assist data
|
||||
boolean isAssistDataAllowed = false;
|
||||
try {
|
||||
isAssistDataAllowed = mService.isAssistDataAllowedOnCurrentActivity();
|
||||
} catch (RemoteException e) {
|
||||
// Should never happen
|
||||
}
|
||||
fetchData &= isAssistDataAllowed;
|
||||
fetchScreenshot &= 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) {
|
||||
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.
|
||||
dispatchAssistDataReceived(null);
|
||||
fetchScreenshot = false;
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// Can't happen
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Wasn't allowed... given that, let's not do the screenshot either.
|
||||
dispatchAssistDataReceived(null);
|
||||
fetchScreenshot = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (fetchScreenshot) {
|
||||
if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage)
|
||||
== MODE_ALLOWED) {
|
||||
try {
|
||||
MetricsLogger.count(mContext, "assist_with_screen", 1);
|
||||
mPendingScreenshotCount++;
|
||||
mWindowManager.requestAssistScreenshot(this);
|
||||
} catch (RemoteException e) {
|
||||
// Can't happen
|
||||
}
|
||||
} else {
|
||||
dispatchAssistScreenshotReceived(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This call should only be made when the callbacks are capable of handling the received assist
|
||||
* data.
|
||||
*/
|
||||
public void processPendingAssistData() {
|
||||
final int dataCount = mAssistData.size();
|
||||
for (int i = 0; i < dataCount; i++) {
|
||||
dispatchAssistDataReceived(mAssistData.get(i));
|
||||
}
|
||||
final int screenshotsCount = mAssistScreenshot.size();
|
||||
for (int i = 0; i < screenshotsCount; i++) {
|
||||
dispatchAssistScreenshotReceived(mAssistScreenshot.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
processPendingAssistData();
|
||||
dispatchAssistDataReceived(data);
|
||||
} 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
|
||||
processPendingAssistData();
|
||||
dispatchAssistScreenshotReceived(screenshot);
|
||||
} 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(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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -226,7 +226,7 @@ import android.view.animation.Animation;
|
||||
import android.view.inputmethod.InputMethodManagerInternal;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.app.IAssistScreenshotReceiver;
|
||||
import com.android.internal.app.IAssistDataReceiver;
|
||||
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.internal.policy.IKeyguardDismissCallback;
|
||||
@@ -3783,7 +3783,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
* of the target image.
|
||||
*/
|
||||
@Override
|
||||
public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) {
|
||||
public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
|
||||
if (!checkCallingPermission(READ_FRAME_BUFFER,
|
||||
"requestAssistScreenshot()")) {
|
||||
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
|
||||
@@ -3795,7 +3795,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
|
||||
false /* includeDecor */);
|
||||
try {
|
||||
receiver.send(bm);
|
||||
receiver.onHandleAssistScreenshot(bm);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* 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.AppOpsManager.MODE_ALLOWED;
|
||||
import static android.app.AppOpsManager.MODE_ERRORED;
|
||||
import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
|
||||
import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
|
||||
import static android.graphics.Bitmap.Config.ARGB_8888;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.IActivityManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.MediumTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.util.Log;
|
||||
import android.view.IWindowManager;
|
||||
|
||||
import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Note: Currently, we only support fetching the screenshot for the current application, so the
|
||||
* screenshot checks are hardcoded accordingly.
|
||||
*
|
||||
* runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
|
||||
*/
|
||||
@MediumTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AssistDataRequesterTest extends ActivityTestsBase {
|
||||
|
||||
private static final String TAG = AssistDataRequesterTest.class.getSimpleName();
|
||||
|
||||
private static final boolean CURRENT_ACTIVITY_ASSIST_ALLOWED = true;
|
||||
private static final boolean CALLER_ASSIST_STRUCTURE_ALLOWED = true;
|
||||
private static final boolean CALLER_ASSIST_SCREENSHOT_ALLOWED = true;
|
||||
private static final boolean FETCH_DATA = true;
|
||||
private static final boolean FETCH_SCREENSHOTS = true;
|
||||
|
||||
private static final int TEST_UID = 0;
|
||||
private static final String TEST_PACKAGE = "";
|
||||
|
||||
private Context mContext;
|
||||
private AssistDataRequester mDataRequester;
|
||||
private Callbacks mCallbacks;
|
||||
private Object mCallbacksLock;
|
||||
private Handler mHandler;
|
||||
private IActivityManager mAm;
|
||||
private IWindowManager mWm;
|
||||
private AppOpsManager mAppOpsManager;
|
||||
|
||||
/**
|
||||
* The requests to fetch assist data are done incrementally from the text thread, and we
|
||||
* immediately post onto the main thread handler below, which would immediately make the
|
||||
* callback and decrement the pending counts. In order to assert the pending counts, we defer
|
||||
* the callbacks on the test-side until after we flip the gate, after which we can drain the
|
||||
* main thread handler and make assertions on the actual callbacks
|
||||
*/
|
||||
private CountDownLatch mGate;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mAm = mock(IActivityManager.class);
|
||||
mWm = mock(IWindowManager.class);
|
||||
mAppOpsManager = mock(AppOpsManager.class);
|
||||
mContext = InstrumentationRegistry.getContext();
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mCallbacksLock = new Object();
|
||||
mCallbacks = new Callbacks();
|
||||
mDataRequester = new AssistDataRequester(mContext, mAm, mWm, mAppOpsManager, mCallbacks,
|
||||
mCallbacksLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
|
||||
|
||||
// Gate the continuation of the assist data callbacks until we are ready within the tests
|
||||
mGate = new CountDownLatch(1);
|
||||
doAnswer(invocation -> {
|
||||
mHandler.post(() -> {
|
||||
try {
|
||||
mGate.await(10, TimeUnit.SECONDS);
|
||||
mDataRequester.onHandleAssistData(new Bundle());
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Failed to wait", e);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), anyBoolean(),
|
||||
anyBoolean());
|
||||
doAnswer(invocation -> {
|
||||
mHandler.post(() -> {
|
||||
try {
|
||||
mGate.await(10, TimeUnit.SECONDS);
|
||||
mDataRequester.onHandleAssistScreenshot(Bitmap.createBitmap(1, 1,
|
||||
ARGB_8888));
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Failed to wait", e);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}).when(mWm).requestAssistScreenshot(any());
|
||||
}
|
||||
|
||||
private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
|
||||
boolean assistScreenshotAllowed) throws Exception {
|
||||
doReturn(currentActivityAssistAllowed).when(mAm).isAssistDataAllowedOnCurrentActivity();
|
||||
doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
|
||||
.checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString());
|
||||
doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
|
||||
.checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestData() throws Exception {
|
||||
setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
|
||||
CALLER_ASSIST_SCREENSHOT_ALLOWED);
|
||||
|
||||
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
|
||||
TEST_UID, TEST_PACKAGE);
|
||||
assertReceivedDataCount(5, 5, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyActivities_expectNoCallbacks() throws Exception {
|
||||
setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
|
||||
CALLER_ASSIST_SCREENSHOT_ALLOWED);
|
||||
|
||||
mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS,
|
||||
TEST_UID, TEST_PACKAGE);
|
||||
assertReceivedDataCount(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentAppDisallow_expectNoCallbacks() throws Exception {
|
||||
setupMocks(!CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
|
||||
CALLER_ASSIST_SCREENSHOT_ALLOWED);
|
||||
|
||||
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
|
||||
TEST_UID, TEST_PACKAGE);
|
||||
assertReceivedDataCount(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessPendingData() throws Exception {
|
||||
setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
|
||||
CALLER_ASSIST_SCREENSHOT_ALLOWED);
|
||||
|
||||
mCallbacks.canHandleReceivedData = false;
|
||||
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
|
||||
TEST_UID, TEST_PACKAGE);
|
||||
assertTrue(mDataRequester.getPendingDataCount() == 5);
|
||||
assertTrue(mDataRequester.getPendingScreenshotCount() == 1);
|
||||
mGate.countDown();
|
||||
waitForIdle(mHandler);
|
||||
|
||||
// Callbacks still not ready to receive, but all pending data is received
|
||||
assertTrue(mDataRequester.getPendingDataCount() == 0);
|
||||
assertTrue(mDataRequester.getPendingScreenshotCount() == 0);
|
||||
assertTrue(mCallbacks.receivedData.isEmpty());
|
||||
assertTrue(mCallbacks.receivedScreenshots.isEmpty());
|
||||
|
||||
mCallbacks.canHandleReceivedData = true;
|
||||
mDataRequester.processPendingAssistData();
|
||||
assertTrue(mCallbacks.receivedData.size() == 5);
|
||||
assertTrue(mCallbacks.receivedScreenshots.size() == 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFetchData_expectNoCallbacks() throws Exception {
|
||||
setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
|
||||
CALLER_ASSIST_SCREENSHOT_ALLOWED);
|
||||
|
||||
mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS,
|
||||
TEST_UID, TEST_PACKAGE);
|
||||
assertReceivedDataCount(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisallowAssistStructure_expectNullDataCallbacks() throws Exception {
|
||||
setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED,
|
||||
CALLER_ASSIST_SCREENSHOT_ALLOWED);
|
||||
|
||||
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
|
||||
TEST_UID, TEST_PACKAGE);
|
||||
// Expect a single null data when the appops is denied
|
||||
assertReceivedDataCount(0, 1, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisallowAssistContextExtras_expectNullDataCallbacks() throws Exception {
|
||||
setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
|
||||
CALLER_ASSIST_SCREENSHOT_ALLOWED);
|
||||
doReturn(false).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(),
|
||||
anyBoolean(), anyBoolean());
|
||||
|
||||
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
|
||||
TEST_UID, TEST_PACKAGE);
|
||||
// Expect a single null data when requestAssistContextExtras() fails
|
||||
assertReceivedDataCount(0, 1, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception {
|
||||
setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
|
||||
CALLER_ASSIST_SCREENSHOT_ALLOWED);
|
||||
|
||||
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS,
|
||||
TEST_UID, TEST_PACKAGE);
|
||||
assertReceivedDataCount(5, 5, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception {
|
||||
setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
|
||||
!CALLER_ASSIST_SCREENSHOT_ALLOWED);
|
||||
|
||||
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
|
||||
TEST_UID, TEST_PACKAGE);
|
||||
// Expect a single null screenshot when the appops is denied
|
||||
assertReceivedDataCount(5, 5, 0, 1);
|
||||
}
|
||||
|
||||
private void assertReceivedDataCount(int numPendingData, int numReceivedData,
|
||||
int numPendingScreenshots, int numReceivedScreenshots) throws Exception {
|
||||
assertTrue("Expected " + numPendingData + " pending data, got "
|
||||
+ mDataRequester.getPendingDataCount(),
|
||||
mDataRequester.getPendingDataCount() == numPendingData);
|
||||
assertTrue("Expected " + numPendingScreenshots + " pending screenshots, got "
|
||||
+ mDataRequester.getPendingScreenshotCount(),
|
||||
mDataRequester.getPendingScreenshotCount() == numPendingScreenshots);
|
||||
mGate.countDown();
|
||||
waitForIdle(mHandler);
|
||||
assertTrue("Expected " + numReceivedData + " data, received "
|
||||
+ mCallbacks.receivedData.size(),
|
||||
mCallbacks.receivedData.size() == numReceivedData);
|
||||
assertTrue("Expected " + numReceivedScreenshots + " screenshots, received "
|
||||
+ mCallbacks.receivedScreenshots.size(),
|
||||
mCallbacks.receivedScreenshots.size() == numReceivedScreenshots);
|
||||
}
|
||||
|
||||
private List<IBinder> createActivityList(int size) {
|
||||
ArrayList<IBinder> activities = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
activities.add(mock(IBinder.class));
|
||||
}
|
||||
return activities;
|
||||
}
|
||||
|
||||
public void waitForIdle(Handler h) throws Exception {
|
||||
if (Looper.myLooper() == h.getLooper()) {
|
||||
throw new RuntimeException("This method can not be called from the waiting looper");
|
||||
}
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
h.post(() -> latch.countDown());
|
||||
latch.await(2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private static class Callbacks implements AssistDataRequesterCallbacks {
|
||||
|
||||
boolean canHandleReceivedData = true;
|
||||
ArrayList<Bundle> receivedData = new ArrayList<>();
|
||||
ArrayList<Bitmap> receivedScreenshots = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public boolean canHandleReceivedAssistDataLocked() {
|
||||
return canHandleReceivedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
|
||||
receivedData.add(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
|
||||
receivedScreenshots.add(screenshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ import com.android.server.LocalServices;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
|
||||
@@ -162,13 +163,16 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
|
||||
mInfo.getServiceInfo().applicationInfo.uid, mHandler);
|
||||
}
|
||||
List<IBinder> activityTokens = null;
|
||||
if (activityToken == null) {
|
||||
if (activityToken != null) {
|
||||
activityTokens = new ArrayList<>();
|
||||
activityTokens.add(activityToken);
|
||||
} else {
|
||||
// Let's get top activities from all visible stacks
|
||||
activityTokens = LocalServices.getService(ActivityManagerInternal.class)
|
||||
.getTopVisibleActivities();
|
||||
}
|
||||
return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
|
||||
activityToken, activityTokens);
|
||||
activityTokens);
|
||||
}
|
||||
|
||||
public boolean hideSessionLocked() {
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
package com.android.server.voiceinteraction;
|
||||
|
||||
import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
|
||||
import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
|
||||
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
||||
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.IActivityManager;
|
||||
@@ -43,31 +50,24 @@ import android.service.voice.VoiceInteractionService;
|
||||
import android.service.voice.VoiceInteractionSession;
|
||||
import android.util.Slog;
|
||||
import android.view.IWindowManager;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.app.AssistUtils;
|
||||
import com.android.internal.app.IAssistScreenshotReceiver;
|
||||
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
|
||||
import com.android.internal.app.IVoiceInteractor;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.am.AssistDataRequester;
|
||||
import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
|
||||
import com.android.server.statusbar.StatusBarManagerInternal;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
|
||||
|
||||
final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
final class VoiceInteractionSessionConnection implements ServiceConnection,
|
||||
AssistDataRequesterCallbacks {
|
||||
|
||||
final static String TAG = "VoiceInteractionServiceManager";
|
||||
|
||||
private static final String KEY_RECEIVER_EXTRA_COUNT = "count";
|
||||
private static final String KEY_RECEIVER_EXTRA_INDEX = "index";
|
||||
|
||||
final IBinder mToken = new Binder();
|
||||
final Object mLock;
|
||||
final ComponentName mSessionComponentName;
|
||||
@@ -90,27 +90,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
IVoiceInteractionSessionService mService;
|
||||
IVoiceInteractionSession mSession;
|
||||
IVoiceInteractor mInteractor;
|
||||
boolean mHaveAssistData;
|
||||
int mPendingAssistDataCount;
|
||||
ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>();
|
||||
boolean mHaveScreenshot;
|
||||
Bitmap mScreenshot;
|
||||
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
|
||||
|
||||
static class AssistDataForActivity {
|
||||
int activityIndex;
|
||||
int activityCount;
|
||||
Bundle data;
|
||||
|
||||
public AssistDataForActivity(Bundle data) {
|
||||
this.data = data;
|
||||
Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
|
||||
if (receiverExtras != null) {
|
||||
activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
|
||||
activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
|
||||
}
|
||||
}
|
||||
}
|
||||
AssistDataRequester mAssistDataRequester;
|
||||
|
||||
IVoiceInteractionSessionShowCallback mShowCallback =
|
||||
new IVoiceInteractionSessionShowCallback.Stub() {
|
||||
@@ -146,32 +127,6 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
}
|
||||
};
|
||||
|
||||
final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
|
||||
@Override
|
||||
public void send(int resultCode, Bundle resultData) throws RemoteException {
|
||||
synchronized (mLock) {
|
||||
if (mShown) {
|
||||
mHaveAssistData = true;
|
||||
mAssistData.add(new AssistDataForActivity(resultData));
|
||||
deliverSessionDataLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
|
||||
@Override
|
||||
public void send(Bitmap screenshot) throws RemoteException {
|
||||
synchronized (mLock) {
|
||||
if (mShown) {
|
||||
mHaveScreenshot = true;
|
||||
mScreenshot = screenshot;
|
||||
deliverSessionDataLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
|
||||
Context context, Callback callback, int callingUid, Handler handler) {
|
||||
mLock = lock;
|
||||
@@ -185,6 +140,9 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
mIWindowManager = IWindowManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.WINDOW_SERVICE));
|
||||
mAppOps = context.getSystemService(AppOpsManager.class);
|
||||
mAssistDataRequester = new AssistDataRequester(mContext, mAm, mIWindowManager,
|
||||
(AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
|
||||
this, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
|
||||
IBinder permOwner = null;
|
||||
try {
|
||||
permOwner = mAm.newUriPermissionOwner("voicesession:"
|
||||
@@ -224,8 +182,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
}
|
||||
|
||||
public boolean showLocked(Bundle args, int flags, int disabledContext,
|
||||
IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken,
|
||||
List<IBinder> topActivities) {
|
||||
IVoiceInteractionSessionShowCallback showCallback, List<IBinder> topActivities) {
|
||||
if (mBound) {
|
||||
if (!mFullyBound) {
|
||||
mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
|
||||
@@ -233,75 +190,18 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
| Context.BIND_FOREGROUND_SERVICE,
|
||||
new UserHandle(mUser));
|
||||
}
|
||||
|
||||
mShown = true;
|
||||
boolean isAssistDataAllowed = true;
|
||||
try {
|
||||
isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity();
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
disabledContext |= getUserDisabledShowContextLocked();
|
||||
boolean structureEnabled = isAssistDataAllowed
|
||||
&& (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0;
|
||||
boolean screenshotEnabled = isAssistDataAllowed && structureEnabled
|
||||
&& (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0;
|
||||
mShowArgs = args;
|
||||
mShowFlags = flags;
|
||||
mHaveAssistData = false;
|
||||
mPendingAssistDataCount = 0;
|
||||
boolean needDisclosure = false;
|
||||
if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
|
||||
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
|
||||
mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
|
||||
&& structureEnabled) {
|
||||
mAssistData.clear();
|
||||
final int count = activityToken != null ? 1 : topActivities.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
IBinder topActivity = count == 1 ? activityToken : topActivities.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, count);
|
||||
if (mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
|
||||
mAssistReceiver, receiverExtras, topActivity,
|
||||
/* focused= */ i == 0, /* newSessionId= */ i == 0)) {
|
||||
needDisclosure = true;
|
||||
mPendingAssistDataCount++;
|
||||
} else if (i == 0) {
|
||||
// Wasn't allowed... given that, let's not do the screenshot either.
|
||||
mHaveAssistData = true;
|
||||
mAssistData.clear();
|
||||
screenshotEnabled = false;
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mHaveAssistData = true;
|
||||
mAssistData.clear();
|
||||
}
|
||||
} else {
|
||||
mAssistData.clear();
|
||||
}
|
||||
mHaveScreenshot = false;
|
||||
if ((flags&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0) {
|
||||
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
|
||||
mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
|
||||
&& screenshotEnabled) {
|
||||
try {
|
||||
MetricsLogger.count(mContext, "assist_with_screen", 1);
|
||||
needDisclosure = true;
|
||||
mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
} else {
|
||||
mHaveScreenshot = true;
|
||||
mScreenshot = null;
|
||||
}
|
||||
} else {
|
||||
mScreenshot = null;
|
||||
}
|
||||
|
||||
mAssistDataRequester.requestAssistData(topActivities,
|
||||
(disabledContext & VoiceInteractionSession.SHOW_WITH_ASSIST) == 0,
|
||||
(disabledContext & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0,
|
||||
mCallingUid, mSessionComponentName.getPackageName());
|
||||
|
||||
boolean needDisclosure = mAssistDataRequester.getPendingDataCount() > 0
|
||||
|| mAssistDataRequester.getPendingScreenshotCount() > 0;
|
||||
if (needDisclosure && AssistUtils.shouldDisclose(mContext, mSessionComponentName)) {
|
||||
mHandler.post(mShowAssistDisclosureRunnable);
|
||||
}
|
||||
@@ -312,7 +212,6 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
mShowFlags = 0;
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
deliverSessionDataLocked();
|
||||
} else if (showCallback != null) {
|
||||
mPendingShowCallbacks.add(showCallback);
|
||||
}
|
||||
@@ -328,6 +227,59 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandleReceivedAssistDataLocked() {
|
||||
return mSession != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
|
||||
if (data == null) {
|
||||
try {
|
||||
mSession.handleAssist(null, null, null, 0, 0);
|
||||
} catch (RemoteException e) {
|
||||
// Can't happen
|
||||
}
|
||||
} else {
|
||||
final Bundle assistData = data.getBundle(VoiceInteractionSession.KEY_DATA);
|
||||
final AssistStructure structure = data.getParcelable(
|
||||
VoiceInteractionSession.KEY_STRUCTURE);
|
||||
final AssistContent content = data.getParcelable(
|
||||
VoiceInteractionSession.KEY_CONTENT);
|
||||
final int uid = data.getInt(Intent.EXTRA_ASSIST_UID, -1);
|
||||
if (uid >= 0 && content != null) {
|
||||
Intent intent = content.getIntent();
|
||||
if (intent != null) {
|
||||
ClipData clipData = intent.getClipData();
|
||||
if (clipData != null && Intent.isAccessUriMode(intent.getFlags())) {
|
||||
grantClipDataPermissions(clipData, intent.getFlags(), uid,
|
||||
mCallingUid, mSessionComponentName.getPackageName());
|
||||
}
|
||||
}
|
||||
ClipData clipData = content.getClipData();
|
||||
if (clipData != null) {
|
||||
grantClipDataPermissions(clipData, FLAG_GRANT_READ_URI_PERMISSION,
|
||||
uid, mCallingUid, mSessionComponentName.getPackageName());
|
||||
}
|
||||
}
|
||||
try {
|
||||
mSession.handleAssist(assistData, structure, content, activityIndex,
|
||||
activityCount);
|
||||
} catch (RemoteException e) {
|
||||
// Can't happen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
|
||||
try {
|
||||
mSession.handleScreenshot(screenshot);
|
||||
} catch (RemoteException e) {
|
||||
// Can't happen
|
||||
}
|
||||
}
|
||||
|
||||
void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
|
||||
if (!"content".equals(uri.getScheme())) {
|
||||
return;
|
||||
@@ -341,7 +293,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
|
||||
uri = ContentProvider.getUriWithoutUserId(uri);
|
||||
mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
|
||||
uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
|
||||
uri, FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
|
||||
} catch (RemoteException e) {
|
||||
} catch (SecurityException e) {
|
||||
Slog.w(TAG, "Can't propagate permission", e);
|
||||
@@ -370,89 +322,13 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
}
|
||||
}
|
||||
|
||||
void deliverSessionDataLocked() {
|
||||
if (mSession == null) {
|
||||
return;
|
||||
}
|
||||
if (mHaveAssistData) {
|
||||
AssistDataForActivity assistData;
|
||||
if (mAssistData.isEmpty()) {
|
||||
// We're not actually going to get any data, deliver some nothing
|
||||
try {
|
||||
mSession.handleAssist(null, null, null, 0, 0);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
} else {
|
||||
while (!mAssistData.isEmpty()) {
|
||||
if (mPendingAssistDataCount <= 0) {
|
||||
Slog.e(TAG, "mPendingAssistDataCount is " + mPendingAssistDataCount);
|
||||
}
|
||||
mPendingAssistDataCount--;
|
||||
assistData = mAssistData.remove(0);
|
||||
if (assistData.data == null) {
|
||||
try {
|
||||
mSession.handleAssist(null, null, null, assistData.activityIndex,
|
||||
assistData.activityCount);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
} else {
|
||||
deliverSessionDataLocked(assistData);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mPendingAssistDataCount <= 0) {
|
||||
mHaveAssistData = false;
|
||||
} // else, more to come
|
||||
}
|
||||
if (mHaveScreenshot) {
|
||||
try {
|
||||
mSession.handleScreenshot(mScreenshot);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
mScreenshot = null;
|
||||
mHaveScreenshot = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverSessionDataLocked(AssistDataForActivity assistDataForActivity) {
|
||||
Bundle assistData = assistDataForActivity.data.getBundle(
|
||||
VoiceInteractionSession.KEY_DATA);
|
||||
AssistStructure structure = assistDataForActivity.data.getParcelable(
|
||||
VoiceInteractionSession.KEY_STRUCTURE);
|
||||
AssistContent content = assistDataForActivity.data.getParcelable(
|
||||
VoiceInteractionSession.KEY_CONTENT);
|
||||
int uid = assistDataForActivity.data.getInt(Intent.EXTRA_ASSIST_UID, -1);
|
||||
if (uid >= 0 && content != null) {
|
||||
Intent intent = content.getIntent();
|
||||
if (intent != null) {
|
||||
ClipData data = intent.getClipData();
|
||||
if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
|
||||
grantClipDataPermissions(data, intent.getFlags(), uid,
|
||||
mCallingUid, mSessionComponentName.getPackageName());
|
||||
}
|
||||
}
|
||||
ClipData data = content.getClipData();
|
||||
if (data != null) {
|
||||
grantClipDataPermissions(data,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
||||
uid, mCallingUid, mSessionComponentName.getPackageName());
|
||||
}
|
||||
}
|
||||
try {
|
||||
mSession.handleAssist(assistData, structure, content,
|
||||
assistDataForActivity.activityIndex, assistDataForActivity.activityCount);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hideLocked() {
|
||||
if (mBound) {
|
||||
if (mShown) {
|
||||
mShown = false;
|
||||
mShowArgs = null;
|
||||
mShowFlags = 0;
|
||||
mHaveAssistData = false;
|
||||
mAssistData.clear();
|
||||
mAssistDataRequester.cancel();
|
||||
mPendingShowCallbacks.clear();
|
||||
if (mSession != null) {
|
||||
try {
|
||||
@@ -462,8 +338,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
}
|
||||
try {
|
||||
mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
||||
FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION,
|
||||
mUser);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
@@ -529,7 +404,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
mShowFlags = 0;
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
deliverSessionDataLocked();
|
||||
mAssistDataRequester.processPendingAssistData();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -587,10 +462,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
|
||||
pw.print(prefix); pw.print("mSession="); pw.println(mSession);
|
||||
pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
|
||||
}
|
||||
pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
|
||||
if (mHaveAssistData) {
|
||||
pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
|
||||
}
|
||||
mAssistDataRequester.dump(prefix, pw);
|
||||
}
|
||||
|
||||
private Runnable mShowAssistDisclosureRunnable = new Runnable() {
|
||||
|
||||
Reference in New Issue
Block a user