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