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