diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index c21e36ab6ecca..7e499ebdf6916 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -119,15 +119,12 @@ import java.util.ArrayList; */ public class ClipboardOverlayController { private static final String TAG = "ClipboardOverlayCtrlr"; - private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY"; /** Constants for screenshot/copy deconflicting */ public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT"; public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF"; public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY"; - private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard"; - private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000; private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe private static final int FONT_SEARCH_STEP_PX = 4; @@ -383,7 +380,7 @@ public class ClipboardOverlayController { mTextPreview); accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); } - Intent remoteCopyIntent = getRemoteCopyIntent(clipData); + Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext); // Only show remote copy if it's available. PackageManager packageManager = mContext.getPackageManager(); if (packageManager.resolveActivity( @@ -500,41 +497,19 @@ public class ClipboardOverlayController { private void editImage(Uri uri) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - String editorPackage = mContext.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_CLIPBOARD, true); - mContext.startActivity(editIntent); + mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext)); animateOut(); } private void editText() { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - Intent editIntent = new Intent(mContext, EditTextActivity.class); - editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - mContext.startActivity(editIntent); + mContext.startActivity(IntentCreator.getTextEditorIntent(mContext)); animateOut(); } private void shareContent(ClipData clip) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED); - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.setDataAndType( - clip.getItemAt(0).getUri(), clip.getDescription().getMimeType(0)); - shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText().toString()); - if (clip.getItemAt(0).getUri() != null) { - shareIntent.putExtra(Intent.EXTRA_STREAM, clip.getItemAt(0).getUri()); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - Intent chooserIntent = Intent.createChooser(shareIntent, null) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - mContext.startActivity(chooserIntent); + mContext.startActivity(IntentCreator.getShareIntent(clip, mContext)); animateOut(); } @@ -667,20 +642,6 @@ public class ClipboardOverlayController { mContext.getString(R.string.clipboard_edit), null); } - private Intent getRemoteCopyIntent(ClipData clipData) { - Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION); - - String remoteCopyPackage = mContext.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; - } - private void animateIn() { if (mAccessibilityManager.isEnabled()) { mDismissButton.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java new file mode 100644 index 0000000000000..3d5e601f18f53 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java @@ -0,0 +1,86 @@ +/* + * 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.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; + +import com.android.systemui.R; + +class IntentCreator { + private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_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" + if (clipData.getItemAt(0).getUri() != null) { + shareIntent.setDataAndType( + clipData.getItemAt(0).getUri(), clipData.getDescription().getMimeType(0)); + shareIntent.putExtra(Intent.EXTRA_STREAM, clipData.getItemAt(0).getUri()); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + shareIntent.putExtra(Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context)); + 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_CLIPBOARD, true); + 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; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java new file mode 100644 index 0000000000000..08fe7c4865297 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java @@ -0,0 +1,137 @@ +/* + * 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.ComponentName; +import android.content.Intent; +import android.net.Uri; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class IntentCreatorTest 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; + + @Test + public void test_getTextEditorIntent() { + Intent intent = IntentCreator.getTextEditorIntent(getContext()); + assertEquals(new ComponentName(getContext(), EditTextActivity.class), + intent.getComponent()); + assertFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + } + + @Test + public void test_getRemoteCopyIntent() { + getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, + ""); + + ClipData clipData = ClipData.newPlainText("Test", "Test Item"); + Intent intent = IntentCreator.getRemoteCopyIntent(clipData, getContext()); + + assertEquals(null, intent.getComponent()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + assertEquals(clipData, intent.getClipData()); + + // Try again with a remote copy component + ComponentName fakeComponent = new ComponentName("com.android.remotecopy", + "com.android.remotecopy.RemoteCopyActivity"); + getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, + fakeComponent.flattenToString()); + + intent = IntentCreator.getRemoteCopyIntent(clipData, getContext()); + assertEquals(fakeComponent, intent.getComponent()); + } + + @Test + public void test_getImageEditIntent() { + getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, + ""); + Uri fakeUri = Uri.parse("content://foo"); + Intent intent = IntentCreator.getImageEditIntent(fakeUri, getContext()); + + assertEquals(Intent.ACTION_EDIT, intent.getAction()); + assertEquals("image/*", intent.getType()); + assertEquals(null, intent.getComponent()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + + // try again with an editor component + ComponentName fakeComponent = new ComponentName("com.android.remotecopy", + "com.android.remotecopy.RemoteCopyActivity"); + getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, + fakeComponent.flattenToString()); + intent = IntentCreator.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()); + + assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); + assertEquals("Test Item", target.getStringExtra(Intent.EXTRA_TEXT)); + assertEquals("text/plain", target.getType()); + } + + @Test + public void test_getShareIntent_html() { + ClipData clipData = ClipData.newHtmlText("Test", "Some HTML", + "Some HTML"); + Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + + assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); + assertEquals("Some HTML", target.getStringExtra(Intent.EXTRA_TEXT)); + assertEquals("text/plain", target.getType()); + } + + @Test + public void test_getShareIntent_image() { + 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()); + + assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); + assertEquals(uri, target.getData()); + assertEquals("image/png", target.getType()); + } + + // Assert that the given flags are set + private void assertFlags(Intent intent, int flags) { + assertTrue((intent.getFlags() & flags) == flags); + } + +}