blob: ab8fc652c938592001490de11700c60784f3d8c7 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.screenshot;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.IAssistDataReceiver;
import android.app.assist.AssistContent;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* Can be used to request the AssistContent from a provided task id, useful for getting the web uri
* if provided from the task.
*
* Forked from
* packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
*/
@SysUISingleton
public class AssistContentRequester {
private static final String TAG = "AssistContentRequester";
private static final String ASSIST_KEY_CONTENT = "content";
/** For receiving content, called on the main thread. */
public interface Callback {
/**
* Called when the {@link android.app.assist.AssistContent} of the requested task is
* available.
**/
void onAssistContentAvailable(AssistContent assistContent);
}
private final IActivityTaskManager mActivityTaskManager;
private final String mPackageName;
private final Executor mCallbackExecutor;
private final Executor mSystemInteractionExecutor;
private final String mAttributionTag;
// If system loses the callback, our internal cache of original callback will also get cleared.
private final Map<Object, Callback> mPendingCallbacks =
Collections.synchronizedMap(new WeakHashMap<>());
@Inject
public AssistContentRequester(Context context, @Main Executor mainExecutor,
@Background Executor bgExecutor) {
mActivityTaskManager = ActivityTaskManager.getService();
mPackageName = context.getApplicationContext().getPackageName();
mCallbackExecutor = mainExecutor;
mSystemInteractionExecutor = bgExecutor;
mAttributionTag = context.getAttributionTag();
}
/**
* Request the {@link AssistContent} from the task with the provided id.
*
* @param taskId to query for the content.
* @param callback to call when the content is available, called on the main thread.
*/
public void requestAssistContent(final int taskId, final Callback callback) {
// ActivityTaskManager interaction here is synchronous, so call off the main thread.
mSystemInteractionExecutor.execute(() -> {
try {
mActivityTaskManager.requestAssistDataForTask(
new AssistDataReceiver(callback, this), taskId, mPackageName,
mAttributionTag);
} catch (RemoteException e) {
Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
}
});
}
private void executeOnMainExecutor(Runnable callback) {
mCallbackExecutor.execute(callback);
}
private static final class AssistDataReceiver extends IAssistDataReceiver.Stub {
// The AssistDataReceiver binder callback object is passed to a system server, that may
// keep hold of it for longer than the lifetime of the AssistContentRequester object,
// potentially causing a memory leak. In the callback passed to the system server, only
// keep a weak reference to the parent object and lookup its callback if it still exists.
private final WeakReference<AssistContentRequester> mParentRef;
private final Object mCallbackKey = new Object();
AssistDataReceiver(Callback callback, AssistContentRequester parent) {
parent.mPendingCallbacks.put(mCallbackKey, callback);
mParentRef = new WeakReference<>(parent);
}
@Override
public void onHandleAssistData(Bundle data) {
if (data == null) {
return;
}
final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
if (content == null) {
Log.e(TAG, "Received AssistData, but no AssistContent found");
return;
}
AssistContentRequester requester = mParentRef.get();
if (requester != null) {
Callback callback = requester.mPendingCallbacks.get(mCallbackKey);
if (callback != null) {
requester.executeOnMainExecutor(
() -> callback.onAssistContentAvailable(content));
} else {
Log.d(TAG, "Callback received after calling UI was disposed of");
}
} else {
Log.d(TAG, "Callback received after Requester was collected");
}
}
@Override
public void onHandleAssistScreenshot(Bitmap screenshot) {}
}
}