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

Commit e9e80719 authored by Miranda Kephart's avatar Miranda Kephart
Browse files

Make all copied URIs shareable

We can only display thumbnails for images, but the sharesheet can
handle other types of URIs. Add the share button for these types
of copied data.

Also switches to not show UI if the clipData is null, and just show a toast if the clipboard contents have no items. (These cases should be approximately unreachable.)

Bug: 268057213
Fix: 268057213
Test: atest
Change-Id: I4c1e118c38c48c8ea8fd0d89ee426d7d961460af
parent eb87dfe8
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -97,8 +97,9 @@ public class ClipboardListener implements
            return;
        }

        if (!isUserSetupComplete()) {
            // just show a toast, user should not access intents from this state
        if (!isUserSetupComplete() // user should not access intents from this state
                || clipData == null // shouldn't happen, but just in case
                || clipData.getItemCount() == 0) {
            if (shouldShowToast(clipData)) {
                mUiEventLogger.log(CLIPBOARD_TOAST_SHOWN, 0, clipSource);
                mClipboardToast.showCopiedToast();
+31 −20
Original line number Diff line number Diff line
@@ -19,19 +19,23 @@ import android.content.ClipData
import android.content.ClipDescription.EXTRA_IS_SENSITIVE
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.text.TextUtils
import android.util.Log
import android.util.Size
import android.view.textclassifier.TextLinks
import com.android.systemui.R
import java.io.IOException

data class ClipboardModel(
    val clipData: ClipData?,
    val clipData: ClipData,
    val source: String,
    val type: Type = Type.OTHER,
    val item: ClipData.Item? = null,
    val isSensitive: Boolean = false,
    val isRemote: Boolean = false,
    val type: Type,
    val text: CharSequence?,
    val textLinks: TextLinks?,
    val uri: Uri?,
    val isSensitive: Boolean,
    val isRemote: Boolean,
) {
    private var _bitmap: Bitmap? = null

@@ -41,17 +45,16 @@ data class ClipboardModel(
        }
        return source == other.source &&
            type == other.type &&
            item?.text == other.item?.text &&
            item?.uri == other.item?.uri &&
            text == other.text &&
            uri == other.uri &&
            isSensitive == other.isSensitive
    }

    fun loadThumbnail(context: Context): Bitmap? {
        if (_bitmap == null && type == Type.IMAGE && item?.uri != null) {
        if (_bitmap == null && type == Type.IMAGE && uri != null) {
            try {
                val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
                _bitmap =
                    context.contentResolver.loadThumbnail(item.uri, Size(size, size * 4), null)
                _bitmap = context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
            } catch (e: IOException) {
                Log.e(TAG, "Thumbnail loading failed!", e)
            }
@@ -66,27 +69,34 @@ data class ClipboardModel(
        fun fromClipData(
            context: Context,
            utils: ClipboardOverlayUtils,
            clipData: ClipData?,
            clipData: ClipData,
            source: String
        ): ClipboardModel {
            if (clipData == null || clipData.itemCount == 0) {
                return ClipboardModel(clipData, source)
            }
            val sensitive = clipData.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
            val item = clipData.getItemAt(0)!!
            val type = getType(context, item)
            val remote = utils.isRemoteCopy(context, clipData, source)
            return ClipboardModel(clipData, source, type, item, sensitive, remote)
            return ClipboardModel(
                clipData,
                source,
                type,
                item.text,
                item.textLinks,
                item.uri,
                sensitive,
                remote
            )
        }

        private fun getType(context: Context, item: ClipData.Item): Type {
            return if (!TextUtils.isEmpty(item.text)) {
                Type.TEXT
            } else if (
                item.uri != null &&
                    context.contentResolver.getType(item.uri)?.startsWith("image") == true
            ) {
            } else if (item.uri != null) {
                if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
                    Type.IMAGE
                } else {
                    Type.URI
                }
            } else {
                Type.OTHER
            }
@@ -96,6 +106,7 @@ data class ClipboardModel(
    enum class Type {
        TEXT,
        IMAGE,
        URI,
        OTHER
    }
}
+8 −7
Original line number Diff line number Diff line
@@ -309,14 +309,14 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
                if ((mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && model.isRemote())
                        || DeviceConfig.getBoolean(
                        DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
                    if (model.getItem().getTextLinks() != null) {
                    if (model.getTextLinks() != null) {
                        classifyText(model);
                    }
                }
                if (model.isSensitive()) {
                    mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
                } else {
                    mView.showTextPreview(model.getItem().getText(), false);
                    mView.showTextPreview(model.getText(), false);
                }
                mView.setEditAccessibilityAction(true);
                mOnPreviewTapped = this::editText;
@@ -326,12 +326,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
                    mView.showImagePreview(
                            model.isSensitive() ? null : model.loadThumbnail(mContext));
                    mView.setEditAccessibilityAction(true);
                    mOnPreviewTapped = () -> editImage(model.getItem().getUri());
                    mOnPreviewTapped = () -> editImage(model.getUri());
                } else {
                    // image loading failed
                    mView.showDefaultTextPreview();
                }
                break;
            case URI:
            case OTHER:
                mView.showDefaultTextPreview();
                break;
@@ -371,8 +372,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv

    private void classifyText(ClipboardModel model) {
        mBgExecutor.execute(() -> {
            Optional<RemoteAction> remoteAction =
                    mClipboardUtils.getAction(model.getItem(), model.getSource());
            Optional<RemoteAction> remoteAction = mClipboardUtils.getAction(
                            model.getText(), model.getTextLinks(), model.getSource());
            if (model.equals(mClipboardModel)) {
                remoteAction.ifPresent(action -> {
                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
@@ -419,10 +420,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
            accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
        } else if (clipData.getItemAt(0).getUri() != null) {
            if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
                mOnShareTapped = () -> shareContent(clipData);
                mView.showShareChip();
                accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
            }
            mOnShareTapped = () -> shareContent(clipData);
            mView.showShareChip();
        } else {
            mView.showDefaultTextPreview();
        }
+17 −0
Original line number Diff line number Diff line
@@ -65,6 +65,23 @@ class ClipboardOverlayUtils {
        return false;
    }

    public Optional<RemoteAction> getAction(CharSequence text, TextLinks textLinks, String source) {
        return getActions(text, textLinks).stream().filter(remoteAction -> {
            ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
            return component != null && !TextUtils.equals(source, component.getPackageName());
        }).findFirst();
    }

    private ArrayList<RemoteAction> getActions(CharSequence text, TextLinks textLinks) {
        ArrayList<RemoteAction> actions = new ArrayList<>();
        for (TextLinks.TextLink link : textLinks.getLinks()) {
            TextClassification classification = mTextClassifier.classifyText(
                    text, link.getStart(), link.getEnd(), null);
            actions.addAll(classification.getActions());
        }
        return actions;
    }

    public Optional<RemoteAction> getAction(ClipData.Item item, String source) {
        return getActions(item).stream().filter(remoteAction -> {
            ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
+29 −0
Original line number Diff line number Diff line
@@ -51,6 +51,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import java.util.ArrayList;

import javax.inject.Provider;

@SmallTest
@@ -194,6 +196,33 @@ public class ClipboardListenerTest extends SysuiTestCase {
        verifyZeroInteractions(mOverlayControllerProvider);
    }

    @Test
    public void test_nullClipData_showsNothing() {
        when(mClipboardManager.getPrimaryClip()).thenReturn(null);

        mClipboardListener.start();
        mClipboardListener.onPrimaryClipChanged();

        verifyZeroInteractions(mUiEventLogger);
        verifyZeroInteractions(mClipboardToast);
        verifyZeroInteractions(mOverlayControllerProvider);
    }

    @Test
    public void test_emptyClipData_showsToast() {
        ClipDescription description = new ClipDescription("Test", new String[0]);
        ClipData noItems = new ClipData(description, new ArrayList<>());
        when(mClipboardManager.getPrimaryClip()).thenReturn(noItems);

        mClipboardListener.start();
        mClipboardListener.onPrimaryClipChanged();

        verify(mUiEventLogger, times(1)).log(
                ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
        verify(mClipboardToast, times(1)).showCopiedToast();
        verifyZeroInteractions(mOverlayControllerProvider);
    }

    @Test
    public void test_minimizedLayoutFlagOff_usesLegacy() {
        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
Loading