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

Commit 0a6b8a99 authored by Miranda Kephart's avatar Miranda Kephart
Browse files

Add action chips to clipboard overlay

Uses TextClassifier to obtain additional action chips (e.g. open
Maps for an address) when text is copied.

Bug: 195554988
Test: manual
Change-Id: I65e7d244a083c5c4ea4cbf7476bd5ca94a0cc443
parent 961f9be1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
<com.android.systemui.clipboardoverlay.DraggableConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:alpha="0"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
+2 −2
Original line number Diff line number Diff line
@@ -60,8 +60,8 @@ public class ClipboardListener extends CoreStartable
            return;
        }
        if (mClipboardOverlayController == null) {
            mClipboardOverlayController = new ClipboardOverlayController(mContext,
                    new TimeoutHandler(mContext));
            mClipboardOverlayController =
                    new ClipboardOverlayController(mContext, new TimeoutHandler(mContext));
        }
        mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip());
        mClipboardOverlayController.setOnSessionCompleteListener(() -> {
+71 −11
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.MainThread;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ComponentName;
@@ -43,6 +44,7 @@ import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
@@ -60,8 +62,13 @@ import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.android.internal.policy.PhoneWindow;
@@ -71,6 +78,7 @@ import com.android.systemui.screenshot.ScreenshotActionChip;
import com.android.systemui.screenshot.TimeoutHandler;

import java.io.IOException;
import java.util.ArrayList;

/**
 * Controls state and UI for the overlay that appears when something is added to the clipboard
@@ -93,6 +101,7 @@ public class ClipboardOverlayController {
    private final PhoneWindow mWindow;
    private final TimeoutHandler mTimeoutHandler;
    private final AccessibilityManager mAccessibilityManager;
    private final TextClassifier mTextClassifier;

    private final DraggableConstraintLayout mView;
    private final ImageView mImagePreview;
@@ -101,9 +110,13 @@ public class ClipboardOverlayController {
    private final ScreenshotActionChip mRemoteCopyChip;
    private final View mActionContainerBackground;
    private final View mDismissButton;
    private final LinearLayout mActionContainer;
    private final ArrayList<ScreenshotActionChip> mActionChips = new ArrayList<>();

    private Runnable mOnSessionCompleteListener;


    private InputMonitor mInputMonitor;
    private InputEventReceiver mInputEventReceiver;

    private BroadcastReceiver mCloseDialogsReceiver;
@@ -117,6 +130,8 @@ public class ClipboardOverlayController {
        mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);

        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
        mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
                .getTextClassifier();

        mWindowManager = mContext.getSystemService(WindowManager.class);

@@ -134,8 +149,9 @@ public class ClipboardOverlayController {

        mView = (DraggableConstraintLayout)
                LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
        mActionContainerBackground = requireNonNull(
                mView.findViewById(R.id.actions_container_background));
        mActionContainerBackground =
                requireNonNull(mView.findViewById(R.id.actions_container_background));
        mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
        mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
        mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
        mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
@@ -143,7 +159,7 @@ public class ClipboardOverlayController {
        mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));

        mView.setOnDismissCallback(this::hideImmediate);
        mView.setOnInteractionCallback(() -> mTimeoutHandler.resetTimeout());
        mView.setOnInteractionCallback(mTimeoutHandler::resetTimeout);

        mDismissButton.setOnClickListener(view -> animateOut());

@@ -166,7 +182,7 @@ public class ClipboardOverlayController {
        withWindowAttached(() -> {
            mWindow.setContentView(mView);
            updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
            mView.post(() -> getEnterAnimation().start());
            mView.post(this::animateIn);
        });

        mTimeoutHandler.setOnTimeoutRunnable(this::animateOut);
@@ -199,12 +215,15 @@ public class ClipboardOverlayController {

    void setClipData(ClipData clipData) {
        reset();

        if (clipData == null || clipData.getItemCount() == 0) {
            showTextPreview(
                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
            showTextPreview(mContext.getResources().getString(
                    R.string.clipboard_overlay_text_copied));
        } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
            showEditableText(clipData.getItemAt(0).getText());
            ClipData.Item item = clipData.getItemAt(0);
            if (item.getTextLinks() != null) {
                AsyncTask.execute(() -> classifyText(clipData.getItemAt(0)));
            }
            showEditableText(item.getText());
        } else if (clipData.getItemAt(0).getUri() != null) {
            // How to handle non-image URIs?
            showEditableImage(clipData.getItemAt(0).getUri());
@@ -212,7 +231,6 @@ public class ClipboardOverlayController {
            showTextPreview(
                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
        }

        mTimeoutHandler.resetTimeout();
    }

@@ -220,10 +238,40 @@ public class ClipboardOverlayController {
        mOnSessionCompleteListener = runnable;
    }

    private void classifyText(ClipData.Item item) {
        ArrayList<RemoteAction> actions = new ArrayList<>();
        for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
            TextClassification classification = mTextClassifier.classifyText(
                    item.getText(), link.getStart(), link.getEnd(), null);
            actions.addAll(classification.getActions());
        }
        mView.post(() -> {
            for (ScreenshotActionChip chip : mActionChips) {
                mActionContainer.removeView(chip);
            }
            mActionChips.clear();
            for (RemoteAction action : actions) {
                ScreenshotActionChip chip = constructActionChip(action);
                mActionContainer.addView(chip);
                mActionChips.add(chip);
            }
        });
    }

    private ScreenshotActionChip constructActionChip(RemoteAction action) {
        ScreenshotActionChip chip = (ScreenshotActionChip) LayoutInflater.from(mContext).inflate(
                R.layout.screenshot_action_chip, mActionContainer, false);
        chip.setText(action.getTitle());
        chip.setIcon(action.getIcon(), false);
        chip.setPendingIntent(action.getActionIntent(), this::animateOut);
        chip.setAlpha(1);
        return chip;
    }

    private void monitorOutsideTouches() {
        InputManager inputManager = mContext.getSystemService(InputManager.class);
        InputMonitor monitor = inputManager.monitorGestureInput("clipboard overlay", 0);
        mInputEventReceiver = new InputEventReceiver(monitor.getInputChannel(),
        mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
        mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
                Looper.getMainLooper()) {
            @Override
            public void onInputEvent(InputEvent event) {
@@ -311,6 +359,10 @@ public class ClipboardOverlayController {
        return nearbyIntent;
    }

    private void animateIn() {
        getEnterAnimation().start();
    }

    private void animateOut() {
        getExitAnimation().start();
    }
@@ -390,6 +442,10 @@ public class ClipboardOverlayController {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        if (mInputMonitor != null) {
            mInputMonitor.dispose();
            mInputMonitor = null;
        }
        if (mOnSessionCompleteListener != null) {
            mOnSessionCompleteListener.run();
        }
@@ -398,6 +454,10 @@ public class ClipboardOverlayController {
    private void reset() {
        mView.setTranslationX(0);
        mView.setAlpha(0);
        for (ScreenshotActionChip chip : mActionChips) {
            mActionContainer.removeView(chip);
        }
        mActionChips.clear();
        mTimeoutHandler.cancelTimeout();
    }

+0 −2
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.content.Context;
import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -129,7 +128,6 @@ public class ScreenshotActionChip extends FrameLayout {
            iconParams.setMarginStart(paddingHorizontal);
            iconParams.setMarginEnd(paddingHorizontal);
        }
        mTextView.setVisibility(hasText ? View.VISIBLE : View.GONE);
        mIconView.setLayoutParams(iconParams);
        mTextView.setLayoutParams(textParams);
    }