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 66 // If system loses the callback, our internal cache of original callback will also get cleared. 67 private final Map<Object, Callback> mPendingCallbacks = 68 Collections.synchronizedMap(new WeakHashMap<>()); 69 70 @Inject AssistContentRequester(Context context, @Main Executor mainExecutor, @Background Executor bgExecutor)71 public AssistContentRequester(Context context, @Main Executor mainExecutor, 72 @Background Executor bgExecutor) { 73 mActivityTaskManager = ActivityTaskManager.getService(); 74 mPackageName = context.getApplicationContext().getPackageName(); 75 mCallbackExecutor = mainExecutor; 76 mSystemInteractionExecutor = bgExecutor; 77 } 78 79 /** 80 * Request the {@link AssistContent} from the task with the provided id. 81 * 82 * @param taskId to query for the content. 83 * @param callback to call when the content is available, called on the main thread. 84 */ requestAssistContent(final int taskId, final Callback callback)85 public void requestAssistContent(final int taskId, final Callback callback) { 86 // ActivityTaskManager interaction here is synchronous, so call off the main thread. 87 mSystemInteractionExecutor.execute(() -> { 88 try { 89 mActivityTaskManager.requestAssistDataForTask( 90 new AssistDataReceiver(callback, this), taskId, mPackageName); 91 } catch (RemoteException e) { 92 Log.e(TAG, "Requesting assist content failed for task: " + taskId, e); 93 } 94 }); 95 } 96 executeOnMainExecutor(Runnable callback)97 private void executeOnMainExecutor(Runnable callback) { 98 mCallbackExecutor.execute(callback); 99 } 100 101 private static final class AssistDataReceiver extends IAssistDataReceiver.Stub { 102 103 // The AssistDataReceiver binder callback object is passed to a system server, that may 104 // keep hold of it for longer than the lifetime of the AssistContentRequester object, 105 // potentially causing a memory leak. In the callback passed to the system server, only 106 // keep a weak reference to the parent object and lookup its callback if it still exists. 107 private final WeakReference<AssistContentRequester> mParentRef; 108 private final Object mCallbackKey = new Object(); 109 AssistDataReceiver(Callback callback, AssistContentRequester parent)110 AssistDataReceiver(Callback callback, AssistContentRequester parent) { 111 parent.mPendingCallbacks.put(mCallbackKey, callback); 112 mParentRef = new WeakReference<>(parent); 113 } 114 115 @Override onHandleAssistData(Bundle data)116 public void onHandleAssistData(Bundle data) { 117 if (data == null) { 118 return; 119 } 120 121 final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT); 122 if (content == null) { 123 Log.e(TAG, "Received AssistData, but no AssistContent found"); 124 return; 125 } 126 127 AssistContentRequester requester = mParentRef.get(); 128 if (requester != null) { 129 Callback callback = requester.mPendingCallbacks.get(mCallbackKey); 130 if (callback != null) { 131 requester.executeOnMainExecutor( 132 () -> callback.onAssistContentAvailable(content)); 133 } else { 134 Log.d(TAG, "Callback received after calling UI was disposed of"); 135 } 136 } else { 137 Log.d(TAG, "Callback received after Requester was collected"); 138 } 139 } 140 141 @Override onHandleAssistScreenshot(Bitmap screenshot)142 public void onHandleAssistScreenshot(Bitmap screenshot) {} 143 } 144 } 145