Loading packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt +16 −1 Original line number Diff line number Diff line Loading @@ -25,8 +25,22 @@ import android.net.Uri import com.android.systemui.R object ActionIntentCreator { /** @return a chooser intent to share the given URI. */ fun createShareIntent(uri: Uri) = createShareIntent(uri, null, null) /** @return a chooser intent to share the given URI with the optional provided subject. */ fun createShareIntent(uri: Uri, subject: String?): Intent { fun createShareIntentWithSubject(uri: Uri, subject: String?) = createShareIntent(uri, subject = subject) /** @return a chooser intent to share the given URI with the optional provided extra text. */ fun createShareIntentWithExtraText(uri: Uri, extraText: String?) = createShareIntent(uri, extraText = extraText) private fun createShareIntent( uri: Uri, subject: String? = null, extraText: String? = null ): Intent { // Create a share intent, this will always go through the chooser activity first // which should not trigger auto-enter PiP val sharingIntent = Loading @@ -43,6 +57,7 @@ object ActionIntentCreator { ) putExtra(Intent.EXTRA_SUBJECT, subject) putExtra(Intent.EXTRA_TEXT, extraText) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } Loading packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java 0 → 100644 +147 −0 Original line number Diff line number Diff line /* * 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) {} } } packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +1 −1 Original line number Diff line number Diff line Loading @@ -366,7 +366,7 @@ public class LongScreenshotActivity extends Activity { private void doShare(Uri uri) { if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null); Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri); mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle.getIdentifier(), false); } else { Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +17 −2 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import android.app.ExitTransitionCoordinator; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.ICompatCameraControlCallback; import android.app.Notification; import android.app.assist.AssistContent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; Loading Loading @@ -281,6 +282,7 @@ public class ScreenshotController { private final ActionIntentExecutor mActionExecutor; private final UserManager mUserManager; private final WorkProfileMessageController mWorkProfileMessageController; private final AssistContentRequester mAssistContentRequester; private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { if (DEBUG_INPUT) { Loading Loading @@ -328,7 +330,8 @@ public class ScreenshotController { ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, ActionIntentExecutor actionExecutor, UserManager userManager, WorkProfileMessageController workProfileMessageController WorkProfileMessageController workProfileMessageController, AssistContentRequester assistContentRequester ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; Loading Loading @@ -361,6 +364,7 @@ public class ScreenshotController { mActionExecutor = actionExecutor; mUserManager = userManager; mWorkProfileMessageController = workProfileMessageController; mAssistContentRequester = assistContentRequester; mAccessibilityManager = AccessibilityManager.getInstance(mContext); Loading Loading @@ -466,7 +470,18 @@ public class ScreenshotController { mContext.getDrawable(R.drawable.overlay_badge_background), screenshot.getUserHandle())); } mScreenshotView.setScreenshot(mScreenBitmap, screenshot.getInsets()); mScreenshotView.setScreenshot(screenshot); if (screenshot.getTaskId() >= 0) { mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), new AssistContentRequester.Callback() { @Override public void onAssistContentAvailable(AssistContent assistContent) { screenshot.setContextUrl(assistContent.getWebUri()); } }); } if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mScreenshotView); } Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt +3 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import android.content.ComponentName import android.graphics.Bitmap import android.graphics.Insets import android.graphics.Rect import android.net.Uri import android.os.UserHandle import android.view.WindowManager.ScreenshotSource import android.view.WindowManager.ScreenshotType Loading @@ -21,6 +22,8 @@ data class ScreenshotData( var taskId: Int, var insets: Insets, var bitmap: Bitmap?, /** App-provided URL representing the content the user was looking at in the screenshot. */ var contextUrl: Uri? = null, ) { val packageNameString: String get() = if (topComponent == null) "" else topComponent!!.packageName Loading Loading
packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt +16 −1 Original line number Diff line number Diff line Loading @@ -25,8 +25,22 @@ import android.net.Uri import com.android.systemui.R object ActionIntentCreator { /** @return a chooser intent to share the given URI. */ fun createShareIntent(uri: Uri) = createShareIntent(uri, null, null) /** @return a chooser intent to share the given URI with the optional provided subject. */ fun createShareIntent(uri: Uri, subject: String?): Intent { fun createShareIntentWithSubject(uri: Uri, subject: String?) = createShareIntent(uri, subject = subject) /** @return a chooser intent to share the given URI with the optional provided extra text. */ fun createShareIntentWithExtraText(uri: Uri, extraText: String?) = createShareIntent(uri, extraText = extraText) private fun createShareIntent( uri: Uri, subject: String? = null, extraText: String? = null ): Intent { // Create a share intent, this will always go through the chooser activity first // which should not trigger auto-enter PiP val sharingIntent = Loading @@ -43,6 +57,7 @@ object ActionIntentCreator { ) putExtra(Intent.EXTRA_SUBJECT, subject) putExtra(Intent.EXTRA_TEXT, extraText) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } Loading
packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java 0 → 100644 +147 −0 Original line number Diff line number Diff line /* * 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) {} } }
packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +1 −1 Original line number Diff line number Diff line Loading @@ -366,7 +366,7 @@ public class LongScreenshotActivity extends Activity { private void doShare(Uri uri) { if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null); Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri); mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle.getIdentifier(), false); } else { Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +17 −2 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import android.app.ExitTransitionCoordinator; import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.ICompatCameraControlCallback; import android.app.Notification; import android.app.assist.AssistContent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; Loading Loading @@ -281,6 +282,7 @@ public class ScreenshotController { private final ActionIntentExecutor mActionExecutor; private final UserManager mUserManager; private final WorkProfileMessageController mWorkProfileMessageController; private final AssistContentRequester mAssistContentRequester; private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { if (DEBUG_INPUT) { Loading Loading @@ -328,7 +330,8 @@ public class ScreenshotController { ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, ActionIntentExecutor actionExecutor, UserManager userManager, WorkProfileMessageController workProfileMessageController WorkProfileMessageController workProfileMessageController, AssistContentRequester assistContentRequester ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; Loading Loading @@ -361,6 +364,7 @@ public class ScreenshotController { mActionExecutor = actionExecutor; mUserManager = userManager; mWorkProfileMessageController = workProfileMessageController; mAssistContentRequester = assistContentRequester; mAccessibilityManager = AccessibilityManager.getInstance(mContext); Loading Loading @@ -466,7 +470,18 @@ public class ScreenshotController { mContext.getDrawable(R.drawable.overlay_badge_background), screenshot.getUserHandle())); } mScreenshotView.setScreenshot(mScreenBitmap, screenshot.getInsets()); mScreenshotView.setScreenshot(screenshot); if (screenshot.getTaskId() >= 0) { mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), new AssistContentRequester.Callback() { @Override public void onAssistContentAvailable(AssistContent assistContent) { screenshot.setContextUrl(assistContent.getWebUri()); } }); } if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mScreenshotView); } Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt +3 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import android.content.ComponentName import android.graphics.Bitmap import android.graphics.Insets import android.graphics.Rect import android.net.Uri import android.os.UserHandle import android.view.WindowManager.ScreenshotSource import android.view.WindowManager.ScreenshotType Loading @@ -21,6 +22,8 @@ data class ScreenshotData( var taskId: Int, var insets: Insets, var bitmap: Bitmap?, /** App-provided URL representing the content the user was looking at in the screenshot. */ var contextUrl: Uri? = null, ) { val packageNameString: String get() = if (topComponent == null) "" else topComponent!!.packageName Loading