[contextualsearch] implement assist data request

Test: added cts
Bug: 309657924
Change-Id: Ic13f20104f6a083ad4dad1f0e3a6e31529b16392
diff --git a/core/java/android/app/contextualsearch/CallbackToken.java b/core/java/android/app/contextualsearch/CallbackToken.java
index 378193f..94cdc73 100644
--- a/core/java/android/app/contextualsearch/CallbackToken.java
+++ b/core/java/android/app/contextualsearch/CallbackToken.java
@@ -68,6 +68,8 @@
      * invocations of this method will result in {@link OutcomeReceiver#onError} being called with
      * an {@link IllegalAccessException}.
      *
+     * Note that the callback could be invoked multiple times, e.g. in the case of split screen.
+     *
      * @param executor The executor which will be used to invoke the callback.
      * @param callback The callback which will be used to return {@link ContextualSearchState}
      *                 if/when it is available via {@link OutcomeReceiver#onResult}. It will also be
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index 16a9933..028a1f9 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -17,6 +17,8 @@
 package com.android.server.contextualsearch;
 
 import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH;
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
 import static android.content.Context.CONTEXTUAL_SEARCH_SERVICE;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
@@ -24,11 +26,17 @@
 import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
+import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.ActivityOptions;
+import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
 import android.app.contextualsearch.CallbackToken;
 import android.app.contextualsearch.ContextualSearchManager;
 import android.app.contextualsearch.ContextualSearchState;
@@ -37,6 +45,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.os.Binder;
@@ -49,15 +58,19 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.util.Log;
 import android.util.Slog;
+import android.view.IWindowManager;
 import android.window.ScreenCapture;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.am.AssistDataRequester;
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
 import com.android.server.wm.ActivityAssistInfo;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -68,19 +81,66 @@
 import java.util.Objects;
 
 public class ContextualSearchManagerService extends SystemService {
-
+    private static final String TAG = ContextualSearchManagerService.class.getSimpleName();
     private static final int MSG_RESET_TEMPORARY_PACKAGE = 0;
     private static final int MAX_TEMP_PACKAGE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
     private final Context mContext;
     private final ActivityTaskManagerInternal mAtmInternal;
+    private final PackageManagerInternal mPackageManager;
     private final WindowManagerInternal mWmInternal;
     private final DevicePolicyManagerInternal mDpmInternal;
+    private final Object mLock = new Object();
+    private final AssistDataRequester mAssistDataRequester;
 
-    private Handler mTemporaryHandler;
+    private final AssistDataRequesterCallbacks mAssistDataCallbacks =
+            new AssistDataRequesterCallbacks() {
+                @Override
+                public boolean canHandleReceivedAssistDataLocked() {
+                    synchronized (mLock) {
+                        return mStateCallback != null;
+                    }
+                }
+
+                @Override
+                public void onAssistDataReceivedLocked(
+                        final Bundle data,
+                        final int activityIndex,
+                        final int activityCount) {
+                    final IContextualSearchCallback callback;
+                    synchronized (mLock) {
+                        callback = mStateCallback;
+                    }
+
+                    if (callback != null) {
+                        try {
+                            callback.onResult(new ContextualSearchState(
+                                    data.getParcelable(ASSIST_KEY_STRUCTURE, AssistStructure.class),
+                                    data.getParcelable(ASSIST_KEY_CONTENT, AssistContent.class),
+                                    data));
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error invoking ContextualSearchCallback", e);
+                        }
+                    } else {
+                        Log.w(TAG, "Callback went away!");
+                    }
+                }
+
+                @Override
+                public void onAssistRequestCompleted() {
+                    synchronized (mLock) {
+                        mStateCallback = null;
+                    }
+                }
+            };
 
     @GuardedBy("this")
+    private Handler mTemporaryHandler;
+    @GuardedBy("this")
     private String mTemporaryPackage = null;
-    private static final String TAG = ContextualSearchManagerService.class.getSimpleName();
+
+    @GuardedBy("mLock")
+    private IContextualSearchCallback mStateCallback;
 
     public ContextualSearchManagerService(@NonNull Context context) {
         super(context);
@@ -88,8 +148,14 @@
         mContext = context;
         mAtmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityTaskManagerInternal.class));
+        mPackageManager = LocalServices.getService(PackageManagerInternal.class);
         mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class));
         mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
+        mAssistDataRequester = new AssistDataRequester(
+                mContext,
+                IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)),
+                mContext.getSystemService(AppOpsManager.class),
+                mAssistDataCallbacks, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
     }
 
     @Override
@@ -178,18 +244,37 @@
         launchIntent.putExtra(ContextualSearchManager.EXTRA_TOKEN, mToken);
         boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed();
         final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities();
+        final List<IBinder> activityTokens = new ArrayList<>(records.size());
         ArrayList<String> visiblePackageNames = new ArrayList<>();
         boolean isManagedProfileVisible = false;
         for (ActivityAssistInfo record : records) {
             // Add the package name to the list only if assist data is allowed.
             if (isAssistDataAllowed) {
                 visiblePackageNames.add(record.getComponentName().getPackageName());
+                activityTokens.add(record.getActivityToken());
             }
             if (mDpmInternal != null
                     && mDpmInternal.isUserOrganizationManaged(record.getUserId())) {
                 isManagedProfileVisible = true;
             }
         }
+        if (isAssistDataAllowed) {
+            try {
+                final String csPackage = Objects.requireNonNull(launchIntent.getPackage());
+                final int csUid = mPackageManager.getPackageUid(csPackage, 0, 0);
+                mAssistDataRequester.requestAssistData(
+                        activityTokens,
+                        /* fetchData */ true,
+                        /* fetchScreenshot */ false,
+                        /* allowFetchData */ true,
+                        /* allowFetchScreenshot */ false,
+                        csUid,
+                        csPackage,
+                        null);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not request assist data", e);
+            }
+        }
         final ScreenCapture.ScreenshotHardwareBuffer shb;
         if (mWmInternal != null) {
             shb = mWmInternal.takeAssistScreenshot();
@@ -264,6 +349,7 @@
             synchronized (this) {
                 if (DEBUG_USER) Log.d(TAG, "startContextualSearch");
                 enforcePermission("startContextualSearch");
+                mAssistDataRequester.cancel();
                 mToken = new CallbackToken();
                 // We get the launch intent with the system server's identity because the system
                 // server has READ_FRAME_BUFFER permission to get the screenshot and because only
@@ -298,17 +384,19 @@
                 return;
             }
             mToken = null;
-            // Process data request
-            try {
-                callback.onResult(new ContextualSearchState(null, null, Bundle.EMPTY));
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not invoke onResult callback", e);
+            synchronized (mLock) {
+                mStateCallback = callback;
             }
+            mAssistDataRequester.processPendingAssistData();
         }
 
-        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
-                @Nullable FileDescriptor err, @NonNull String[] args,
-                @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
+        public void onShellCommand(
+                @Nullable FileDescriptor in,
+                @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err,
+                @NonNull String[] args,
+                @Nullable ShellCallback callback,
+                @NonNull ResultReceiver resultReceiver) {
             new ContextualSearchManagerShellCommand(ContextualSearchManagerService.this)
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }