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_width="@dimen/clipboard_preview_size"
|
||||||
android:layout_height="@dimen/clipboard_preview_size"/>
|
android:layout_height="@dimen/clipboard_preview_size"/>
|
||||||
</FrameLayout>
|
</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
|
<FrameLayout
|
||||||
android:id="@+id/dismiss_button"
|
android:id="@+id/dismiss_button"
|
||||||
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
|
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
|
||||||
@@ -132,10 +171,10 @@
|
|||||||
android:elevation="10dp"
|
android:elevation="10dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:alpha="0"
|
android:alpha="0"
|
||||||
app:layout_constraintStart_toEndOf="@id/clipboard_preview"
|
app:layout_constraintStart_toEndOf="@id/clipboard_content_end"
|
||||||
app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
|
app:layout_constraintEnd_toEndOf="@id/clipboard_content_end"
|
||||||
app:layout_constraintTop_toTopOf="@id/clipboard_preview"
|
app:layout_constraintTop_toTopOf="@id/clipboard_content_top"
|
||||||
app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
|
app:layout_constraintBottom_toTopOf="@id/clipboard_content_top"
|
||||||
android:contentDescription="@string/clipboard_dismiss_description">
|
android:contentDescription="@string/clipboard_dismiss_description">
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/dismiss_image"
|
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_ENTERED;
|
||||||
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
|
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.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;
|
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.internal.logging.UiEventLogger;
|
||||||
import com.android.systemui.CoreStartable;
|
import com.android.systemui.CoreStartable;
|
||||||
import com.android.systemui.dagger.SysUISingleton;
|
import com.android.systemui.dagger.SysUISingleton;
|
||||||
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
@@ -57,6 +59,7 @@ public class ClipboardListener implements
|
|||||||
private final Provider<ClipboardOverlayController> mOverlayProvider;
|
private final Provider<ClipboardOverlayController> mOverlayProvider;
|
||||||
private final ClipboardToast mClipboardToast;
|
private final ClipboardToast mClipboardToast;
|
||||||
private final ClipboardManager mClipboardManager;
|
private final ClipboardManager mClipboardManager;
|
||||||
|
private final FeatureFlags mFeatureFlags;
|
||||||
private final UiEventLogger mUiEventLogger;
|
private final UiEventLogger mUiEventLogger;
|
||||||
private ClipboardOverlay mClipboardOverlay;
|
private ClipboardOverlay mClipboardOverlay;
|
||||||
|
|
||||||
@@ -65,11 +68,13 @@ public class ClipboardListener implements
|
|||||||
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
|
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
|
||||||
ClipboardToast clipboardToast,
|
ClipboardToast clipboardToast,
|
||||||
ClipboardManager clipboardManager,
|
ClipboardManager clipboardManager,
|
||||||
|
FeatureFlags featureFlags,
|
||||||
UiEventLogger uiEventLogger) {
|
UiEventLogger uiEventLogger) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mOverlayProvider = clipboardOverlayControllerProvider;
|
mOverlayProvider = clipboardOverlayControllerProvider;
|
||||||
mClipboardToast = clipboardToast;
|
mClipboardToast = clipboardToast;
|
||||||
mClipboardManager = clipboardManager;
|
mClipboardManager = clipboardManager;
|
||||||
|
mFeatureFlags = featureFlags;
|
||||||
mUiEventLogger = uiEventLogger;
|
mUiEventLogger = uiEventLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +112,11 @@ public class ClipboardListener implements
|
|||||||
} else {
|
} else {
|
||||||
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
|
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
|
||||||
}
|
}
|
||||||
|
if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
|
||||||
mClipboardOverlay.setClipData(clipData, clipSource);
|
mClipboardOverlay.setClipData(clipData, clipSource);
|
||||||
|
} else {
|
||||||
|
mClipboardOverlay.setClipDataLegacy(clipData, clipSource);
|
||||||
|
}
|
||||||
mClipboardOverlay.setOnSessionCompleteListener(() -> {
|
mClipboardOverlay.setOnSessionCompleteListener(() -> {
|
||||||
// Session is complete, free memory until it's needed again.
|
// Session is complete, free memory until it's needed again.
|
||||||
mClipboardOverlay = null;
|
mClipboardOverlay = null;
|
||||||
@@ -150,6 +159,8 @@ public class ClipboardListener implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ClipboardOverlay {
|
interface ClipboardOverlay {
|
||||||
|
void setClipDataLegacy(ClipData clipData, String clipSource);
|
||||||
|
|
||||||
void setClipData(ClipData clipData, String clipSource);
|
void setClipData(ClipData clipData, String clipSource);
|
||||||
|
|
||||||
void setOnSessionCompleteListener(Runnable runnable);
|
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;
|
package com.android.systemui.clipboardoverlay;
|
||||||
|
|
||||||
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
|
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_ACTIONS;
|
||||||
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
|
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_SWIPE_DISMISSED;
|
||||||
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
|
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.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 com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.app.RemoteAction;
|
import android.app.RemoteAction;
|
||||||
@@ -47,7 +45,6 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.hardware.display.DisplayManager;
|
|
||||||
import android.hardware.input.InputManager;
|
import android.hardware.input.InputManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@@ -55,14 +52,15 @@ import android.provider.DeviceConfig;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Size;
|
import android.util.Size;
|
||||||
import android.view.Display;
|
|
||||||
import android.view.InputEvent;
|
import android.view.InputEvent;
|
||||||
import android.view.InputEventReceiver;
|
import android.view.InputEventReceiver;
|
||||||
import android.view.InputMonitor;
|
import android.view.InputMonitor;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.logging.UiEventLogger;
|
import com.android.internal.logging.UiEventLogger;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
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.dagger.qualifiers.Background;
|
||||||
import com.android.systemui.flags.FeatureFlags;
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
import com.android.systemui.screenshot.TimeoutHandler;
|
import com.android.systemui.screenshot.TimeoutHandler;
|
||||||
import com.android.systemui.settings.DisplayTracker;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -95,8 +92,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
|||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final ClipboardLogger mClipboardLogger;
|
private final ClipboardLogger mClipboardLogger;
|
||||||
private final BroadcastDispatcher mBroadcastDispatcher;
|
private final BroadcastDispatcher mBroadcastDispatcher;
|
||||||
private final DisplayManager mDisplayManager;
|
|
||||||
private final DisplayTracker mDisplayTracker;
|
|
||||||
private final ClipboardOverlayWindow mWindow;
|
private final ClipboardOverlayWindow mWindow;
|
||||||
private final TimeoutHandler mTimeoutHandler;
|
private final TimeoutHandler mTimeoutHandler;
|
||||||
private final ClipboardOverlayUtils mClipboardUtils;
|
private final ClipboardOverlayUtils mClipboardUtils;
|
||||||
@@ -122,6 +117,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
|||||||
|
|
||||||
private Runnable mOnUiUpdate;
|
private Runnable mOnUiUpdate;
|
||||||
|
|
||||||
|
private boolean mIsMinimized;
|
||||||
|
private ClipboardModel mClipboardModel;
|
||||||
|
|
||||||
private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
|
private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
|
||||||
new ClipboardOverlayView.ClipboardOverlayCallbacks() {
|
new ClipboardOverlayView.ClipboardOverlayCallbacks() {
|
||||||
@Override
|
@Override
|
||||||
@@ -175,6 +173,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
|||||||
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
|
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
|
||||||
animateOut();
|
animateOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMinimizedViewTapped() {
|
||||||
|
if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
|
||||||
|
animateFromMinimized();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -187,33 +192,28 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
|||||||
FeatureFlags featureFlags,
|
FeatureFlags featureFlags,
|
||||||
ClipboardOverlayUtils clipboardUtils,
|
ClipboardOverlayUtils clipboardUtils,
|
||||||
@Background Executor bgExecutor,
|
@Background Executor bgExecutor,
|
||||||
UiEventLogger uiEventLogger,
|
UiEventLogger uiEventLogger) {
|
||||||
DisplayTracker displayTracker) {
|
mContext = context;
|
||||||
mBroadcastDispatcher = broadcastDispatcher;
|
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);
|
mClipboardLogger = new ClipboardLogger(uiEventLogger);
|
||||||
|
|
||||||
mView = clipboardOverlayView;
|
mView = clipboardOverlayView;
|
||||||
mWindow = clipboardOverlayWindow;
|
mWindow = clipboardOverlayWindow;
|
||||||
mWindow.init(mView::setInsets, () -> {
|
mWindow.init(this::onInsetsChanged, () -> {
|
||||||
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
|
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
|
||||||
hideImmediate();
|
hideImmediate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mFeatureFlags = featureFlags;
|
||||||
mTimeoutHandler = timeoutHandler;
|
mTimeoutHandler = timeoutHandler;
|
||||||
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
|
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
|
||||||
|
|
||||||
mFeatureFlags = featureFlags;
|
|
||||||
mClipboardUtils = clipboardUtils;
|
mClipboardUtils = clipboardUtils;
|
||||||
mBgExecutor = bgExecutor;
|
mBgExecutor = bgExecutor;
|
||||||
|
|
||||||
mView.setCallbacks(mClipboardCallbacks);
|
mView.setCallbacks(mClipboardCallbacks);
|
||||||
|
|
||||||
|
|
||||||
mWindow.withWindowAttached(() -> {
|
mWindow.withWindowAttached(() -> {
|
||||||
mWindow.setContentView(mView);
|
mWindow.setContentView(mView);
|
||||||
mView.setInsets(mWindow.getWindowInsets(),
|
mView.setInsets(mWindow.getWindowInsets(),
|
||||||
@@ -258,8 +258,135 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
|||||||
broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
|
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
|
@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()) {
|
if (mExitAnimator != null && mExitAnimator.isRunning()) {
|
||||||
mExitAnimator.cancel();
|
mExitAnimator.cancel();
|
||||||
}
|
}
|
||||||
@@ -516,10 +643,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
|
|||||||
mClipboardLogger.reset();
|
mClipboardLogger.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Display getDefaultDisplay() {
|
|
||||||
return mDisplayManager.getDisplay(mDisplayTracker.getDefaultDisplayId());
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ClipboardLogger {
|
static class ClipboardLogger {
|
||||||
private final UiEventLogger mUiEventLogger;
|
private final UiEventLogger mUiEventLogger;
|
||||||
private String mClipSource;
|
private String mClipSource;
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ package com.android.systemui.clipboardoverlay;
|
|||||||
|
|
||||||
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
|
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
@@ -77,6 +75,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
void onShareButtonTapped();
|
void onShareButtonTapped();
|
||||||
|
|
||||||
void onPreviewTapped();
|
void onPreviewTapped();
|
||||||
|
|
||||||
|
void onMinimizedViewTapped();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String TAG = "ClipboardView";
|
private static final String TAG = "ClipboardView";
|
||||||
@@ -92,6 +92,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
private ImageView mImagePreview;
|
private ImageView mImagePreview;
|
||||||
private TextView mTextPreview;
|
private TextView mTextPreview;
|
||||||
private TextView mHiddenPreview;
|
private TextView mHiddenPreview;
|
||||||
|
private LinearLayout mMinimizedPreview;
|
||||||
private View mPreviewBorder;
|
private View mPreviewBorder;
|
||||||
private OverlayActionChip mEditChip;
|
private OverlayActionChip mEditChip;
|
||||||
private OverlayActionChip mShareChip;
|
private OverlayActionChip mShareChip;
|
||||||
@@ -117,18 +118,18 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
mActionContainerBackground =
|
mActionContainerBackground = requireViewById(R.id.actions_container_background);
|
||||||
requireNonNull(findViewById(R.id.actions_container_background));
|
mActionContainer = requireViewById(R.id.actions);
|
||||||
mActionContainer = requireNonNull(findViewById(R.id.actions));
|
mClipboardPreview = requireViewById(R.id.clipboard_preview);
|
||||||
mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview));
|
mPreviewBorder = requireViewById(R.id.preview_border);
|
||||||
mImagePreview = requireNonNull(findViewById(R.id.image_preview));
|
mImagePreview = requireViewById(R.id.image_preview);
|
||||||
mTextPreview = requireNonNull(findViewById(R.id.text_preview));
|
mTextPreview = requireViewById(R.id.text_preview);
|
||||||
mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview));
|
mHiddenPreview = requireViewById(R.id.hidden_preview);
|
||||||
mPreviewBorder = requireNonNull(findViewById(R.id.preview_border));
|
mMinimizedPreview = requireViewById(R.id.minimized_preview);
|
||||||
mEditChip = requireNonNull(findViewById(R.id.edit_chip));
|
mEditChip = requireViewById(R.id.edit_chip);
|
||||||
mShareChip = requireNonNull(findViewById(R.id.share_chip));
|
mShareChip = requireViewById(R.id.share_chip);
|
||||||
mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip));
|
mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
|
||||||
mDismissButton = requireNonNull(findViewById(R.id.dismiss_button));
|
mDismissButton = requireViewById(R.id.dismiss_button);
|
||||||
|
|
||||||
mEditChip.setAlpha(1);
|
mEditChip.setAlpha(1);
|
||||||
mShareChip.setAlpha(1);
|
mShareChip.setAlpha(1);
|
||||||
@@ -163,6 +164,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
|
mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
|
||||||
mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
|
mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
|
||||||
mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
|
mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
|
||||||
|
mMinimizedPreview.setOnClickListener(v -> clipboardCallbacks.onMinimizedViewTapped());
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEditAccessibilityAction(boolean editable) {
|
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) {
|
void setInsets(WindowInsets insets, int orientation) {
|
||||||
FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
|
FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
|
||||||
if (p == null) {
|
if (p == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Rect margins = computeMargins(insets, orientation);
|
Rect margins = computeMargins(insets, orientation);
|
||||||
|
|
||||||
p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
|
p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
|
||||||
setLayoutParams(p);
|
setLayoutParams(p);
|
||||||
requestLayout();
|
requestLayout();
|
||||||
@@ -204,6 +222,12 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
|
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
|
||||||
touchRegion.op(tmpRect, Region.Op.UNION);
|
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);
|
mDismissButton.getBoundsOnScreen(tmpRect);
|
||||||
touchRegion.op(tmpRect, Region.Op.UNION);
|
touchRegion.op(tmpRect, Region.Op.UNION);
|
||||||
|
|
||||||
@@ -298,6 +322,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
scaleAnim.setDuration(333);
|
scaleAnim.setDuration(333);
|
||||||
scaleAnim.addUpdateListener(animation -> {
|
scaleAnim.addUpdateListener(animation -> {
|
||||||
float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
|
float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
|
||||||
|
mMinimizedPreview.setScaleX(previewScale);
|
||||||
|
mMinimizedPreview.setScaleY(previewScale);
|
||||||
mClipboardPreview.setScaleX(previewScale);
|
mClipboardPreview.setScaleX(previewScale);
|
||||||
mClipboardPreview.setScaleY(previewScale);
|
mClipboardPreview.setScaleY(previewScale);
|
||||||
mPreviewBorder.setScaleX(previewScale);
|
mPreviewBorder.setScaleX(previewScale);
|
||||||
@@ -319,12 +345,14 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
alphaAnim.setDuration(283);
|
alphaAnim.setDuration(283);
|
||||||
alphaAnim.addUpdateListener(animation -> {
|
alphaAnim.addUpdateListener(animation -> {
|
||||||
float alpha = animation.getAnimatedFraction();
|
float alpha = animation.getAnimatedFraction();
|
||||||
|
mMinimizedPreview.setAlpha(alpha);
|
||||||
mClipboardPreview.setAlpha(alpha);
|
mClipboardPreview.setAlpha(alpha);
|
||||||
mPreviewBorder.setAlpha(alpha);
|
mPreviewBorder.setAlpha(alpha);
|
||||||
mDismissButton.setAlpha(alpha);
|
mDismissButton.setAlpha(alpha);
|
||||||
mActionContainer.setAlpha(alpha);
|
mActionContainer.setAlpha(alpha);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mMinimizedPreview.setAlpha(0);
|
||||||
mActionContainer.setAlpha(0);
|
mActionContainer.setAlpha(0);
|
||||||
mPreviewBorder.setAlpha(0);
|
mPreviewBorder.setAlpha(0);
|
||||||
mClipboardPreview.setAlpha(0);
|
mClipboardPreview.setAlpha(0);
|
||||||
@@ -356,6 +384,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
scaleAnim.setDuration(250);
|
scaleAnim.setDuration(250);
|
||||||
scaleAnim.addUpdateListener(animation -> {
|
scaleAnim.addUpdateListener(animation -> {
|
||||||
float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
|
float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
|
||||||
|
mMinimizedPreview.setScaleX(previewScale);
|
||||||
|
mMinimizedPreview.setScaleY(previewScale);
|
||||||
mClipboardPreview.setScaleX(previewScale);
|
mClipboardPreview.setScaleX(previewScale);
|
||||||
mClipboardPreview.setScaleY(previewScale);
|
mClipboardPreview.setScaleY(previewScale);
|
||||||
mPreviewBorder.setScaleX(previewScale);
|
mPreviewBorder.setScaleX(previewScale);
|
||||||
@@ -377,6 +407,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
alphaAnim.setDuration(166);
|
alphaAnim.setDuration(166);
|
||||||
alphaAnim.addUpdateListener(animation -> {
|
alphaAnim.addUpdateListener(animation -> {
|
||||||
float alpha = 1 - animation.getAnimatedFraction();
|
float alpha = 1 - animation.getAnimatedFraction();
|
||||||
|
mMinimizedPreview.setAlpha(alpha);
|
||||||
mClipboardPreview.setAlpha(alpha);
|
mClipboardPreview.setAlpha(alpha);
|
||||||
mPreviewBorder.setAlpha(alpha);
|
mPreviewBorder.setAlpha(alpha);
|
||||||
mDismissButton.setAlpha(alpha);
|
mDismissButton.setAlpha(alpha);
|
||||||
@@ -399,6 +430,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
|
|||||||
mTextPreview.setVisibility(View.GONE);
|
mTextPreview.setVisibility(View.GONE);
|
||||||
mImagePreview.setVisibility(View.GONE);
|
mImagePreview.setVisibility(View.GONE);
|
||||||
mHiddenPreview.setVisibility(View.GONE);
|
mHiddenPreview.setVisibility(View.GONE);
|
||||||
|
mMinimizedPreview.setVisibility(View.GONE);
|
||||||
v.setVisibility(View.VISIBLE);
|
v.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -549,6 +549,8 @@ object Flags {
|
|||||||
|
|
||||||
// 1700 - clipboard
|
// 1700 - clipboard
|
||||||
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
|
@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
|
// 1800 - shade container
|
||||||
@JvmField
|
@JvmField
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.systemui.clipboardoverlay;
|
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 com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -38,6 +40,7 @@ import androidx.test.runner.AndroidJUnit4;
|
|||||||
|
|
||||||
import com.android.internal.logging.UiEventLogger;
|
import com.android.internal.logging.UiEventLogger;
|
||||||
import com.android.systemui.SysuiTestCase;
|
import com.android.systemui.SysuiTestCase;
|
||||||
|
import com.android.systemui.flags.FakeFeatureFlags;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -60,6 +63,7 @@ public class ClipboardListenerTest extends SysuiTestCase {
|
|||||||
private ClipboardOverlayController mOverlayController;
|
private ClipboardOverlayController mOverlayController;
|
||||||
@Mock
|
@Mock
|
||||||
private ClipboardToast mClipboardToast;
|
private ClipboardToast mClipboardToast;
|
||||||
|
private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
|
||||||
@Mock
|
@Mock
|
||||||
private UiEventLogger mUiEventLogger;
|
private UiEventLogger mUiEventLogger;
|
||||||
|
|
||||||
@@ -93,8 +97,10 @@ public class ClipboardListenerTest extends SysuiTestCase {
|
|||||||
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
|
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
|
||||||
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
|
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
|
||||||
|
|
||||||
|
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true);
|
||||||
|
|
||||||
mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
|
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();
|
verify(mClipboardToast, times(1)).showCopiedToast();
|
||||||
verifyZeroInteractions(mOverlayControllerProvider);
|
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;
|
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_ACTION_SHOWN;
|
||||||
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
|
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_SHARE_TAPPED;
|
||||||
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
|
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 com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
@@ -35,8 +39,11 @@ import android.app.RemoteAction;
|
|||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipDescription;
|
import android.content.ClipDescription;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Insets;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.PersistableBundle;
|
import android.os.PersistableBundle;
|
||||||
|
import android.view.WindowInsets;
|
||||||
import android.view.textclassifier.TextLinks;
|
import android.view.textclassifier.TextLinks;
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
@@ -102,11 +109,14 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
|||||||
|
|
||||||
when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
|
when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
|
||||||
when(mClipboardOverlayView.getExitAnimation()).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"},
|
mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
|
||||||
new ClipData.Item("Test Item"));
|
new ClipData.Item("Test Item"));
|
||||||
|
|
||||||
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
|
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
|
||||||
|
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true); // turned off for legacy tests
|
||||||
|
|
||||||
mOverlayController = new ClipboardOverlayController(
|
mOverlayController = new ClipboardOverlayController(
|
||||||
mContext,
|
mContext,
|
||||||
@@ -118,8 +128,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
|||||||
mFeatureFlags,
|
mFeatureFlags,
|
||||||
mClipboardUtils,
|
mClipboardUtils,
|
||||||
mExecutor,
|
mExecutor,
|
||||||
mUiEventLogger,
|
mUiEventLogger);
|
||||||
mDisplayTracker);
|
|
||||||
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
|
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
|
||||||
mCallbacks = mOverlayCallbacksCaptor.getValue();
|
mCallbacks = mOverlayCallbacksCaptor.getValue();
|
||||||
}
|
}
|
||||||
@@ -129,6 +138,159 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
|||||||
mOverlayController.hideImmediate();
|
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
|
@Test
|
||||||
public void test_setClipData_nullData() {
|
public void test_setClipData_nullData() {
|
||||||
ClipData clipData = null;
|
ClipData clipData = null;
|
||||||
@@ -280,4 +442,43 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
|
|||||||
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
|
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
|
||||||
verifyNoMoreInteractions(mUiEventLogger);
|
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