diff options
7 files changed, 14 insertions, 386 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java index 6224e1bf2414..afb0280a3917 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java @@ -16,6 +16,7 @@ package com.android.systemui.screenshot; +import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -29,4 +30,9 @@ public interface ReferenceScreenshotModule { static ScreenshotNotificationSmartActionsProvider providesScrnshtNotifSmartActionsProvider() { return new ScreenshotNotificationSmartActionsProvider(); } + + /** */ + @Binds + ScreenshotActionsProvider.Factory bindScreenshotActionsProviderFactory( + DefaultScreenshotActionsProvider.Factory defaultScreenshotActionsProviderFactory); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt index 0ccb19cd9d31..328742543184 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt @@ -28,7 +28,6 @@ import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubjec import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED -import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted @@ -59,7 +58,6 @@ class DefaultScreenshotActionsProvider constructor( private val context: Context, private val viewModel: ScreenshotViewModel, - private val smartActionsProvider: SmartActionsProvider, private val uiEventLogger: UiEventLogger, @Assisted val request: ScreenshotData, @Assisted val requestId: String, @@ -115,37 +113,6 @@ constructor( ) } } - - smartActionsProvider.requestQuickShare(request, requestId) { quickShare -> - if (!quickShare.actionIntent.isImmutable) { - viewModel.addAction( - ActionButtonAppearance( - quickShare.getIcon().loadDrawable(context), - quickShare.title, - quickShare.title - ) - ) { - debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" } - onDeferrableActionTapped { result -> - uiEventLogger.log( - SCREENSHOT_SMART_ACTION_TAPPED, - 0, - request.packageNameString - ) - val pendingIntentWithUri = - smartActionsProvider.wrapIntent( - quickShare, - result.uri, - result.subject, - requestId - ) - actionExecutor.sendPendingIntent(pendingIntentWithUri) - } - } - } else { - Log.w(TAG, "Received immutable quick share pending intent; ignoring") - } - } } override fun onScrollChipReady(onClick: Runnable) { @@ -167,21 +134,6 @@ constructor( } this.result = result pendingAction?.invoke(result) - smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions -> - smartActions.forEach { - smartActions.forEach { action -> - viewModel.addAction( - ActionButtonAppearance( - action.getIcon().loadDrawable(context), - action.title, - action.title, - ) - ) { - actionExecutor.sendPendingIntent(action.actionIntent) - } - } - } - } } private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 13dd229d8f62..0e48bef9a770 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -176,11 +176,12 @@ public class ScreenshotController { // These strings are used for communicating the action invoked to // ScreenshotNotificationSmartActionsProvider. - static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; - static final String EXTRA_ID = "android:screenshot_id"; - static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; - static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; - static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin"; + public static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; + public static final String EXTRA_ID = "android:screenshot_id"; + public static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; + public static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; + public static final String EXTRA_ACTION_INTENT_FILLIN = + "android:screenshot_action_intent_fillin"; // From WizardManagerHelper.java diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java index 3eafbfbf37d7..23f05e0d8337 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java @@ -44,7 +44,7 @@ public class ScreenshotNotificationSmartActionsProvider { public static final String DEFAULT_ACTION_TYPE = "Smart Action"; /* Define phases of screenshot execution. */ - protected enum ScreenshotOp { + public enum ScreenshotOp { OP_UNKNOWN, RETRIEVE_SMART_ACTIONS, REQUEST_SMART_ACTIONS, @@ -52,7 +52,7 @@ public class ScreenshotNotificationSmartActionsProvider { } /* Enum to report success or failure for screenshot execution phases. */ - protected enum ScreenshotOpStatus { + public enum ScreenshotOpStatus { OP_STATUS_UNKNOWN, SUCCESS, ERROR, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt deleted file mode 100644 index a895b300b900..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2024 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.Notification -import android.app.PendingIntent -import android.content.ClipData -import android.content.ClipDescription -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.graphics.Bitmap -import android.net.Uri -import android.os.Bundle -import android.os.Process -import android.os.SystemClock -import android.os.UserHandle -import android.provider.DeviceConfig -import android.util.Log -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags -import com.android.systemui.log.DebugLogger.debugLog -import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS -import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION -import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException -import javax.inject.Inject -import kotlin.random.Random - -/** - * Handle requesting smart/quickshare actions from the provider and executing an action when the - * action futures complete. - */ -class SmartActionsProvider -@Inject -constructor( - private val context: Context, - private val smartActions: ScreenshotNotificationSmartActionsProvider, -) { - /** - * Requests quick share action for a given screenshot. - * - * @param data the ScreenshotData request - * @param id the request id for the screenshot - * @param onAction callback to run when quick share action is returned - */ - fun requestQuickShare( - data: ScreenshotData, - id: String, - onAction: (Notification.Action) -> Unit - ) { - val bitmap = data.bitmap ?: return - val component = data.topComponent ?: ComponentName("", "") - requestQuickShareAction(id, bitmap, component, data.getUserOrDefault()) { quickShare -> - onAction(quickShare) - } - } - - /** - * Requests smart actions for a given screenshot. - * - * @param data the ScreenshotData request - * @param id the request id for the screenshot - * @param result the data for the saved image - * @param onActions callback to run when actions are returned - */ - fun requestSmartActions( - data: ScreenshotData, - id: String, - result: ScreenshotSavedResult, - onActions: (List<Notification.Action>) -> Unit - ) { - val bitmap = data.bitmap ?: return - val component = data.topComponent ?: ComponentName("", "") - requestSmartActions( - id, - bitmap, - component, - data.getUserOrDefault(), - result.uri, - REGULAR_SMART_ACTIONS - ) { actions -> - onActions(actions) - } - } - - /** - * Wraps the given quick share action in a broadcast intent. - * - * @param quickShare the quick share action to wrap - * @param uri the URI of the saved screenshot - * @param subject the subject/title for the screenshot - * @param id the request ID of the screenshot - * @return the pending intent with correct URI - */ - fun wrapIntent( - quickShare: Notification.Action, - uri: Uri, - subject: String, - id: String - ): PendingIntent { - val wrappedIntent: Intent = - Intent(context, SmartActionsReceiver::class.java) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent) - .putExtra( - ScreenshotController.EXTRA_ACTION_INTENT_FILLIN, - createFillInIntent(uri, subject) - ) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) - val extras: Bundle = quickShare.extras - val actionType = - extras.getString( - ScreenshotNotificationSmartActionsProvider.ACTION_TYPE, - ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE - ) - // We only query for quick share actions when smart actions are enabled, so we can assert - // that it's true here. - wrappedIntent - .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType) - .putExtra(ScreenshotController.EXTRA_ID, id) - .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, true) - return PendingIntent.getBroadcast( - context, - Random.nextInt(), - wrappedIntent, - PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - } - - private fun createFillInIntent(uri: Uri, subject: String): Intent { - val fillIn = Intent() - fillIn.setType("image/png") - fillIn.putExtra(Intent.EXTRA_STREAM, uri) - fillIn.putExtra(Intent.EXTRA_SUBJECT, subject) - // Include URI in ClipData also, so that grantPermission picks it up. - // We don't use setData here because some apps interpret this as "to:". - val clipData = - ClipData(ClipDescription("content", arrayOf("image/png")), ClipData.Item(uri)) - fillIn.clipData = clipData - fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - return fillIn - } - - private fun requestQuickShareAction( - id: String, - image: Bitmap, - component: ComponentName, - user: UserHandle, - timeoutMs: Long = 500, - onAction: (Notification.Action) -> Unit - ) { - requestSmartActions(id, image, component, user, null, QUICK_SHARE_ACTION, timeoutMs) { - it.firstOrNull()?.let { action -> onAction(action) } - } - } - - private fun requestSmartActions( - id: String, - image: Bitmap, - component: ComponentName, - user: UserHandle, - uri: Uri?, - actionType: ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType, - timeoutMs: Long = 500, - onActions: (List<Notification.Action>) -> Unit - ) { - val enabled = isSmartActionsEnabled(user) - debugLog(DEBUG_ACTIONS) { - ("getSmartActionsFuture id=$id, uri=$uri, provider=$smartActions, " + - "actionType=$actionType, smartActionsEnabled=$enabled, userHandle=$user") - } - if (!enabled) { - debugLog(DEBUG_ACTIONS) { "Screenshot Intelligence not enabled, returning empty list" } - onActions(listOf()) - return - } - if (image.config != Bitmap.Config.HARDWARE) { - debugLog(DEBUG_ACTIONS) { - "Bitmap expected: Hardware, Bitmap found: ${image.config}. Returning empty list." - } - onActions(listOf()) - return - } - val smartActionsFuture: CompletableFuture<List<Notification.Action>> - val startTimeMs = SystemClock.uptimeMillis() - try { - smartActionsFuture = - smartActions.getActions(id, uri, image, component, actionType, user) - } catch (e: Throwable) { - val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs - debugLog(DEBUG_ACTIONS, error = e) { - "Failed to get future for screenshot notification smart actions." - } - notifyScreenshotOp( - id, - ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS, - ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR, - waitTimeMs - ) - onActions(listOf()) - return - } - try { - val actions = smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS) - val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs - debugLog(DEBUG_ACTIONS) { - ("Got ${actions.size} smart actions. Wait time: $waitTimeMs ms, " + - "actionType=$actionType") - } - notifyScreenshotOp( - id, - ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS, - ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS, - waitTimeMs - ) - onActions(actions) - } catch (e: Throwable) { - val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs - debugLog(DEBUG_ACTIONS, error = e) { - "Error getting smart actions. Wait time: $waitTimeMs ms, actionType=$actionType" - } - val status = - if (e is TimeoutException) { - ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT - } else { - ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR - } - notifyScreenshotOp( - id, - ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS, - status, - waitTimeMs - ) - onActions(listOf()) - } - } - - private fun notifyScreenshotOp( - screenshotId: String, - op: ScreenshotNotificationSmartActionsProvider.ScreenshotOp, - status: ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus, - durationMs: Long - ) { - debugLog(DEBUG_ACTIONS) { - "$smartActions notifyOp: $op id=$screenshotId, status=$status, durationMs=$durationMs" - } - try { - smartActions.notifyOp(screenshotId, op, status, durationMs) - } catch (e: Throwable) { - Log.e(TAG, "Error in notifyScreenshotOp: ", e) - } - } - - private fun isSmartActionsEnabled(user: UserHandle): Boolean { - // Smart actions don't yet work for cross-user saves. - val savingToOtherUser = user !== Process.myUserHandle() - val actionsEnabled = - DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, - true - ) - return !savingToOtherUser && actionsEnabled - } - - companion object { - private const val TAG = "SmartActionsProvider" - private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java index 6ff0fdaeab0f..ab23e5fdb7d9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java @@ -22,11 +22,9 @@ import android.app.Service; import android.view.accessibility.AccessibilityManager; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.screenshot.DefaultScreenshotActionsProvider; import com.android.systemui.screenshot.ImageCapture; import com.android.systemui.screenshot.ImageCaptureImpl; import com.android.systemui.screenshot.LegacyScreenshotViewProxy; -import com.android.systemui.screenshot.ScreenshotActionsProvider; import com.android.systemui.screenshot.ScreenshotPolicy; import com.android.systemui.screenshot.ScreenshotPolicyImpl; import com.android.systemui.screenshot.ScreenshotShelfViewProxy; @@ -90,10 +88,6 @@ public abstract class ScreenshotModule { abstract ScreenshotSoundController bindScreenshotSoundController( ScreenshotSoundControllerImpl screenshotSoundProviderImpl); - @Binds - abstract ScreenshotActionsProvider.Factory bindScreenshotActionsProviderFactory( - DefaultScreenshotActionsProvider.Factory defaultScreenshotActionsProviderFactory); - @Provides @SysUISingleton static ScreenshotViewModel providesScreenshotViewModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt index bde821b79469..853e50a12ea5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt @@ -16,8 +16,6 @@ package com.android.systemui.screenshot -import android.app.Notification -import android.app.PendingIntent import android.content.Intent import android.net.Uri import android.os.Process @@ -27,8 +25,6 @@ import android.view.accessibility.AccessibilityManager import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase -import com.android.systemui.clipboardoverlay.EditTextActivity -import com.android.systemui.res.R import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -41,10 +37,7 @@ import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.runner.RunWith import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.kotlin.any -import org.mockito.kotlin.never import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) @SmallTest @@ -52,7 +45,6 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { private val actionExecutor = mock<ActionExecutor>() private val accessibilityManager = mock<AccessibilityManager>() private val uiEventLogger = mock<UiEventLogger>() - private val smartActionsProvider = mock<SmartActionsProvider>() private val request = ScreenshotData.forTesting() private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0) @@ -119,42 +111,10 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER) } - @Test - fun quickShareTapped_wrapsAndSendsIntent() = runTest { - val quickShare = - Notification.Action( - R.drawable.ic_screenshot_edit, - "TestQuickShare", - PendingIntent.getActivity( - context, - 0, - Intent(context, EditTextActivity::class.java), - PendingIntent.FLAG_MUTABLE - ) - ) - whenever(smartActionsProvider.requestQuickShare(any(), any(), any())).then { - (it.getArgument(2) as ((Notification.Action) -> Unit)).invoke(quickShare) - } - whenever(smartActionsProvider.wrapIntent(any(), any(), any(), any())).thenAnswer { - (it.getArgument(0) as Notification.Action).actionIntent - } - actionsProvider = createActionsProvider() - - viewModel.actions.value[2].onClicked?.invoke() - verify(uiEventLogger, never()) - .log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), any(), any()) - verify(smartActionsProvider, never()).wrapIntent(any(), any(), any(), any()) - actionsProvider.setCompletedScreenshot(validResult) - verify(smartActionsProvider) - .wrapIntent(eq(quickShare), eq(validResult.uri), eq(validResult.subject), eq("testid")) - verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), eq(0), eq("")) - } - private fun createActionsProvider(): ScreenshotActionsProvider { return DefaultScreenshotActionsProvider( context, viewModel, - smartActionsProvider, uiEventLogger, request, "testid", |