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