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