Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 967e668d authored by Miranda Kephart's avatar Miranda Kephart Committed by Android (Google) Code Review
Browse files

Merge "Handle pending actions within screenshot action provider" into main

parents d31aca2f be3c42e0
Loading
Loading
Loading
Loading
+97 −42
Original line number Diff line number Diff line
@@ -16,78 +16,133 @@

package com.android.systemui.screenshot

import android.app.ActivityOptions
import android.app.ExitTransitionCoordinator
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.UserHandle
import android.util.Log
import android.util.Pair
import androidx.appcompat.content.res.AppCompatResources
import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
import javax.inject.Inject
import com.android.systemui.screenshot.ActionIntentCreator.createEdit
import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject
import com.android.systemui.screenshot.ScreenshotController.SavedImageData
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope

/**
 * Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI
 * implementation.
 */
interface ScreenshotActionsProvider {
    data class ScreenshotAction(
        val icon: Drawable? = null,
        val text: String? = null,
        val description: String,
        val overrideTransition: Boolean = false,
        val retrieveIntent: (Uri) -> Intent
    )

    interface ScreenshotActionsCallback {
        fun setPreviewAction(overrideTransition: Boolean = false, retrieveIntent: (Uri) -> Intent)
        fun addAction(action: ScreenshotAction) = addActions(listOf(action))
        fun addActions(actions: List<ScreenshotAction>)
    }
    fun setCompletedScreenshot(result: SavedImageData)
    fun isPendingSharedTransition(): Boolean

    interface Factory {
        fun create(
            context: Context,
            user: UserHandle?,
            callback: ScreenshotActionsCallback
            request: ScreenshotData,
            windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
        ): ScreenshotActionsProvider
    }
}

