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

Commit 8e8f9813 authored by Matt Casey's avatar Matt Casey Committed by Android (Google) Code Review
Browse files

Merge "Make clipboard overlay honor the preferred editor" into main

parents 9e3e8ada 22464f0b
Loading
Loading
Loading
Loading
+77 −4
Original line number Diff line number Diff line
@@ -19,12 +19,17 @@ package com.android.systemui.clipboardoverlay
import android.content.ClipData
import android.content.ComponentName
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.net.Uri
import android.text.SpannableString
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -33,6 +38,8 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -40,8 +47,10 @@ class ActionIntentCreatorTest : SysuiTestCase() {
    private val scheduler = TestCoroutineScheduler()
    private val mainDispatcher = UnconfinedTestDispatcher(scheduler)
    private val testScope = TestScope(mainDispatcher)
    val packageManager = mock<PackageManager>()

    val creator = ActionIntentCreator(testScope.backgroundScope)
    val creator =
        ActionIntentCreator(context, packageManager, testScope.backgroundScope, mainDispatcher)

    @Test
    fun test_getTextEditorIntent() {
@@ -73,7 +82,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
    }

    @Test
    fun test_getImageEditIntent() = runTest {
    fun test_getImageEditIntent_noDefault() = runTest {
        context.getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, "")
        val fakeUri = Uri.parse("content://foo")
        var intent = creator.getImageEditIntent(fakeUri, context)
@@ -83,17 +92,81 @@ class ActionIntentCreatorTest : SysuiTestCase() {
        assertEquals(null, intent.component)
        assertEquals("clipboard", intent.getStringExtra("edit_source"))
        assertFlags(intent, EXTERNAL_INTENT_FLAGS)
    }

    @Test
    fun test_getImageEditIntent_defaultProvided() = runTest {
        val fakeUri = Uri.parse("content://foo")

        // try again with an editor component
        val fakeComponent =
            ComponentName("com.android.remotecopy", "com.android.remotecopy.RemoteCopyActivity")
        context
            .getOrCreateTestableResources()
            .addOverride(R.string.config_screenshotEditor, fakeComponent.flattenToString())
        intent = creator.getImageEditIntent(fakeUri, context)
        val intent = creator.getImageEditIntent(fakeUri, context)
        assertEquals(fakeComponent, intent.component)
    }

    @Test
    fun test_getImageEditIntent_preferredProvidedButDisabled() = runTest {
        val fakeUri = Uri.parse("content://foo")

        val defaultComponent = ComponentName("com.android.foo", "com.android.foo.Something")
        val preferredComponent = ComponentName("com.android.bar", "com.android.bar.Something")

        val packageInfo =
            PackageInfo().apply {
                activities = arrayOf() // no activities
            }
        whenever(packageManager.getPackageInfo(eq(preferredComponent.packageName), anyInt()))
            .thenReturn(packageInfo)

        context
            .getOrCreateTestableResources()
            .addOverride(R.string.config_screenshotEditor, defaultComponent.flattenToString())
        context
            .getOrCreateTestableResources()
            .addOverride(
                R.string.config_preferredScreenshotEditor,
                preferredComponent.flattenToString(),
            )
        val intent = creator.getImageEditIntent(fakeUri, context)
        assertEquals(defaultComponent, intent.component)
    }

    @Test
    fun test_getImageEditIntent_preferredProvided() = runTest {
        val fakeUri = Uri.parse("content://foo")

        val defaultComponent = ComponentName("com.android.foo", "com.android.foo.Something")
        val preferredComponent = ComponentName("com.android.bar", "com.android.bar.Something")

        val packageInfo =
            PackageInfo().apply {
                activities =
                    arrayOf(
                        ActivityInfo().apply {
                            packageName = preferredComponent.packageName
                            name = preferredComponent.className
                        }
                    )
            }
        whenever(packageManager.getPackageInfo(eq(preferredComponent.packageName), anyInt()))
            .thenReturn(packageInfo)

        context
            .getOrCreateTestableResources()
            .addOverride(R.string.config_screenshotEditor, defaultComponent.flattenToString())
        context
            .getOrCreateTestableResources()
            .addOverride(
                R.string.config_preferredScreenshotEditor,
                preferredComponent.flattenToString(),
            )
        val intent = creator.getImageEditIntent(fakeUri, context)
        assertEquals(preferredComponent, intent.component)
    }

    @Test
    fun test_getShareIntent_plaintext() {
        val clipData = ClipData.newPlainText("Test", "Test Item")
+46 −5
Original line number Diff line number Diff line
@@ -21,20 +21,30 @@ import android.content.ClipDescription
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri
import android.text.TextUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

@SysUISingleton
class ActionIntentCreator
@Inject
constructor(@Application private val applicationScope: CoroutineScope) : IntentCreator {
constructor(
    private val context: Context,
    private val packageManager: PackageManager,
    @Application private val applicationScope: CoroutineScope,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) : IntentCreator {
    override fun getTextEditorIntent(context: Context?) =
        Intent(context, EditTextActivity::class.java).apply {
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
@@ -72,11 +82,9 @@ constructor(@Application private val applicationScope: CoroutineScope) : IntentC
    }

    suspend fun getImageEditIntent(uri: Uri?, context: Context): Intent {
        val editorPackage = context.getString(R.string.config_screenshotEditor)
        return Intent(Intent.ACTION_EDIT).apply {
            if (!TextUtils.isEmpty(editorPackage)) {
                setComponent(ComponentName.unflattenFromString(editorPackage))
            }
            // Use the preferred editor if it's available, otherwise fall back to the default editor
            component = preferredEditor() ?: defaultEditor()
            setDataAndType(uri, "image/*")
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
@@ -105,6 +113,39 @@ constructor(@Application private val applicationScope: CoroutineScope) : IntentC
        }
    }

    private suspend fun preferredEditor(): ComponentName? =
        runCatching {
                val preferredEditor = context.getString(R.string.config_preferredScreenshotEditor)
                val component = ComponentName.unflattenFromString(preferredEditor) ?: return null

                return if (isComponentAvailable(component)) component else null
            }
            .getOrNull()

    private suspend fun isComponentAvailable(component: ComponentName): Boolean =
        withContext(backgroundDispatcher) {
            try {
                val info =
                    packageManager.getPackageInfo(
                        component.packageName,
                        PackageManager.GET_ACTIVITIES,
                    )
                info.activities?.firstOrNull {
                    it.componentName.className == component.className
                } != null
            } catch (e: NameNotFoundException) {
                false
            }
        }

    private fun defaultEditor(): ComponentName? =
        runCatching {
                context.getString(R.string.config_screenshotEditor).let {
                    ComponentName.unflattenFromString(it)
                }
            }
            .getOrNull()

    companion object {
        private const val EXTRA_EDIT_SOURCE: String = "edit_source"
        private const val EDIT_SOURCE_CLIPBOARD: String = "clipboard"