Merge changes I4c1e118c,Id66f2971 into tm-qpr-dev

* changes:
  Make all copied URIs shareable
  Fix legacy clipboard controller tests
This commit is contained in:
Matt Casey
2023-02-15 00:57:04 +00:00
committed by Android (Google) Code Review
7 changed files with 136 additions and 66 deletions

View File

@@ -97,8 +97,9 @@ public class ClipboardListener implements
return;
}
if (!isUserSetupComplete()) {
// just show a toast, user should not access intents from this state
if (!isUserSetupComplete() // user should not access intents from this state
|| clipData == null // shouldn't happen, but just in case
|| clipData.getItemCount() == 0) {
if (shouldShowToast(clipData)) {
mUiEventLogger.log(CLIPBOARD_TOAST_SHOWN, 0, clipSource);
mClipboardToast.showCopiedToast();

View File

@@ -19,19 +19,23 @@ import android.content.ClipData
import android.content.ClipDescription.EXTRA_IS_SENSITIVE
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.text.TextUtils
import android.util.Log
import android.util.Size
import android.view.textclassifier.TextLinks
import com.android.systemui.R
import java.io.IOException
data class ClipboardModel(
val clipData: ClipData?,
val clipData: ClipData,
val source: String,
val type: Type = Type.OTHER,
val item: ClipData.Item? = null,
val isSensitive: Boolean = false,
val isRemote: Boolean = false,
val type: Type,
val text: CharSequence?,
val textLinks: TextLinks?,
val uri: Uri?,
val isSensitive: Boolean,
val isRemote: Boolean,
) {
private var _bitmap: Bitmap? = null
@@ -41,17 +45,16 @@ data class ClipboardModel(
}
return source == other.source &&
type == other.type &&
item?.text == other.item?.text &&
item?.uri == other.item?.uri &&
text == other.text &&
uri == other.uri &&
isSensitive == other.isSensitive
}
fun loadThumbnail(context: Context): Bitmap? {
if (_bitmap == null && type == Type.IMAGE && item?.uri != null) {
if (_bitmap == null && type == Type.IMAGE && uri != null) {
try {
val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
_bitmap =
context.contentResolver.loadThumbnail(item.uri, Size(size, size * 4), null)
_bitmap = context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
} catch (e: IOException) {
Log.e(TAG, "Thumbnail loading failed!", e)
}
@@ -66,27 +69,34 @@ data class ClipboardModel(
fun fromClipData(
context: Context,
utils: ClipboardOverlayUtils,
clipData: ClipData?,
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)
return ClipboardModel(
clipData,
source,
type,
item.text,
item.textLinks,
item.uri,
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 if (item.uri != null) {
if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
Type.IMAGE
} else {
Type.URI
}
} else {
Type.OTHER
}
@@ -96,6 +106,7 @@ data class ClipboardModel(
enum class Type {
TEXT,
IMAGE,
URI,
OTHER
}
}

View File

@@ -309,14 +309,14 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
if ((mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && model.isRemote())
|| DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
if (model.getItem().getTextLinks() != null) {
if (model.getTextLinks() != null) {
classifyText(model);
}
}
if (model.isSensitive()) {
mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
} else {
mView.showTextPreview(model.getItem().getText(), false);
mView.showTextPreview(model.getText(), false);
}
mView.setEditAccessibilityAction(true);
mOnPreviewTapped = this::editText;
@@ -326,12 +326,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
mView.showImagePreview(
model.isSensitive() ? null : model.loadThumbnail(mContext));
mView.setEditAccessibilityAction(true);
mOnPreviewTapped = () -> editImage(model.getItem().getUri());
mOnPreviewTapped = () -> editImage(model.getUri());
} else {
// image loading failed
mView.showDefaultTextPreview();
}
break;
case URI:
case OTHER:
mView.showDefaultTextPreview();
break;
@@ -371,8 +372,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
private void classifyText(ClipboardModel model) {
mBgExecutor.execute(() -> {
Optional<RemoteAction> remoteAction =
mClipboardUtils.getAction(model.getItem(), model.getSource());
Optional<RemoteAction> remoteAction = mClipboardUtils.getAction(
model.getText(), model.getTextLinks(), model.getSource());
if (model.equals(mClipboardModel)) {
remoteAction.ifPresent(action -> {
mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
@@ -419,10 +420,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
} else if (clipData.getItemAt(0).getUri() != null) {
if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
mOnShareTapped = () -> shareContent(clipData);
mView.showShareChip();
accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
}
mOnShareTapped = () -> shareContent(clipData);
mView.showShareChip();
} else {
mView.showDefaultTextPreview();
}

View File

@@ -65,6 +65,23 @@ class ClipboardOverlayUtils {
return false;
}
public Optional<RemoteAction> getAction(CharSequence text, TextLinks textLinks, String source) {
return getActions(text, textLinks).stream().filter(remoteAction -> {
ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
return component != null && !TextUtils.equals(source, component.getPackageName());
}).findFirst();
}
private ArrayList<RemoteAction> getActions(CharSequence text, TextLinks textLinks) {
ArrayList<RemoteAction> actions = new ArrayList<>();
for (TextLinks.TextLink link : textLinks.getLinks()) {
TextClassification classification = mTextClassifier.classifyText(
text, link.getStart(), link.getEnd(), null);
actions.addAll(classification.getActions());
}
return actions;
}
public Optional<RemoteAction> getAction(ClipData.Item item, String source) {
return getActions(item).stream().filter(remoteAction -> {
ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();

View File

@@ -51,6 +51,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import java.util.ArrayList;
import javax.inject.Provider;
@SmallTest
@@ -194,6 +196,33 @@ public class ClipboardListenerTest extends SysuiTestCase {
verifyZeroInteractions(mOverlayControllerProvider);
}
@Test
public void test_nullClipData_showsNothing() {
when(mClipboardManager.getPrimaryClip()).thenReturn(null);
mClipboardListener.start();
mClipboardListener.onPrimaryClipChanged();
verifyZeroInteractions(mUiEventLogger);
verifyZeroInteractions(mClipboardToast);
verifyZeroInteractions(mOverlayControllerProvider);
}
@Test
public void test_emptyClipData_showsToast() {
ClipDescription description = new ClipDescription("Test", new String[0]);
ClipData noItems = new ClipData(description, new ArrayList<>());
when(mClipboardManager.getPrimaryClip()).thenReturn(noItems);
mClipboardListener.start();
mClipboardListener.onPrimaryClipChanged();
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
verify(mClipboardToast, times(1)).showCopiedToast();
verifyZeroInteractions(mOverlayControllerProvider);
}
@Test
public void test_minimizedLayoutFlagOff_usesLegacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);

View File

@@ -52,18 +52,6 @@ class ClipboardModelTest : SysuiTestCase() {
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"
@@ -71,7 +59,9 @@ class ClipboardModelTest : SysuiTestCase() {
assertEquals(mSampleClipData, model.clipData)
assertEquals(source, model.source)
assertEquals(ClipboardModel.Type.TEXT, model.type)
assertEquals(mSampleClipData.getItemAt(0), model.item)
assertEquals(mSampleClipData.getItemAt(0).text, model.text)
assertEquals(mSampleClipData.getItemAt(0).textLinks, model.textLinks)
assertEquals(mSampleClipData.getItemAt(0).uri, model.uri)
assertFalse(model.isSensitive)
assertFalse(model.isRemote)
assertNull(model.loadThumbnail(mContext))
@@ -84,7 +74,7 @@ class ClipboardModelTest : SysuiTestCase() {
b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
description.extras = b
val data = ClipData(description, mSampleClipData.getItemAt(0))
val (_, _, _, _, sensitive) =
val (_, _, _, _, _, _, sensitive) =
ClipboardModel.fromClipData(mContext, mClipboardUtils, data, "")
assertTrue(sensitive)
}

View File

@@ -138,30 +138,36 @@ 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() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
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)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@Test
public void test_setClipData_nonImageUri_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
ClipData clipData = new ClipData("", new String[]{"resource/png"},
new ClipData.Item(Uri.parse("")));
mOverlayController.setClipDataLegacy(clipData, "");
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
verify(mClipboardOverlayView, times(1)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@Test
public void test_setClipData_textData_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mOverlayController.setClipDataLegacy(mSampleClipData, "");
verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
@@ -171,6 +177,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_setClipData_sensitiveTextData_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
ClipDescription description = mSampleClipData.getDescription();
PersistableBundle b = new PersistableBundle();
b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
@@ -185,6 +193,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_setClipData_repeatedCalls_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
when(mAnimator.isRunning()).thenReturn(true);
mOverlayController.setClipDataLegacy(mSampleClipData, "");
@@ -195,6 +204,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_viewCallbacks_onShareTapped_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mOverlayController.setClipDataLegacy(mSampleClipData, "");
mCallbacks.onShareButtonTapped();
@@ -205,6 +215,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_viewCallbacks_onDismissTapped_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mOverlayController.setClipDataLegacy(mSampleClipData, "");
mCallbacks.onDismissButtonTapped();
@@ -215,6 +226,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_multipleDismissals_dismissesOnce_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mCallbacks.onSwipeDismissInitiated(mAnimator);
mCallbacks.onDismissButtonTapped();
mCallbacks.onSwipeDismissInitiated(mAnimator);
@@ -226,6 +239,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_remoteCopy_withFlagOn_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
@@ -236,6 +250,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_remoteCopy_withFlagOff_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
mOverlayController.setClipDataLegacy(mSampleClipData, "");
@@ -245,6 +260,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_nonRemoteCopy_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
@@ -255,6 +271,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_logsUseLastClipSource_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mOverlayController.setClipDataLegacy(mSampleClipData, "first.package");
mCallbacks.onDismissButtonTapped();
mOverlayController.setClipDataLegacy(mSampleClipData, "second.package");
@@ -267,6 +285,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
@Test
public void test_logOnClipboardActionsShown_legacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
ClipData.Item item = mSampleClipData.getItemAt(0);
item.setTextLinks(Mockito.mock(TextLinks.class));
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
@@ -291,16 +310,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
}
// start of refactored setClipData tests
@Test
public void test_setClipData_nullData() {
ClipData clipData = null;
mOverlayController.setClipData(clipData, "");
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
verify(mClipboardOverlayView, times(0)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@Test
public void test_setClipData_invalidImageData() {
ClipData clipData = new ClipData("", new String[]{"image/png"},
@@ -309,7 +318,19 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
mOverlayController.setClipData(clipData, "");
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
verify(mClipboardOverlayView, times(0)).showShareChip();
verify(mClipboardOverlayView, times(1)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@Test
public void test_setClipData_nonImageUri() {
ClipData clipData = new ClipData("", new String[]{"resource/png"},
new ClipData.Item(Uri.parse("")));
mOverlayController.setClipData(clipData, "");
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
verify(mClipboardOverlayView, times(1)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@@ -425,7 +446,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
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()))
when(mClipboardUtils.getAction(any(CharSequence.class), any(TextLinks.class), anyString()))
.thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
@Override