diff options
| author | 2023-02-01 19:34:18 +0000 | |
|---|---|---|
| committer | 2023-02-01 19:34:18 +0000 | |
| commit | 16bc6a5ebd1633f15806827e82f6ed1ad5d47f8c (patch) | |
| tree | 6fee9883a256af788cdc3e89351148c656eb2aae | |
| parent | 7b0429e3d793e4a7f2cd8f5f204f9a3c3962a3ce (diff) | |
| parent | 1a70395d523886b90a5d70382895dd54e5c8dbb9 (diff) | |
Merge "Implement minimized clipboard layout" into tm-qpr-dev
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(); + } } |