class DefaultScreenshotActionsProvider(
class DefaultScreenshotActionsProvider
@AssistedInject
constructor(
    private val context: Context,
    private val user: UserHandle?,
    private val callback: ScreenshotActionsProvider.ScreenshotActionsCallback
    private val viewModel: ScreenshotViewModel,
    private val actionExecutor: ActionIntentExecutor,
    @Application private val applicationScope: CoroutineScope,
    @Assisted val request: ScreenshotData,
    @Assisted val windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
) : ScreenshotActionsProvider {
    private var pendingAction: ((SavedImageData) -> Unit)? = null
    private var result: SavedImageData? = null
    private var isPendingSharedTransition = false

    init {
        callback.setPreviewAction(true) { ActionIntentCreator.createEdit(it, context) }
        val editAction =
            ScreenshotActionsProvider.ScreenshotAction(
        viewModel.setPreviewAction {
            debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
            onDeferrableActionTapped { result ->
                startSharedTransition(createEdit(result.uri, context), true)
            }
        }
        viewModel.addAction(
            ActionButtonViewModel(
                AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
                context.resources.getString(R.string.screenshot_edit_label),
                context.resources.getString(R.string.screenshot_edit_description),
                true
            ) { uri ->
                ActionIntentCreator.createEdit(uri, context)
            ) {
                debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
                onDeferrableActionTapped { result ->
                    startSharedTransition(createEdit(result.uri, context), true)
                }
        val shareAction =
            ScreenshotActionsProvider.ScreenshotAction(
            }
        )
        viewModel.addAction(
            ActionButtonViewModel(
                AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
                context.resources.getString(R.string.screenshot_share_label),
                context.resources.getString(R.string.screenshot_share_description),
                false
            ) { uri ->
                ActionIntentCreator.createShare(uri)
            ) {
                debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
                onDeferrableActionTapped { result ->
                    startSharedTransition(createShareWithSubject(result.uri, result.subject), false)
                }
            }
        )
    }

    override fun setCompletedScreenshot(result: SavedImageData) {
        if (this.result != null) {
            Log.e(TAG, "Got a second completed screenshot for existing request!")
            return
        }
        if (result.uri == null || result.owner == null || result.subject == null) {
            Log.e(TAG, "Invalid result provided!")
            return
        }
        this.result = result
        pendingAction?.invoke(result)
    }

    override fun isPendingSharedTransition(): Boolean {
        return isPendingSharedTransition
    }

    private fun onDeferrableActionTapped(onResult: (SavedImageData) -> Unit) {
        result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult }
    }

    private fun startSharedTransition(intent: Intent, overrideTransition: Boolean) {
        val user =
            result?.owner
                ?: run {
                    Log.wtf(TAG, "User handle not provided in screenshot result! Result: $result")
                    return
                }
        isPendingSharedTransition = true
        applicationScope.launch("$TAG#launchIntentAsync") {
            actionExecutor.launchIntent(intent, windowTransition.invoke(), user, overrideTransition)
        }
        callback.addActions(listOf(editAction, shareAction))
    }

    class Factory @Inject constructor() : ScreenshotActionsProvider.Factory {
    @AssistedFactory
    interface Factory : ScreenshotActionsProvider.Factory {
        override fun create(
            context: Context,
            user: UserHandle?,
            callback: ScreenshotActionsProvider.ScreenshotActionsCallback
        ): ScreenshotActionsProvider {
            return DefaultScreenshotActionsProvider(context, user, callback)
            request: ScreenshotData,
            windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
        ): DefaultScreenshotActionsProvider
    }

    companion object {
        private const val TAG = "ScreenshotActionsProvider"
    }
}
+17 −8
Original line number Diff line number Diff line
@@ -498,8 +498,8 @@ public class ScreenshotController {
        mViewProxy.reset();

        if (screenshotShelfUi()) {
            mActionsProvider = mActionsProviderFactory.create(mContext, screenshot.getUserHandle(),
                    ((ScreenshotActionsProvider.ScreenshotActionsCallback) mViewProxy));
            mActionsProvider =
                    mActionsProviderFactory.create(screenshot, this::createWindowTransition);
        }

        if (mViewProxy.isAttachedToWindow()) {
@@ -529,8 +529,12 @@ public class ScreenshotController {
    }

    boolean isPendingSharedTransition() {
        if (screenshotShelfUi()) {
            return mActionsProvider != null && mActionsProvider.isPendingSharedTransition();
        } else {
            return mViewProxy.isPendingSharedTransition();
        }
    }

    // Any cleanup needed when the service is being destroyed.
    void onDestroy() {
@@ -682,7 +686,8 @@ public class ScreenshotController {
                        mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());

                if (newScreenshot != null) {
                    // delay starting scroll capture to make sure scrim is up before the app moves
                    // delay starting scroll capture to make sure scrim is up before the app
                    // moves
                    mViewProxy.prepareScrollingTransition(
                            response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
                            () -> runBatchScrollCapture(response, owner));
@@ -951,13 +956,17 @@ public class ScreenshotController {
     */
    private void showUiOnActionsReady(ScreenshotController.SavedImageData imageData) {
        logSuccessOnActionsReady(imageData);
        if (DEBUG_UI) {
            Log.d(TAG, "Showing UI actions");
        }

        mScreenshotHandler.resetTimeout();

        if (screenshotShelfUi()) {
            mActionsProvider.setCompletedScreenshot(imageData);
            return;
        }

        if (imageData.uri != null) {
            if (DEBUG_UI) {
                Log.d(TAG, "Showing UI actions");
            }
            if (!imageData.owner.equals(Process.myUserHandle())) {
                Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
                        + imageData.uri);
+9 −52
Original line number Diff line number Diff line
@@ -20,10 +20,8 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.Notification
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Rect
import android.net.Uri
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ScrollCaptureResponse
@@ -35,7 +33,6 @@ import android.window.OnBackInvokedDispatcher
import com.android.internal.logging.UiEventLogger
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.LogConfig.DEBUG_INPUT
import com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW
@@ -45,7 +42,6 @@ import com.android.systemui.screenshot.scroll.ScrollCaptureController
import com.android.systemui.screenshot.ui.ScreenshotAnimationController
import com.android.systemui.screenshot.ui.ScreenshotShelfView
import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -59,7 +55,7 @@ constructor(
    private val viewModel: ScreenshotViewModel,
    @Assisted private val context: Context,
    @Assisted private val displayId: Int
) : ScreenshotViewProxy, ScreenshotActionsProvider.ScreenshotActionsCallback {
) : ScreenshotViewProxy {
    override val view: ScreenshotShelfView =
        LayoutInflater.from(context).inflate(R.layout.screenshot_shelf, null) as ScreenshotShelfView
    override val screenshotPreview: View
@@ -77,8 +73,6 @@ constructor(
    override var isPendingSharedTransition = false

    private val animationController = ScreenshotAnimationController(view)
    private var imageData: SavedImageData? = null
    private var runOnImageDataAcquired: ((SavedImageData) -> Unit)? = null

    init {
        ScreenshotShelfViewBinder.bind(view, viewModel, LayoutInflater.from(context))
@@ -91,9 +85,7 @@ constructor(
    override fun reset() {
        animationController.cancel()
        isPendingSharedTransition = false
        imageData = null
        viewModel.reset()
        runOnImageDataAcquired = null
    }
    override fun updateInsets(insets: WindowInsets) {}
    override fun updateOrientation(insets: WindowInsets) {}
@@ -104,10 +96,7 @@ constructor(

    override fun addQuickShareChip(quickShareAction: Notification.Action) {}

    override fun setChipIntents(data: SavedImageData) {
        imageData = data
        runOnImageDataAcquired?.invoke(data)
    }
    override fun setChipIntents(imageData: SavedImageData) {}

    override fun requestDismissal(event: ScreenshotEvent) {
        debugLog(DEBUG_DISMISS) { "screenshot dismissal requested: $event" }
@@ -143,13 +132,18 @@ constructor(
        newScreenshot: Bitmap,
        screenshotTakenInPortrait: Boolean,
        onTransitionPrepared: Runnable,
    ) {}
    ) {
        onTransitionPrepared.run()
    }

    override fun startLongScreenshotTransition(
        transitionDestination: Rect,
        onTransitionEnd: Runnable,
        longScreenshot: ScrollCaptureController.LongScreenshot
    ) {}
    ) {
        onTransitionEnd.run()
        callbacks?.onDismiss()
    }

    override fun restoreNonScrollingUi() {}

@@ -219,41 +213,4 @@ constructor(
    interface Factory : ScreenshotViewProxy.Factory {
        override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
    }

    override fun setPreviewAction(overrideTransition: Boolean, retrieveIntent: (Uri) -> Intent) {
        viewModel.setPreviewAction {
            imageData?.let {
                val intent = retrieveIntent(it.uri)
                debugLog(DEBUG_ACTIONS) { "Preview tapped: $intent" }
                isPendingSharedTransition = true
                callbacks?.onAction(intent, it.owner, overrideTransition)
            }
        }
    }

    override fun addActions(actions: List<ScreenshotActionsProvider.ScreenshotAction>) {
        viewModel.addActions(
            actions.map { action ->
                ActionButtonViewModel(action.icon, action.text, action.description) {
                    val actionRunnable =
                        getActionRunnable(action.retrieveIntent, action.overrideTransition)
                    imageData?.let { actionRunnable(it) }
                        ?: run { runOnImageDataAcquired = actionRunnable }
                }
            }
        )
    }

    private fun getActionRunnable(
        retrieveIntent: (Uri) -> Intent,
        overrideTransition: Boolean
    ): (SavedImageData) -> Unit {
        val onClick: (SavedImageData) -> Unit = {
            val intent = retrieveIntent(it.uri)
            debugLog(DEBUG_ACTIONS) { "Action tapped: $intent" }
            isPendingSharedTransition = true
            callbacks!!.onAction(intent, it.owner, overrideTransition)
        }
        return onClick
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -60,7 +60,7 @@ object ScreenshotShelfViewBinder {
                    }
                    launch {
                        viewModel.previewAction.collect { onClick ->
                            previewView.setOnClickListener { onClick?.run() }
                            previewView.setOnClickListener { onClick?.invoke() }
                        }
                    }
                    launch {
+10 −4
Original line number Diff line number Diff line
@@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.StateFlow
class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager) {
    private val _preview = MutableStateFlow<Bitmap?>(null)
    val preview: StateFlow<Bitmap?> = _preview
    private val _previewAction = MutableStateFlow<Runnable?>(null)
    val previewAction: StateFlow<Runnable?> = _previewAction
    private val _previewAction = MutableStateFlow<(() -> Unit)?>(null)
    val previewAction: StateFlow<(() -> Unit)?> = _previewAction
    private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>())
    val actions: StateFlow<List<ActionButtonViewModel>> = _actions
    val showDismissButton: Boolean
@@ -35,8 +35,14 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager
        _preview.value = bitmap
    }

    fun setPreviewAction(runnable: Runnable) {
        _previewAction.value = runnable
    fun setPreviewAction(onClick: () -> Unit) {
        _previewAction.value = onClick
    }

    fun addAction(action: ActionButtonViewModel) {
        val actionList = _actions.value.toMutableList()
        actionList.add(action)
        _actions.value = actionList
    }

    fun addActions(actions: List<ActionButtonViewModel>) {
Loading