blob: b4e85c06933ab88e6d2dcea15bacd04d520c3711 [file] [log] [blame]
/*
* 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 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_REMOTE_BEHAVIOR;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.animation.Animator;
import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.net.Uri;
import android.os.PersistableBundle;
import android.view.textclassifier.TextLinks;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.screenshot.TimeoutHandler;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Optional;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ClipboardOverlayControllerTest extends SysuiTestCase {
private ClipboardOverlayController mOverlayController;
@Mock
private ClipboardOverlayView mClipboardOverlayView;
@Mock
private ClipboardOverlayWindow mClipboardOverlayWindow;
@Mock
private BroadcastSender mBroadcastSender;
@Mock
private TimeoutHandler mTimeoutHandler;
@Mock
private ClipboardOverlayUtils mClipboardUtils;
@Mock
private UiEventLogger mUiEventLogger;
private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
private Animator mAnimator;
private ClipData mSampleClipData;
@Captor
private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
new ClipData.Item("Test Item"));
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
mOverlayController = new ClipboardOverlayController(
mContext,
mClipboardOverlayView,
mClipboardOverlayWindow,
getFakeBroadcastDispatcher(),
mBroadcastSender,
mTimeoutHandler,
mFeatureFlags,
mClipboardUtils,
mExecutor,
mUiEventLogger);
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
}
@After
public void tearDown() {
mOverlayController.hideImmediate();
}
@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"},
new ClipData.Item(Uri.parse("")));
mOverlayController.setClipData(clipData, "");
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
verify(mClipboardOverlayView, times(0)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@Test
public void test_setClipData_textData() {
mOverlayController.setClipData(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() {
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.setClipData(data, "");
verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
verify(mClipboardOverlayView, times(1)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@Test
public void test_setClipData_repeatedCalls() {
when(mAnimator.isRunning()).thenReturn(true);
mOverlayController.setClipData(mSampleClipData, "");
mOverlayController.setClipData(mSampleClipData, "");
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@Test
public void test_viewCallbacks_onShareTapped() {
mOverlayController.setClipData(mSampleClipData, "");
mCallbacks.onShareButtonTapped();
verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
verify(mClipboardOverlayView, times(1)).getExitAnimation();
}
@Test
public void test_viewCallbacks_onDismissTapped() {
mOverlayController.setClipData(mSampleClipData, "");
mCallbacks.onDismissButtonTapped();
verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
verify(mClipboardOverlayView, times(1)).getExitAnimation();
}
@Test
public void test_multipleDismissals_dismissesOnce() {
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() {
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
mOverlayController.setClipData(mSampleClipData, "");
verify(mTimeoutHandler, never()).resetTimeout();
}
@Test
public void test_remoteCopy_withFlagOff() {
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
mOverlayController.setClipData(mSampleClipData, "");
verify(mTimeoutHandler).resetTimeout();
}
@Test
public void test_nonRemoteCopy() {
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
mOverlayController.setClipData(mSampleClipData, "");
verify(mTimeoutHandler).resetTimeout();
}
@Test
public void test_logsUseLastClipSource() {
mOverlayController.setClipData(mSampleClipData, "first.package");
mCallbacks.onDismissButtonTapped();
mOverlayController.setClipData(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() {
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.setClipData(
new ClipData(mSampleClipData.getDescription(), item), "actionShownSource");
mExecutor.runAllReady();
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
verifyNoMoreInteractions(mUiEventLogger);
}
}