1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.quickstep.util;
18 
19 import android.app.ActivityTaskManager;
20 import android.app.IActivityTaskManager;
21 import android.app.IAssistDataReceiver;
22 import android.app.assist.AssistContent;
23 import android.content.Context;
24 import android.graphics.Bitmap;
25 import android.os.Bundle;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import com.android.launcher3.util.Executors;
30 
31 import java.lang.ref.WeakReference;
32 import java.util.Collections;
33 import java.util.Map;
34 import java.util.WeakHashMap;
35 import java.util.concurrent.Executor;
36 
37 /**
38  * Can be used to request the AssistContent from a provided task id, useful for getting the web uri
39  * if provided from the task.
40  */
41 public class AssistContentRequester {
42     private static final String TAG = "AssistContentRequester";
43     private static final String ASSIST_KEY_CONTENT = "content";
44 
45     /** For receiving content, called on the main thread. */
46     public interface Callback {
47         /**
48          * Called when the {@link android.app.assist.AssistContent} of the requested task is
49          * available.
50          **/
onAssistContentAvailable(AssistContent assistContent)51         void onAssistContentAvailable(AssistContent assistContent);
52     }
53 
54     private final IActivityTaskManager mActivityTaskManager;
55     private final String mAttributionTag;
56     private final String mPackageName;
57     private final Executor mCallbackExecutor;
58     private final Executor mSystemInteractionExecutor;
59 
60     // If system loses the callback, our internal cache of original callback will also get cleared.
61     private final Map<Object, Callback> mPendingCallbacks =
62             Collections.synchronizedMap(new WeakHashMap<>());
63 
AssistContentRequester(Context context)64     public AssistContentRequester(Context context) {
65         mActivityTaskManager = ActivityTaskManager.getService();
66         mAttributionTag = context.getAttributionTag();
67         mPackageName = context.getApplicationContext().getPackageName();
68         mCallbackExecutor = Executors.MAIN_EXECUTOR;
69         mSystemInteractionExecutor = Executors.UI_HELPER_EXECUTOR;
70     }
71 
72     /**
73      * Request the {@link AssistContent} from the task with the provided id.
74      *
75      * @param taskId to query for the content.
76      * @param callback to call when the content is available, called on the main thread.
77      */
requestAssistContent(final int taskId, final Callback callback)78     public void requestAssistContent(final int taskId, final Callback callback) {
79         // ActivityTaskManager interaction here is synchronous, so call off the main thread.
80         mSystemInteractionExecutor.execute(() -> {
81             try {
82                 mActivityTaskManager.requestAssistDataForTask(
83                         new AssistDataReceiver(callback, this), taskId, mPackageName,
84                         mAttributionTag);
85             } catch (RemoteException e) {
86                 Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
87             }
88         });
89     }
90 
executeOnMainExecutor(Runnable callback)91     private void executeOnMainExecutor(Runnable callback) {
92         mCallbackExecutor.execute(callback);
93     }
94 
95     private static final class AssistDataReceiver extends IAssistDataReceiver.Stub {
96 
97         // The AssistDataReceiver binder callback object is passed to a system server, that may
98         // keep hold of it for longer than the lifetime of the AssistContentRequester object,
99         // potentially causing a memory leak. In the callback passed to the system server, only
100         // keep a weak reference to the parent object and lookup its callback if it still exists.
101         private final WeakReference<AssistContentRequester> mParentRef;
102         private final Object mCallbackKey = new Object();
103 
AssistDataReceiver(Callback callback, AssistContentRequester parent)104         AssistDataReceiver(Callback callback, AssistContentRequester parent) {
105             parent.mPendingCallbacks.put(mCallbackKey, callback);
106             mParentRef = new WeakReference<>(parent);
107         }
108 
109         @Override
onHandleAssistData(Bundle data)110         public void onHandleAssistData(Bundle data) {
111             if (data == null) {
112                 return;
113             }
114 
115             final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
116             if (content == null) {
117                 Log.e(TAG, "Received AssistData, but no AssistContent found");
118                 return;
119             }
120 
121             AssistContentRequester requester = mParentRef.get();
122             if (requester != null) {
123                 Callback callback = requester.mPendingCallbacks.get(mCallbackKey);
124                 if (callback != null) {
125                     requester.executeOnMainExecutor(
126                             () -> callback.onAssistContentAvailable(content));
127                 } else {
128                     Log.d(TAG, "Callback received after calling UI was disposed of");
129                 }
130             } else {
131                 Log.d(TAG, "Callback received after Requester was collected");
132             }
133         }
134 
135         @Override
onHandleAssistScreenshot(Bitmap screenshot)136         public void onHandleAssistScreenshot(Bitmap screenshot) {}
137     }
138 }
139