diff options
| author | 2025-03-04 08:18:59 -0800 | |
|---|---|---|
| committer | 2025-03-04 08:18:59 -0800 | |
| commit | a17dc87c00f15cacebbbf250d949818c09ba8c94 (patch) | |
| tree | 1241ef8afb080a4c0e7347e89d6cabe7d13d3d30 | |
| parent | 73f896b82be7090ba0110ab8415c9f3a472ca1b3 (diff) | |
| parent | 8324973d42d277212e7829d50ff8382de5c20553 (diff) | |
Merge "Fork clipboard overlay's IntentCreator behind a flag." into main
| -rw-r--r-- | packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt | 150 | ||||
| -rw-r--r-- | packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java) | 22 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt | 99 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java | 32 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java | 102 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java | 78 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java | 15 | ||||
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java | 14 |
8 files changed, 402 insertions, 110 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt new file mode 100644 index 000000000000..239e02640908 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2025 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.clipboardoverlay + +import android.content.ClipData +import android.content.ComponentName +import android.content.Intent +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 org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ActionIntentCreatorTest : SysuiTestCase() { + val creator = ActionIntentCreator() + + @Test + fun test_getTextEditorIntent() { + val intent = creator.getTextEditorIntent(context) + assertEquals(ComponentName(context, EditTextActivity::class.java), intent.component) + assertFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + + @Test + fun test_getRemoteCopyIntent() { + context.getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, "") + + val clipData = ClipData.newPlainText("Test", "Test Item") + var intent = creator.getRemoteCopyIntent(clipData, context) + + assertEquals(null, intent.component) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + assertEquals(clipData, intent.clipData) + + // Try again with a remote copy component + val fakeComponent = + ComponentName("com.android.remotecopy", "com.android.remotecopy.RemoteCopyActivity") + context + .getOrCreateTestableResources() + .addOverride(R.string.config_remoteCopyPackage, fakeComponent.flattenToString()) + + intent = creator.getRemoteCopyIntent(clipData, context) + assertEquals(fakeComponent, intent.component) + } + + @Test + fun test_getImageEditIntent() { + context.getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, "") + val fakeUri = Uri.parse("content://foo") + var intent = creator.getImageEditIntent(fakeUri, context) + + assertEquals(Intent.ACTION_EDIT, intent.action) + assertEquals("image/*", intent.type) + assertEquals(null, intent.component) + assertEquals("clipboard", intent.getStringExtra("edit_source")) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + + // 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) + assertEquals(fakeComponent, intent.component) + } + + @Test + fun test_getShareIntent_plaintext() { + val clipData = ClipData.newPlainText("Test", "Test Item") + val intent = creator.getShareIntent(clipData, context) + + assertEquals(Intent.ACTION_CHOOSER, intent.action) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertEquals("Test Item", target?.getStringExtra(Intent.EXTRA_TEXT)) + assertEquals("text/plain", target?.type) + } + + @Test + fun test_getShareIntent_html() { + val clipData = ClipData.newHtmlText("Test", "Some HTML", "<b>Some HTML</b>") + val intent = creator.getShareIntent(clipData, getContext()) + + assertEquals(Intent.ACTION_CHOOSER, intent.action) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertEquals("Some HTML", target?.getStringExtra(Intent.EXTRA_TEXT)) + assertEquals("text/plain", target?.type) + } + + @Test + fun test_getShareIntent_image() { + val uri = Uri.parse("content://something") + val clipData = ClipData("Test", arrayOf("image/png"), ClipData.Item(uri)) + val intent = creator.getShareIntent(clipData, context) + + assertEquals(Intent.ACTION_CHOOSER, intent.action) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertEquals(uri, target?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) + assertEquals(uri, target?.clipData?.getItemAt(0)?.uri) + assertEquals("image/png", target?.type) + } + + @Test + fun test_getShareIntent_spannableText() { + val clipData = ClipData.newPlainText("Test", SpannableString("Test Item")) + val intent = creator.getShareIntent(clipData, context) + + assertEquals(Intent.ACTION_CHOOSER, intent.action) + assertFlags(intent, EXTERNAL_INTENT_FLAGS) + val target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + assertEquals("Test Item", target?.getStringExtra(Intent.EXTRA_TEXT)) + assertEquals("text/plain", target?.type) + } + + // Assert that the given flags are set + private fun assertFlags(intent: Intent, flags: Int) { + assertTrue((intent.flags and flags) == flags) + } + + companion object { + private const val EXTERNAL_INTENT_FLAGS: Int = + (Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_GRANT_READ_URI_PERMISSION) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java index ea6cb3b6d178..126b3fa9e7ca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java @@ -36,13 +36,15 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) -public class IntentCreatorTest extends SysuiTestCase { +public class DefaultIntentCreatorTest extends SysuiTestCase { private static final int EXTERNAL_INTENT_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION; + private final DefaultIntentCreator mIntentCreator = new DefaultIntentCreator(); + @Test public void test_getTextEditorIntent() { - Intent intent = IntentCreator.getTextEditorIntent(getContext()); + Intent intent = mIntentCreator.getTextEditorIntent(getContext()); assertEquals(new ComponentName(getContext(), EditTextActivity.class), intent.getComponent()); assertFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -54,7 +56,7 @@ public class IntentCreatorTest extends SysuiTestCase { ""); ClipData clipData = ClipData.newPlainText("Test", "Test Item"); - Intent intent = IntentCreator.getRemoteCopyIntent(clipData, getContext()); + Intent intent = mIntentCreator.getRemoteCopyIntent(clipData, getContext()); assertEquals(null, intent.getComponent()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); @@ -66,7 +68,7 @@ public class IntentCreatorTest extends SysuiTestCase { getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, fakeComponent.flattenToString()); - intent = IntentCreator.getRemoteCopyIntent(clipData, getContext()); + intent = mIntentCreator.getRemoteCopyIntent(clipData, getContext()); assertEquals(fakeComponent, intent.getComponent()); } @@ -75,7 +77,7 @@ public class IntentCreatorTest extends SysuiTestCase { getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, ""); Uri fakeUri = Uri.parse("content://foo"); - Intent intent = IntentCreator.getImageEditIntent(fakeUri, getContext()); + Intent intent = mIntentCreator.getImageEditIntent(fakeUri, getContext()); assertEquals(Intent.ACTION_EDIT, intent.getAction()); assertEquals("image/*", intent.getType()); @@ -88,14 +90,14 @@ public class IntentCreatorTest extends SysuiTestCase { "com.android.remotecopy.RemoteCopyActivity"); getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, fakeComponent.flattenToString()); - intent = IntentCreator.getImageEditIntent(fakeUri, getContext()); + intent = mIntentCreator.getImageEditIntent(fakeUri, getContext()); assertEquals(fakeComponent, intent.getComponent()); } @Test public void test_getShareIntent_plaintext() { ClipData clipData = ClipData.newPlainText("Test", "Test Item"); - Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + Intent intent = mIntentCreator.getShareIntent(clipData, getContext()); assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); @@ -108,7 +110,7 @@ public class IntentCreatorTest extends SysuiTestCase { public void test_getShareIntent_html() { ClipData clipData = ClipData.newHtmlText("Test", "Some HTML", "<b>Some HTML</b>"); - Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + Intent intent = mIntentCreator.getShareIntent(clipData, getContext()); assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); @@ -122,7 +124,7 @@ public class IntentCreatorTest extends SysuiTestCase { Uri uri = Uri.parse("content://something"); ClipData clipData = new ClipData("Test", new String[]{"image/png"}, new ClipData.Item(uri)); - Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + Intent intent = mIntentCreator.getShareIntent(clipData, getContext()); assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); @@ -135,7 +137,7 @@ public class IntentCreatorTest extends SysuiTestCase { @Test public void test_getShareIntent_spannableText() { ClipData clipData = ClipData.newPlainText("Test", new SpannableString("Test Item")); - Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + Intent intent = mIntentCreator.getShareIntent(clipData, getContext()); assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); assertFlags(intent, EXTERNAL_INTENT_FLAGS); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt new file mode 100644 index 000000000000..df6c1b18e3e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2025 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.clipboardoverlay + +import android.content.ClipData +import android.content.ClipDescription +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.text.TextUtils +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R +import javax.inject.Inject + +@SysUISingleton +class ActionIntentCreator @Inject constructor() : IntentCreator { + override fun getTextEditorIntent(context: Context?) = + Intent(context, EditTextActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + + override fun getShareIntent(clipData: ClipData, context: Context?): Intent { + val shareIntent = Intent(Intent.ACTION_SEND) + + // From the ACTION_SEND docs: + // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the + // MIME type of the data in EXTRA_STREAM" + val uri = clipData.getItemAt(0).uri + shareIntent.apply { + if (uri != null) { + // We don't use setData here because some apps interpret this as "to:". + setType(clipData.description.getMimeType(0)) + // Include URI in ClipData also, so that grantPermission picks it up. + setClipData( + ClipData( + ClipDescription("content", arrayOf(clipData.description.getMimeType(0))), + ClipData.Item(uri), + ) + ) + putExtra(Intent.EXTRA_STREAM, uri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } else { + putExtra(Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString()) + setType("text/plain") + } + } + + return Intent.createChooser(shareIntent, null) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + override 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)) + } + setDataAndType(uri, "image/*") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD) + } + } + + override fun getRemoteCopyIntent(clipData: ClipData?, context: Context): Intent { + val remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage) + return Intent(REMOTE_COPY_ACTION).apply { + if (!TextUtils.isEmpty(remoteCopyPackage)) { + setComponent(ComponentName.unflattenFromString(remoteCopyPackage)) + } + + setClipData(clipData) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + } + + companion object { + private const val EXTRA_EDIT_SOURCE: String = "edit_source" + private const val EDIT_SOURCE_CLIPBOARD: String = "clipboard" + private const val REMOTE_COPY_ACTION: String = "android.intent.action.REMOTE_COPY" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index ac747845267c..314b6e7f5a28 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -65,7 +65,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext; import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; import com.android.systemui.screenshot.TimeoutHandler; @@ -94,13 +93,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private final ClipboardOverlayWindow mWindow; private final TimeoutHandler mTimeoutHandler; private final ClipboardOverlayUtils mClipboardUtils; - private final FeatureFlags mFeatureFlags; private final Executor mBgExecutor; private final ClipboardImageLoader mClipboardImageLoader; private final ClipboardTransitionExecutor mTransitionExecutor; private final ClipboardOverlayView mView; private final ClipboardIndicationProvider mClipboardIndicationProvider; + private final IntentCreator mIntentCreator; private Runnable mOnSessionCompleteListener; private Runnable mOnRemoteCopyTapped; @@ -189,13 +188,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv BroadcastDispatcher broadcastDispatcher, BroadcastSender broadcastSender, TimeoutHandler timeoutHandler, - FeatureFlags featureFlags, ClipboardOverlayUtils clipboardUtils, @Background Executor bgExecutor, ClipboardImageLoader clipboardImageLoader, ClipboardTransitionExecutor transitionExecutor, ClipboardIndicationProvider clipboardIndicationProvider, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + IntentCreator intentCreator) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mClipboardImageLoader = clipboardImageLoader; @@ -203,6 +202,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv mClipboardIndicationProvider = clipboardIndicationProvider; mClipboardLogger = new ClipboardLogger(uiEventLogger); + mIntentCreator = intentCreator; mView = clipboardOverlayView; mWindow = clipboardOverlayWindow; @@ -211,7 +211,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv hideImmediate(); }); - mFeatureFlags = featureFlags; mTimeoutHandler = timeoutHandler; mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS); @@ -508,7 +507,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv } private void maybeShowRemoteCopy(ClipData clipData) { - Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext); + Intent remoteCopyIntent = mIntentCreator.getRemoteCopyIntent(clipData, mContext); + // Only show remote copy if it's available. PackageManager packageManager = mContext.getPackageManager(); if (packageManager.resolveActivity( @@ -558,19 +558,19 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private void editImage(Uri uri) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext)); + mContext.startActivity(mIntentCreator.getImageEditIntent(uri, mContext)); animateOut(); } private void editText() { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - mContext.startActivity(IntentCreator.getTextEditorIntent(mContext)); + mContext.startActivity(mIntentCreator.getTextEditorIntent(mContext)); animateOut(); } private void shareContent(ClipData clip) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED); - mContext.startActivity(IntentCreator.getShareIntent(clip, mContext)); + mContext.startActivity(mIntentCreator.getShareIntent(clip, mContext)); animateOut(); } @@ -717,22 +717,22 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv public void onRemoteCopyButtonTapped() { if (clipboardSharedTransitions()) { finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED, - IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext)); + mIntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext)); } } @Override public void onShareButtonTapped() { if (clipboardSharedTransitions()) { + Intent shareIntent = + mIntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext); switch (mClipboardModel.getType()) { case TEXT: case URI: - finish(CLIPBOARD_OVERLAY_SHARE_TAPPED, - IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext)); + finish(CLIPBOARD_OVERLAY_SHARE_TAPPED, shareIntent); break; case IMAGE: - finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED, - IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext)); + finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED, shareIntent); break; } } @@ -744,11 +744,11 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv switch (mClipboardModel.getType()) { case TEXT: finish(CLIPBOARD_OVERLAY_EDIT_TAPPED, - IntentCreator.getTextEditorIntent(mContext)); + mIntentCreator.getTextEditorIntent(mContext)); break; case IMAGE: finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED, - IntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext)); + mIntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext)); break; default: Log.w(TAG, "Got preview tapped callback for non-editable type " diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java new file mode 100644 index 000000000000..4b24536ad28f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java @@ -0,0 +1,102 @@ +/* + * 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.clipboardoverlay; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.res.R; + +import javax.inject.Inject; + +@SysUISingleton +public class DefaultIntentCreator implements IntentCreator { + private static final String EXTRA_EDIT_SOURCE = "edit_source"; + private static final String EDIT_SOURCE_CLIPBOARD = "clipboard"; + private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY"; + + @Inject + public DefaultIntentCreator() {} + + public Intent getTextEditorIntent(Context context) { + Intent intent = new Intent(context, EditTextActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return intent; + } + + public Intent getShareIntent(ClipData clipData, Context context) { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + + // From the ACTION_SEND docs: + // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the + // MIME type of the data in EXTRA_STREAM" + Uri uri = clipData.getItemAt(0).getUri(); + if (uri != null) { + // We don't use setData here because some apps interpret this as "to:". + shareIntent.setType(clipData.getDescription().getMimeType(0)); + // Include URI in ClipData also, so that grantPermission picks it up. + shareIntent.setClipData(new ClipData( + new ClipDescription( + "content", new String[]{clipData.getDescription().getMimeType(0)}), + new ClipData.Item(uri))); + shareIntent.putExtra(Intent.EXTRA_STREAM, uri); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + shareIntent.putExtra( + Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString()); + shareIntent.setType("text/plain"); + } + Intent chooserIntent = Intent.createChooser(shareIntent, null) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + return chooserIntent; + } + + public Intent getImageEditIntent(Uri uri, Context context) { + String editorPackage = context.getString(R.string.config_screenshotEditor); + Intent editIntent = new Intent(Intent.ACTION_EDIT); + if (!TextUtils.isEmpty(editorPackage)) { + editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); + } + editIntent.setDataAndType(uri, "image/*"); + editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD); + return editIntent; + } + + public Intent getRemoteCopyIntent(ClipData clipData, Context context) { + Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION); + + String remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage); + if (!TextUtils.isEmpty(remoteCopyPackage)) { + nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage)); + } + + nearbyIntent.setClipData(clipData); + nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return nearbyIntent; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java index a18b4c84b081..c8a6b05f090b 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2025 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. @@ -17,79 +17,13 @@ package com.android.systemui.clipboardoverlay; import android.content.ClipData; -import android.content.ClipDescription; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.text.TextUtils; -import com.android.systemui.res.R; - -class IntentCreator { - private static final String EXTRA_EDIT_SOURCE = "edit_source"; - private static final String EDIT_SOURCE_CLIPBOARD = "clipboard"; - private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY"; - - static Intent getTextEditorIntent(Context context) { - Intent intent = new Intent(context, EditTextActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - return intent; - } - - static Intent getShareIntent(ClipData clipData, Context context) { - Intent shareIntent = new Intent(Intent.ACTION_SEND); - - // From the ACTION_SEND docs: - // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the - // MIME type of the data in EXTRA_STREAM" - Uri uri = clipData.getItemAt(0).getUri(); - if (uri != null) { - // We don't use setData here because some apps interpret this as "to:". - shareIntent.setType(clipData.getDescription().getMimeType(0)); - // Include URI in ClipData also, so that grantPermission picks it up. - shareIntent.setClipData(new ClipData( - new ClipDescription( - "content", new String[]{clipData.getDescription().getMimeType(0)}), - new ClipData.Item(uri))); - shareIntent.putExtra(Intent.EXTRA_STREAM, uri); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } else { - shareIntent.putExtra( - Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context).toString()); - shareIntent.setType("text/plain"); - } - Intent chooserIntent = Intent.createChooser(shareIntent, null) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - return chooserIntent; - } - - static Intent getImageEditIntent(Uri uri, Context context) { - String editorPackage = context.getString(R.string.config_screenshotEditor); - Intent editIntent = new Intent(Intent.ACTION_EDIT); - if (!TextUtils.isEmpty(editorPackage)) { - editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); - } - editIntent.setDataAndType(uri, "image/*"); - editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD); - return editIntent; - } - - static Intent getRemoteCopyIntent(ClipData clipData, Context context) { - Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION); - - String remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage); - if (!TextUtils.isEmpty(remoteCopyPackage)) { - nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage)); - } - - nearbyIntent.setClipData(clipData); - nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - return nearbyIntent; - } +public interface IntentCreator { + Intent getTextEditorIntent(Context context); + Intent getShareIntent(ClipData clipData, Context context); + Intent getImageEditIntent(Uri uri, Context context); + Intent getRemoteCopyIntent(ClipData clipData, Context context); } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java index 6c10eea07ffc..c86a84b17efe 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java @@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static com.android.systemui.Flags.clipboardOverlayMultiuser; import static com.android.systemui.Flags.enableViewCaptureTracing; +import static com.android.systemui.shared.Flags.usePreferredImageEditor; import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -32,7 +33,10 @@ import android.view.WindowManager; import com.android.app.viewcapture.ViewCapture; import com.android.app.viewcapture.ViewCaptureAwareWindowManager; +import com.android.systemui.clipboardoverlay.ActionIntentCreator; import com.android.systemui.clipboardoverlay.ClipboardOverlayView; +import com.android.systemui.clipboardoverlay.DefaultIntentCreator; +import com.android.systemui.clipboardoverlay.IntentCreator; import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; @@ -102,6 +106,17 @@ public interface ClipboardOverlayModule { /* isViewCaptureEnabled= */ enableViewCaptureTracing()); } + @Provides + static IntentCreator provideIntentCreator( + Lazy<DefaultIntentCreator> defaultIntentCreator, + Lazy<ActionIntentCreator> actionIntentCreator) { + if (usePreferredImageEditor()) { + return actionIntentCreator.get(); + } else { + return defaultIntentCreator.get(); + } + } + @Qualifier @Documented @Retention(RUNTIME) diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index 562481567536..5c893da45b8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -28,7 +28,6 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED; -import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -60,7 +59,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastSender; -import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.screenshot.TimeoutHandler; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.util.concurrency.FakeExecutor; @@ -102,7 +100,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Mock private UiEventLogger mUiEventLogger; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); - private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private Animator mAnimator; @@ -152,8 +149,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mSampleClipData = new ClipData("Test", new String[]{"text/plain"}, new ClipData.Item("Test Item")); - - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests } /** @@ -170,13 +165,13 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { getFakeBroadcastDispatcher(), mBroadcastSender, mTimeoutHandler, - mFeatureFlags, mClipboardUtils, mExecutor, mClipboardImageLoader, mClipboardTransitionExecutor, mClipboardIndicationProvider, - mUiEventLogger); + mUiEventLogger, + new ActionIntentCreator()); verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); mCallbacks = mOverlayCallbacksCaptor.getValue(); } @@ -193,7 +188,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { ClipData clipData = new ClipData("", new String[]{"image/png"}, new ClipData.Item(Uri.parse(""))); - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); mOverlayController.setClipData(clipData, ""); @@ -208,7 +202,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { ClipData clipData = new ClipData("", new String[]{"resource/png"}, new ClipData.Item(Uri.parse(""))); - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); mOverlayController.setClipData(clipData, ""); @@ -219,7 +212,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Test public void test_setClipData_textData_legacy() { - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); initController(); mOverlayController.setClipData(mSampleClipData, "abc"); @@ -232,7 +224,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Test public void test_setClipData_sensitiveTextData_legacy() { - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); initController(); ClipDescription description = mSampleClipData.getDescription(); @@ -250,7 +241,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Test public void test_setClipData_repeatedCalls_legacy() { when(mAnimator.isRunning()).thenReturn(true); - mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false); initController(); mOverlayController.setClipData(mSampleClipData, ""); |