1 /*
2  * Copyright (C) 2022 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.systemui.screenshot;
18 import android.annotation.Nullable;
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.systemui.dagger.SysUISingleton;
30 import com.android.systemui.dagger.qualifiers.Background;
31 import com.android.systemui.dagger.qualifiers.Main;
32 
33 import java.lang.ref.WeakReference;
34 import java.util.Collections;
35 import java.util.Map;
36 import java.util.WeakHashMap;
37 import java.util.concurrent.Executor;
38 
39 import javax.inject.Inject;
40 
41 /**
42  * Can be used to request the AssistContent from a provided task id, useful for getting the web uri
43  * if provided from the task.
44  *
45  * Forked from
46  * packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
47  */
48 @SysUISingleton
49 public class AssistContentRequester {
50     private static final String TAG = "AssistContentRequester";
51     private static final String ASSIST_KEY_CONTENT = "content";
52 
53     /** For receiving content, called on the main thread. */
54     public interface Callback {
55         /**
56          * Called when the {@link android.app.assist.AssistContent} of the requested task is
57          * available.
58          **/
onAssistContentAvailable(@ullable AssistContent assistContent)59         void onAssistContentAvailable(@Nullable AssistContent assistContent);
60     }
61 
62     private final IActivityTaskManager mActivityTaskManager;
63     private final String mPackageName;
64     private final Executor mCallbackExecutor;
65     private final Executor mSystemInteractionExecutor;
66     private final String mAttributionTag;
67 
68     // If system loses the callback, our internal cache of original callback will also get cleared.
69     private final Map<Object, Callback> mPendingCallbacks =
70             Collections.synchronizedMap(new WeakHashMap<>());
71 
72     @Inject
AssistContentRequester(Context context, @Main Executor mainExecutor, @Background Executor bgExecutor)73     public AssistContentRequester(Context context, @Main Executor mainExecutor,
74             @Background Executor bgExecutor) {
75         mActivityTaskManager = ActivityTaskManager.getService();
76         mPackageName = context.getApplicationContext().getPackageName();
77         mCallbackExecutor = mainExecutor;
78         mSystemInteractionExecutor = bgExecutor;
79         mAttributionTag = context.getAttributionTag();
80     }
81 
82     /**
83      * Request the {@link AssistContent} from the task with the provided id.
84      *
85      * @param taskId to query for the content.
86      * @param callback to call when the content is available, called on the main thread.
87      */
requestAssistContent(final int taskId, final Callback callback)88     public void requestAssistContent(final int taskId, final Callback callback) {
89         // ActivityTaskManager interaction here is synchronous, so call off the main thread.
90         mSystemInteractionExecutor.execute(() -> {
91             try {
92                 boolean success = mActivityTaskManager.requestAssistDataForTask(
93                         new AssistDataReceiver(callback, this), taskId, mPackageName,
94                         mAttributionTag);
95                 if (!success) {
96                     callback.onAssistContentAvailable(null);
97                 }
98             } catch (RemoteException e) {
99                 Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
100             }
101         });
102     }
103 
executeOnMainExecutor(Runnable callback)104     private void executeOnMainExecutor(Runnable callback) {
105         mCallbackExecutor.execute(callback);
106     }
107 
108     private static final class AssistDataReceiver extends IAssistDataReceiver.Stub {
109 
110         // The AssistDataReceiver binder callback object is passed to a system server, that may
111         // keep hold of it for longer than the lifetime of the AssistContentRequester object,
112         // potentially causing a memory leak. In the callback passed to the system server, only
113         // keep a weak reference to the parent object and lookup its callback if it still exists.
114         private final WeakReference<AssistContentRequester> mParentRef;
115         private final Object mCallbackKey = new Object();
116 
AssistDataReceiver(Callback callback, AssistContentRequester parent)117         AssistDataReceiver(Callback callback, AssistContentRequester parent) {
118             parent.mPendingCallbacks.put(mCallbackKey, callback);
119             mParentRef = new WeakReference<>(parent);
120         }
121 
122         @Override
onHandleAssistData(Bundle data)123         public void onHandleAssistData(Bundle data) {
124             final AssistContent content = (data == null) ? null
125                     : data.getParcelable(
126                             ASSIST_KEY_CONTENT, AssistContent.class);
127 
128             AssistContentRequester requester = mParentRef.get();
129             if (requester != null) {
130                 Callback callback = requester.mPendingCallbacks.get(mCallbackKey);
131                 if (callback != null) {
132                     requester.executeOnMainExecutor(
133                             () -> callback.onAssistContentAvailable(content));
134                 } else {
135                     Log.d(TAG, "Callback received after calling UI was disposed of");
136                 }
137             } else {
138                 Log.d(TAG, "Callback received after Requester was collected");
139             }
140         }
141 
142         @Override
onHandleAssistScreenshot(Bitmap screenshot)143         public void onHandleAssistScreenshot(Bitmap screenshot) {}
144     }
145 }
146