Merge "Implement minimized clipboard layout" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
16bc6a5ebd
@@ -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>
|
||||
25
packages/SystemUI/res/drawable/ic_content_paste.xml
Normal file
25
packages/SystemUI/res/drawable/ic_content_paste.xml
Normal file
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -129,6 +138,159 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
||||
mOverlayController.hideImmediate();
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user