summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Miranda Kephart <mkephart@google.com> 2023-02-01 19:34:18 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-02-01 19:34:18 +0000
commit16bc6a5ebd1633f15806827e82f6ed1ad5d47f8c (patch)
tree6fee9883a256af788cdc3e89351148c656eb2aae
parent7b0429e3d793e4a7f2cd8f5f204f9a3c3962a3ce (diff)
parent1a70395d523886b90a5d70382895dd54e5c8dbb9 (diff)
Merge "Implement minimized clipboard layout" into tm-qpr-dev
-rw-r--r--packages/SystemUI/res/drawable/clipboard_minimized_background.xml23
-rw-r--r--packages/SystemUI/res/drawable/ic_content_paste.xml25
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml47
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt101
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java167
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java205
11 files changed, 764 insertions, 44 deletions
diff --git a/packages/SystemUI/res/drawable/clipboard_minimized_background.xml b/packages/SystemUI/res/drawable/clipboard_minimized_background.xml
new file mode 100644
index 000000000000..a179c146b280
--- /dev/null
+++ b/packages/SystemUI/res/drawable/clipboard_minimized_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorAccentSecondary"/>
+ <corners android:radius="10dp"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/ic_content_paste.xml b/packages/SystemUI/res/drawable/ic_content_paste.xml
new file mode 100644
index 000000000000..8c8b81ec10d1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_content_paste.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9,42Q7.7,42 6.85,41.15Q6,40.3 6,39V9Q6,7.7 6.85,6.85Q7.7,6 9,6H19.1Q19.45,4.25 20.825,3.125Q22.2,2 24,2Q25.8,2 27.175,3.125Q28.55,4.25 28.9,6H39Q40.3,6 41.15,6.85Q42,7.7 42,9V39Q42,40.3 41.15,41.15Q40.3,42 39,42ZM9,39H39Q39,39 39,39Q39,39 39,39V9Q39,9 39,9Q39,9 39,9H36V13.5H12V9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39ZM24,9Q24.85,9 25.425,8.425Q26,7.85 26,7Q26,6.15 25.425,5.575Q24.85,5 24,5Q23.15,5 22.575,5.575Q22,6.15 22,7Q22,7.85 22.575,8.425Q23.15,9 24,9Z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index eec3b11519b1..9b01bd8c7d80 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -125,6 +125,45 @@
android:layout_width="@dimen/clipboard_preview_size"
android:layout_height="@dimen/clipboard_preview_size"/>
</FrameLayout>
+ <LinearLayout
+ android:id="@+id/minimized_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:elevation="7dp"
+ android:padding="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+ android:background="@drawable/clipboard_minimized_background">
+ <ImageView
+ android:src="@drawable/ic_content_paste"
+ android:tint="?attr/overlayButtonTextColor"
+ android:layout_width="24dp"
+ android:layout_height="24dp"/>
+ <ImageView
+ android:src="@*android:drawable/ic_chevron_end"
+ android:tint="?attr/overlayButtonTextColor"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:paddingEnd="-8dp"
+ android:paddingStart="-4dp"/>
+ </LinearLayout>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_content_top"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:barrierDirection="top"
+ app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_content_end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
<FrameLayout
android:id="@+id/dismiss_button"
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
@@ -132,10 +171,10 @@
android:elevation="10dp"
android:visibility="gone"
android:alpha="0"
- app:layout_constraintStart_toEndOf="@id/clipboard_preview"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview"
- app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+ app:layout_constraintStart_toEndOf="@id/clipboard_content_end"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_content_end"
+ app:layout_constraintTop_toTopOf="@id/clipboard_content_top"
+ app:layout_constraintBottom_toTopOf="@id/clipboard_content_top"
android:contentDescription="@string/clipboard_dismiss_description">
<ImageView
android:id="@+id/dismiss_image"
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 1c26841a00be..82bb7237cab0 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -21,6 +21,7 @@ import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
@@ -35,6 +36,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -57,6 +59,7 @@ public class ClipboardListener implements
private final Provider<ClipboardOverlayController> mOverlayProvider;
private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager;
+ private final FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private ClipboardOverlay mClipboardOverlay;
@@ -65,11 +68,13 @@ public class ClipboardListener implements
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
ClipboardManager clipboardManager,
+ FeatureFlags featureFlags,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager;
+ mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
}
@@ -107,7 +112,11 @@ public class ClipboardListener implements
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
}
- mClipboardOverlay.setClipData(clipData, clipSource);
+ if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+ mClipboardOverlay.setClipData(clipData, clipSource);
+ } else {
+ mClipboardOverlay.setClipDataLegacy(clipData, clipSource);
+ }
mClipboardOverlay.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
mClipboardOverlay = null;
@@ -150,6 +159,8 @@ public class ClipboardListener implements
}
interface ClipboardOverlay {
+ void setClipDataLegacy(ClipData clipData, String clipSource);
+
void setClipData(ClipData clipData, String clipSource);
void setOnSessionCompleteListener(Runnable runnable);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
new file mode 100644
index 000000000000..c7aaf09d6551
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 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.EXTRA_IS_SENSITIVE
+import android.content.Context
+import android.graphics.Bitmap
+import android.text.TextUtils
+import android.util.Log
+import android.util.Size
+import com.android.systemui.R
+import java.io.IOException
+
+data class ClipboardModel(
+ val clipData: ClipData?,
+ val source: String,
+ val type: Type = Type.OTHER,
+ val item: ClipData.Item? = null,
+ val isSensitive: Boolean = false,
+ val isRemote: Boolean = false,
+) {
+ private var _bitmap: Bitmap? = null
+
+ fun dataMatches(other: ClipboardModel?): Boolean {
+ if (other == null) {
+ return false
+ }
+ return source == other.source &&
+ type == other.type &&
+ item?.text == other.item?.text &&
+ item?.uri == other.item?.uri &&
+ isSensitive == other.isSensitive
+ }
+
+ fun loadThumbnail(context: Context): Bitmap? {
+ if (_bitmap == null && type == Type.IMAGE && item?.uri != null) {
+ try {
+ val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+ _bitmap =
+ context.contentResolver.loadThumbnail(item.uri, Size(size, size * 4), null)
+ } catch (e: IOException) {
+ Log.e(TAG, "Thumbnail loading failed!", e)
+ }
+ }
+ return _bitmap
+ }
+
+ internal companion object {
+ private val TAG: String = "ClipboardModel"
+
+ @JvmStatic
+ fun fromClipData(
+ context: Context,
+ utils: ClipboardOverlayUtils,
+ 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)
+ }
+
+ 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
+ ) {
+ Type.IMAGE
+ } else {
+ Type.OTHER
+ }
+ }
+ }
+
+ enum class Type {
+ TEXT,
+ IMAGE,
+ OTHER
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 8c8ee8a325a0..b41f30844e27 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -17,7 +17,6 @@
package com.android.systemui.clipboardoverlay;
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
@@ -31,10 +30,9 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
-import static java.util.Objects.requireNonNull;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.RemoteAction;
@@ -47,7 +45,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
-import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Looper;
@@ -55,14 +52,15 @@ import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
-import android.view.Display;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import androidx.annotation.NonNull;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -71,7 +69,6 @@ import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.Overl
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.screenshot.TimeoutHandler;
-import com.android.systemui.settings.DisplayTracker;
import java.io.IOException;
import java.util.Optional;
@@ -95,8 +92,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
private final Context mContext;
private final ClipboardLogger mClipboardLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final DisplayManager mDisplayManager;
- private final DisplayTracker mDisplayTracker;
private final ClipboardOverlayWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
private final ClipboardOverlayUtils mClipboardUtils;
@@ -122,6 +117,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
private Runnable mOnUiUpdate;
+ private boolean mIsMinimized;
+ private ClipboardModel mClipboardModel;
+
private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
new ClipboardOverlayView.ClipboardOverlayCallbacks() {
@Override
@@ -175,6 +173,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
animateOut();
}
+
+ @Override
+ public void onMinimizedViewTapped() {
+ if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+ animateFromMinimized();
+ }
+ }
};
@Inject
@@ -187,33 +192,28 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
FeatureFlags featureFlags,
ClipboardOverlayUtils clipboardUtils,
@Background Executor bgExecutor,
- UiEventLogger uiEventLogger,
- DisplayTracker displayTracker) {
+ UiEventLogger uiEventLogger) {
+ mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
- mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
- mDisplayTracker = displayTracker;
- final Context displayContext = context.createDisplayContext(getDefaultDisplay());
- mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mClipboardLogger = new ClipboardLogger(uiEventLogger);
mView = clipboardOverlayView;
mWindow = clipboardOverlayWindow;
- mWindow.init(mView::setInsets, () -> {
+ mWindow.init(this::onInsetsChanged, () -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
hideImmediate();
});
+ mFeatureFlags = featureFlags;
mTimeoutHandler = timeoutHandler;
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
- mFeatureFlags = featureFlags;
mClipboardUtils = clipboardUtils;
mBgExecutor = bgExecutor;
mView.setCallbacks(mClipboardCallbacks);
-
mWindow.withWindowAttached(() -> {
mWindow.setContentView(mView);
mView.setInsets(mWindow.getWindowInsets(),
@@ -258,8 +258,135 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
}
+ @VisibleForTesting
+ void onInsetsChanged(WindowInsets insets, int orientation) {
+ mView.setInsets(insets, orientation);
+ if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+ if (shouldShowMinimized(insets) && !mIsMinimized) {
+ mIsMinimized = true;
+ mView.setMinimized(true);
+ }
+ }
+ }
+
@Override // ClipboardListener.ClipboardOverlay
- public void setClipData(ClipData clipData, String clipSource) {
+ public void setClipData(ClipData data, String source) {
+ ClipboardModel model = ClipboardModel.fromClipData(mContext, mClipboardUtils, data, source);
+ if (mExitAnimator != null && mExitAnimator.isRunning()) {
+ mExitAnimator.cancel();
+ }
+ boolean shouldAnimate = !model.dataMatches(mClipboardModel);
+ mClipboardModel = model;
+ mClipboardLogger.setClipSource(mClipboardModel.getSource());
+ if (shouldAnimate) {
+ reset();
+ mClipboardLogger.setClipSource(mClipboardModel.getSource());
+ if (shouldShowMinimized(mWindow.getWindowInsets())) {
+ mIsMinimized = true;
+ mView.setMinimized(true);
+ } else {
+ setExpandedView();
+ }
+ animateIn();
+ mView.announceForAccessibility(getAccessibilityAnnouncement(mClipboardModel.getType()));
+ } else if (!mIsMinimized) {
+ setExpandedView();
+ }
+ if (mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && mClipboardModel.isRemote()) {
+ mTimeoutHandler.cancelTimeout();
+ mOnUiUpdate = null;
+ } else {
+ mOnUiUpdate = mTimeoutHandler::resetTimeout;
+ mOnUiUpdate.run();
+ }
+ }
+
+ private void setExpandedView() {
+ final ClipboardModel model = mClipboardModel;
+ mView.setMinimized(false);
+ switch (model.getType()) {
+ case TEXT:
+ if ((mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && model.isRemote())
+ || DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+ if (model.getItem().getTextLinks() != null) {
+ classifyText(model);
+ }
+ }
+ if (model.isSensitive()) {
+ mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
+ } else {
+ mView.showTextPreview(model.getItem().getText(), false);
+ }
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = this::editText;
+ break;
+ case IMAGE:
+ if (model.isSensitive() || model.loadThumbnail(mContext) != null) {
+ mView.showImagePreview(
+ model.isSensitive() ? null : model.loadThumbnail(mContext));
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = () -> editImage(model.getItem().getUri());
+ } else {
+ // image loading failed
+ mView.showDefaultTextPreview();
+ }
+ break;
+ case OTHER:
+ mView.showDefaultTextPreview();
+ break;
+ }
+ if (mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR)) {
+ if (!model.isRemote()) {
+ maybeShowRemoteCopy(model.getClipData());
+ }
+ } else {
+ maybeShowRemoteCopy(model.getClipData());
+ }
+ if (model.getType() != ClipboardModel.Type.OTHER) {
+ mOnShareTapped = () -> shareContent(model.getClipData());
+ mView.showShareChip();
+ }
+ }
+
+ private boolean shouldShowMinimized(WindowInsets insets) {
+ return insets.getInsets(WindowInsets.Type.ime()).bottom > 0;
+ }
+
+ private void animateFromMinimized() {
+ mIsMinimized = false;
+ setExpandedView();
+ animateIn();
+ }
+
+ private String getAccessibilityAnnouncement(ClipboardModel.Type type) {
+ if (type == ClipboardModel.Type.TEXT) {
+ return mContext.getString(R.string.clipboard_text_copied);
+ } else if (type == ClipboardModel.Type.IMAGE) {
+ return mContext.getString(R.string.clipboard_image_copied);
+ } else {
+ return mContext.getString(R.string.clipboard_content_copied);
+ }
+ }
+
+ private void classifyText(ClipboardModel model) {
+ mBgExecutor.execute(() -> {
+ Optional<RemoteAction> remoteAction =
+ mClipboardUtils.getAction(model.getItem(), model.getSource());
+ if (model.equals(mClipboardModel)) {
+ remoteAction.ifPresent(action -> {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
+ mView.setActionChip(action, () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+ animateOut();
+ });
+ });
+ }
+ });
+ }
+
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setClipDataLegacy(ClipData clipData, String clipSource) {
if (mExitAnimator != null && mExitAnimator.isRunning()) {
mExitAnimator.cancel();
}
@@ -516,10 +643,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
mClipboardLogger.reset();
}
- private Display getDefaultDisplay() {
- return mDisplayManager.getDisplay(mDisplayTracker.getDefaultDisplayId());
- }
-
static class ClipboardLogger {
private final UiEventLogger mUiEventLogger;
private String mClipSource;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 2d3315759371..c9e01ce179f6 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,8 +18,6 @@ package com.android.systemui.clipboardoverlay;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static java.util.Objects.requireNonNull;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -77,6 +75,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
void onShareButtonTapped();
void onPreviewTapped();
+
+ void onMinimizedViewTapped();
}
private static final String TAG = "ClipboardView";
@@ -92,6 +92,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
private ImageView mImagePreview;
private TextView mTextPreview;
private TextView mHiddenPreview;
+ private LinearLayout mMinimizedPreview;
private View mPreviewBorder;
private OverlayActionChip mEditChip;
private OverlayActionChip mShareChip;
@@ -117,18 +118,18 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
@Override
protected void onFinishInflate() {
- mActionContainerBackground =
- requireNonNull(findViewById(R.id.actions_container_background));
- mActionContainer = requireNonNull(findViewById(R.id.actions));
- mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview));
- mImagePreview = requireNonNull(findViewById(R.id.image_preview));
- mTextPreview = requireNonNull(findViewById(R.id.text_preview));
- mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview));
- mPreviewBorder = requireNonNull(findViewById(R.id.preview_border));
- mEditChip = requireNonNull(findViewById(R.id.edit_chip));
- mShareChip = requireNonNull(findViewById(R.id.share_chip));
- mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip));
- mDismissButton = requireNonNull(findViewById(R.id.dismiss_button));
+ mActionContainerBackground = requireViewById(R.id.actions_container_background);
+ mActionContainer = requireViewById(R.id.actions);
+ mClipboardPreview = requireViewById(R.id.clipboard_preview);
+ mPreviewBorder = requireViewById(R.id.preview_border);
+ mImagePreview = requireViewById(R.id.image_preview);
+ mTextPreview = requireViewById(R.id.text_preview);
+ mHiddenPreview = requireViewById(R.id.hidden_preview);
+ mMinimizedPreview = requireViewById(R.id.minimized_preview);
+ mEditChip = requireViewById(R.id.edit_chip);
+ mShareChip = requireViewById(R.id.share_chip);
+ mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
+ mDismissButton = requireViewById(R.id.dismiss_button);
mEditChip.setAlpha(1);
mShareChip.setAlpha(1);
@@ -163,6 +164,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
+ mMinimizedPreview.setOnClickListener(v -> clipboardCallbacks.onMinimizedViewTapped());
}
void setEditAccessibilityAction(boolean editable) {
@@ -177,12 +179,28 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
}
}
+ void setMinimized(boolean minimized) {
+ if (minimized) {
+ mMinimizedPreview.setVisibility(View.VISIBLE);
+ mClipboardPreview.setVisibility(View.GONE);
+ mPreviewBorder.setVisibility(View.GONE);
+ mActionContainer.setVisibility(View.GONE);
+ mActionContainerBackground.setVisibility(View.GONE);
+ } else {
+ mMinimizedPreview.setVisibility(View.GONE);
+ mClipboardPreview.setVisibility(View.VISIBLE);
+ mPreviewBorder.setVisibility(View.VISIBLE);
+ mActionContainer.setVisibility(View.VISIBLE);
+ }
+ }
+
void setInsets(WindowInsets insets, int orientation) {
FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
if (p == null) {
return;
}
Rect margins = computeMargins(insets, orientation);
+
p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
setLayoutParams(p);
requestLayout();
@@ -204,6 +222,12 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
touchRegion.op(tmpRect, Region.Op.UNION);
+ mMinimizedPreview.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+
mDismissButton.getBoundsOnScreen(tmpRect);
touchRegion.op(tmpRect, Region.Op.UNION);
@@ -298,6 +322,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
scaleAnim.setDuration(333);
scaleAnim.addUpdateListener(animation -> {
float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mMinimizedPreview.setScaleX(previewScale);
+ mMinimizedPreview.setScaleY(previewScale);
mClipboardPreview.setScaleX(previewScale);
mClipboardPreview.setScaleY(previewScale);
mPreviewBorder.setScaleX(previewScale);
@@ -319,12 +345,14 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
alphaAnim.setDuration(283);
alphaAnim.addUpdateListener(animation -> {
float alpha = animation.getAnimatedFraction();
+ mMinimizedPreview.setAlpha(alpha);
mClipboardPreview.setAlpha(alpha);
mPreviewBorder.setAlpha(alpha);
mDismissButton.setAlpha(alpha);
mActionContainer.setAlpha(alpha);
});
+ mMinimizedPreview.setAlpha(0);
mActionContainer.setAlpha(0);
mPreviewBorder.setAlpha(0);
mClipboardPreview.setAlpha(0);
@@ -356,6 +384,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
scaleAnim.setDuration(250);
scaleAnim.addUpdateListener(animation -> {
float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mMinimizedPreview.setScaleX(previewScale);
+ mMinimizedPreview.setScaleY(previewScale);
mClipboardPreview.setScaleX(previewScale);
mClipboardPreview.setScaleY(previewScale);
mPreviewBorder.setScaleX(previewScale);
@@ -377,6 +407,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
alphaAnim.setDuration(166);
alphaAnim.addUpdateListener(animation -> {
float alpha = 1 - animation.getAnimatedFraction();
+ mMinimizedPreview.setAlpha(alpha);
mClipboardPreview.setAlpha(alpha);
mPreviewBorder.setAlpha(alpha);
mDismissButton.setAlpha(alpha);
@@ -399,6 +430,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
mTextPreview.setVisibility(View.GONE);
mImagePreview.setVisibility(View.GONE);
mHiddenPreview.setVisibility(View.GONE);
+ mMinimizedPreview.setVisibility(View.GONE);
v.setVisibility(View.VISIBLE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 661b202eb7c2..8bacf4e7cfaf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -549,6 +549,8 @@ object Flags {
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
+ // TODO(b/267162944): Tracking bug
+ @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = unreleasedFlag(1702, "clipboard_data_model")
// 1800 - shade container
@JvmField
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 71c335e6b173..7177919909f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.clipboardoverlay;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
+
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
import static org.junit.Assert.assertEquals;
@@ -38,6 +40,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
import org.junit.Before;
import org.junit.Test;
@@ -60,6 +63,7 @@ public class ClipboardListenerTest extends SysuiTestCase {
private ClipboardOverlayController mOverlayController;
@Mock
private ClipboardToast mClipboardToast;
+ private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
private UiEventLogger mUiEventLogger;
@@ -93,8 +97,10 @@ public class ClipboardListenerTest extends SysuiTestCase {
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true);
+
mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
- mClipboardToast, mClipboardManager, mUiEventLogger);
+ mClipboardToast, mClipboardManager, mFeatureFlags, mUiEventLogger);
}
@@ -187,4 +193,34 @@ public class ClipboardListenerTest extends SysuiTestCase {
verify(mClipboardToast, times(1)).showCopiedToast();
verifyZeroInteractions(mOverlayControllerProvider);
}
+
+ @Test
+ public void test_minimizedLayoutFlagOff_usesLegacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
+
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mOverlayControllerProvider).get();
+
+ verify(mOverlayController).setClipDataLegacy(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
+
+ assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+ assertEquals(mSampleSource, mStringCaptor.getValue());
+ }
+
+ @Test
+ public void test_minimizedLayoutFlagOn_usesNew() {
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mOverlayControllerProvider).get();
+
+ verify(mOverlayController).setClipData(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
+
+ assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+ assertEquals(mSampleSource, mStringCaptor.getValue());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
new file mode 100644
index 000000000000..faef35e7bfcb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.ContentResolver
+import android.content.Context
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.PersistableBundle
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.whenever
+import java.io.IOException
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ClipboardModelTest : SysuiTestCase() {
+ @Mock private lateinit var mClipboardUtils: ClipboardOverlayUtils
+ @Mock private lateinit var mMockContext: Context
+ @Mock private lateinit var mMockContentResolver: ContentResolver
+ private lateinit var mSampleClipData: ClipData
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mSampleClipData = ClipData("Test", arrayOf("text/plain"), ClipData.Item("Test Item"))
+ }
+
+ @Test
+ fun test_nullClipData() {
+ val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, null, "test source")
+ assertNull(model.clipData)
+ assertEquals("test source", model.source)
+ assertEquals(ClipboardModel.Type.OTHER, model.type)
+ assertNull(model.item)
+ assertFalse(model.isSensitive)
+ assertFalse(model.isRemote)
+ assertNull(model.loadThumbnail(mContext))
+ }
+
+ @Test
+ fun test_textClipData() {
+ val source = "test source"
+ val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, mSampleClipData, source)
+ assertEquals(mSampleClipData, model.clipData)
+ assertEquals(source, model.source)
+ assertEquals(ClipboardModel.Type.TEXT, model.type)
+ assertEquals(mSampleClipData.getItemAt(0), model.item)
+ assertFalse(model.isSensitive)
+ assertFalse(model.isRemote)
+ assertNull(model.loadThumbnail(mContext))
+ }
+
+ @Test
+ fun test_sensitiveExtra() {
+ val description = mSampleClipData.description
+ val b = PersistableBundle()
+ b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
+ description.extras = b
+ val data = ClipData(description, mSampleClipData.getItemAt(0))
+ val (_, _, _, _, sensitive) =
+ ClipboardModel.fromClipData(mContext, mClipboardUtils, data, "")
+ assertTrue(sensitive)
+ }
+
+ @Test
+ fun test_remoteExtra() {
+ whenever(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true)
+ val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, mSampleClipData, "")
+ assertTrue(model.isRemote)
+ }
+
+ @Test
+ @Throws(IOException::class)
+ fun test_imageClipData() {
+ val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
+ whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+ whenever(mMockContext.resources).thenReturn(mContext.resources)
+ whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenReturn(testBitmap)
+ whenever(mMockContentResolver.getType(any())).thenReturn("image")
+ val imageClipData =
+ ClipData("Test", arrayOf("text/plain"), ClipData.Item(Uri.parse("test")))
+ val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+ assertEquals(ClipboardModel.Type.IMAGE, model.type)
+ assertEquals(testBitmap, model.loadThumbnail(mMockContext))
+ }
+
+ @Test
+ @Throws(IOException::class)
+ fun test_imageClipData_loadFailure() {
+ whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+ whenever(mMockContext.resources).thenReturn(mContext.resources)
+ whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenThrow(IOException())
+ whenever(mMockContentResolver.getType(any())).thenReturn("image")
+ val imageClipData =
+ ClipData("Test", arrayOf("text/plain"), ClipData.Item(Uri.parse("test")))
+ val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+ assertEquals(ClipboardModel.Type.IMAGE, model.type)
+ assertNull(model.loadThumbnail(mMockContext))
+ }
+}
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 ca5b7af5695a..0ac26676a9c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -16,13 +16,17 @@
package com.android.systemui.clipboardoverlay;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -35,8 +39,11 @@ import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.PersistableBundle;
+import android.view.WindowInsets;
import android.view.textclassifier.TextLinks;
import androidx.test.filters.SmallTest;
@@ -102,11 +109,14 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+ when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
+ getImeInsets(new Rect(0, 0, 0, 0)));
mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
new ClipData.Item("Test Item"));
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true); // turned off for legacy tests
mOverlayController = new ClipboardOverlayController(
mContext,
@@ -118,8 +128,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
mFeatureFlags,
mClipboardUtils,
mExecutor,
- mUiEventLogger,
- mDisplayTracker);
+ mUiEventLogger);
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
}
@@ -130,6 +139,159 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
}
@Test
+ public void test_setClipData_nullData_legacy() {
+ ClipData clipData = null;
+ mOverlayController.setClipDataLegacy(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_invalidImageData_legacy() {
+ ClipData clipData = new ClipData("", new String[]{"image/png"},
+ new ClipData.Item(Uri.parse("")));
+
+ mOverlayController.setClipDataLegacy(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_textData_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_sensitiveTextData_legacy() {
+ ClipDescription description = mSampleClipData.getDescription();
+ PersistableBundle b = new PersistableBundle();
+ b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+ description.setExtras(b);
+ ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+ mOverlayController.setClipDataLegacy(data, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_repeatedCalls_legacy() {
+ when(mAnimator.isRunning()).thenReturn(true);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onShareTapped_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ mCallbacks.onShareButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onDismissTapped_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_multipleDismissals_dismissesOnce_legacy() {
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
+ verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ }
+
+ @Test
+ public void test_remoteCopy_withFlagOn_legacy() {
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mTimeoutHandler, never()).resetTimeout();
+ }
+
+ @Test
+ public void test_remoteCopy_withFlagOff_legacy() {
+ when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mTimeoutHandler).resetTimeout();
+ }
+
+ @Test
+ public void test_nonRemoteCopy_legacy() {
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mTimeoutHandler).resetTimeout();
+ }
+
+ @Test
+ public void test_logsUseLastClipSource_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "first.package");
+ mCallbacks.onDismissButtonTapped();
+ mOverlayController.setClipDataLegacy(mSampleClipData, "second.package");
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+ verifyNoMoreInteractions(mUiEventLogger);
+ }
+
+ @Test
+ public void test_logOnClipboardActionsShown_legacy() {
+ ClipData.Item item = mSampleClipData.getItemAt(0);
+ item.setTextLinks(Mockito.mock(TextLinks.class));
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
+ .thenReturn(true);
+ when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString()))
+ .thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
+ when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }
+ });
+
+ mOverlayController.setClipDataLegacy(
+ new ClipData(mSampleClipData.getDescription(), item), "actionShownSource");
+ mExecutor.runAllReady();
+
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
+ verifyNoMoreInteractions(mUiEventLogger);
+ }
+
+ // start of refactored setClipData tests
+ @Test
public void test_setClipData_nullData() {
ClipData clipData = null;
mOverlayController.setClipData(clipData, "");
@@ -280,4 +442,43 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
verifyNoMoreInteractions(mUiEventLogger);
}
+
+ @Test
+ public void test_noInsets_showsExpanded() {
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, never()).setMinimized(true);
+ verify(mClipboardOverlayView).setMinimized(false);
+ verify(mClipboardOverlayView).showTextPreview("Test Item", false);
+ }
+
+ @Test
+ public void test_insets_showsMinimized() {
+ when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
+ getImeInsets(new Rect(0, 0, 0, 1)));
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView).setMinimized(true);
+ verify(mClipboardOverlayView, never()).setMinimized(false);
+ verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean());
+
+ mCallbacks.onMinimizedViewTapped();
+
+ verify(mClipboardOverlayView).setMinimized(false);
+ verify(mClipboardOverlayView).showTextPreview("Test Item", false);
+ }
+
+ @Test
+ public void test_insetsChanged_minimizes() {
+ mOverlayController.setClipData(mSampleClipData, "");
+ verify(mClipboardOverlayView, never()).setMinimized(true);
+
+ WindowInsets insetsWithKeyboard = getImeInsets(new Rect(0, 0, 0, 1));
+ mOverlayController.onInsetsChanged(insetsWithKeyboard, ORIENTATION_PORTRAIT);
+ verify(mClipboardOverlayView).setMinimized(true);
+ }
+
+ private static WindowInsets getImeInsets(Rect r) {
+ return new WindowInsets.Builder().setInsets(WindowInsets.Type.ime(), Insets.of(r)).build();
+ }
}