chromium/chrome/android/junit/src/org/chromium/chrome/browser/dragdrop/ChromeDragAndDropBrowserDelegateUnitTest.java

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.dragdrop;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.content.ClipData.Item;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.view.DragAndDropPermissions;
import android.view.DragEvent;

import androidx.test.core.app.ApplicationProvider;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.annotation.Config;

import org.chromium.base.ContextUtils;
import org.chromium.base.FeatureList;
import org.chromium.base.FeatureList.TestValues;
import org.chromium.base.IntentUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.multiwindow.MultiWindowTestUtils;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.MockTab;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.common.ContentFeatures;
import org.chromium.ui.base.MimeTypeUtils;
import org.chromium.ui.dragdrop.DragDropMetricUtils.UrlIntentSource;
import org.chromium.url.JUnitTestGURLs;

/** Unit test for {@link ChromeDragAndDropBrowserDelegate}. */
@RunWith(BaseRobolectricTestRunner.class)
public class ChromeDragAndDropBrowserDelegateUnitTest {
    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();

    @Mock private Activity mActivity;
    @Mock private DragEvent mDragEvent;
    @Mock private DragAndDropPermissions mDragAndDropPermissions;
    @Mock private Profile mProfile;

    private Context mApplicationContext;
    private ChromeDragAndDropBrowserDelegate mDelegate;
    private FeatureList.TestValues mTestValues;

    @Before
    public void setup() throws NameNotFoundException {
        mTestValues = new TestValues();
        mTestValues.addFeatureFlagOverride(ContentFeatures.TOUCH_DRAG_AND_CONTEXT_MENU, true);
        mTestValues.addFeatureFlagOverride(ChromeFeatureList.ANIMATED_IMAGE_DRAG_SHADOW, false);
        FeatureList.setTestValues(mTestValues);

        mApplicationContext = ContextUtils.getApplicationContext();
        ContextUtils.initApplicationContextForTests(mApplicationContext);
        PriceTrackingFeatures.setPriceTrackingEnabledForTesting(false);

        when(mActivity.requestDragAndDropPermissions(mDragEvent))
                .thenReturn(mDragAndDropPermissions);
        when(mActivity.getApplicationContext())
                .thenReturn(ApplicationProvider.getApplicationContext());

        mDelegate = new ChromeDragAndDropBrowserDelegate(() -> mActivity);
    }

    @After
    public void teardown() {
        ChromeDragAndDropBrowserDelegate.setClipDataItemWithPendingIntentForTesting(null);
    }

    @Test
    public void testDragAndDropBrowserDelegate_getDragAndDropPermissions() {
        mTestValues.addFieldTrialParamOverride(
                ContentFeatures.TOUCH_DRAG_AND_CONTEXT_MENU,
                ChromeDragAndDropBrowserDelegate.PARAM_DROP_IN_CHROME,
                "true");
        mDelegate = new ChromeDragAndDropBrowserDelegate(() -> mActivity);
        assertTrue("SupportDropInChrome should be true.", mDelegate.getSupportDropInChrome());
        assertFalse(
                "SupportAnimatedImageDragShadow should be false.",
                mDelegate.getSupportAnimatedImageDragShadow());

        DragAndDropPermissions permissions = mDelegate.getDragAndDropPermissions(mDragEvent);
        assertNotNull("DragAndDropPermissions should not be null.", permissions);
    }

    @Test
    public void testDragAndDropBrowserDelegate_NotSupportDropInChrome() {
        mTestValues.addFieldTrialParamOverride(
                ContentFeatures.TOUCH_DRAG_AND_CONTEXT_MENU,
                ChromeDragAndDropBrowserDelegate.PARAM_DROP_IN_CHROME,
                "false");
        mDelegate = new ChromeDragAndDropBrowserDelegate(() -> mActivity);
        assertFalse("SupportDropInChrome should be false.", mDelegate.getSupportDropInChrome());

        AssertionError error = null;
        try {
            mDelegate.getDragAndDropPermissions(mDragEvent);
        } catch (AssertionError e) {
            error = e;
        }

        assertNotNull(
                "getDragAndDropPermissions should raise assert exception "
                        + "when accessed with drop in Chrome disabled.",
                error);
    }

    @Test
    @Config(sdk = 30)
    public void testDragAndDropBrowserDelegate_createLinkIntent_PostR() {
        MultiWindowTestUtils.enableMultiInstance();
        Intent intent =
                mDelegate.createUrlIntent(
                        JUnitTestGURLs.EXAMPLE_URL.getSpec(), UrlIntentSource.TAB_IN_STRIP);
        assertEquals(
                "The intent flags should match.",
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK,
                intent.getFlags());
        assertEquals(
                "The intent class should be DragAndDropLauncherActivity.",
                DragAndDropLauncherActivity.class.getName(),
                intent.getComponent().getClassName());
        assertTrue(
                "The intent should contain the CATEGORY_BROWSABLE category.",
                intent.getCategories().contains(Intent.CATEGORY_BROWSABLE));
        assertTrue(
                "preferNew extra should be true.",
                intent.getBooleanExtra(IntentHandler.EXTRA_PREFER_NEW, false));
        assertEquals(
                "The intent should contain Uri data.",
                Uri.parse(JUnitTestGURLs.EXAMPLE_URL.getSpec()),
                intent.getData());
        assertFalse(
                "The intent should not contain the trusted application extra.",
                intent.hasExtra(IntentUtils.TRUSTED_APPLICATION_CODE_EXTRA));
        assertEquals(
                "The UrlIntentSource extra should match.",
                UrlIntentSource.TAB_IN_STRIP,
                intent.getIntExtra(IntentHandler.EXTRA_URL_DRAG_SOURCE, UrlIntentSource.UNKNOWN));
    }

    @Test
    @Config(sdk = 29)
    public void testDragAndDropBrowserDelegate_createLinkIntent_PreR() {
        Intent intent =
                mDelegate.createUrlIntent(
                        JUnitTestGURLs.EXAMPLE_URL.getSpec(), UrlIntentSource.LINK);
        assertNull("The intent should be null on R- versions.", intent);
    }

    @Test
    @Config(sdk = 30)
    public void testBuildClipData() {
        MultiWindowTestUtils.enableMultiInstance();
        var dropData = createTabDropData(1, false);
        var data = mDelegate.buildClipData(dropData);
        assertEquals(
                "The browser clip data is not as expected",
                dropData.buildTabClipDataText(),
                data.getItemAt(0).getText());
        assertNull("The clip data should not have intent set.", data.getItemAt(0).getIntent());
        assertTrue(
                "The clip data should contain chrome/tab mimetype.",
                data.getDescription().hasMimeType(MimeTypeUtils.CHROME_MIMETYPE_TAB));
        assertTrue(
                "The clip data should contain chrome/link mimetype.",
                data.getDescription().hasMimeType(MimeTypeUtils.CHROME_MIMETYPE_LINK));
        assertTrue(
                "The clip data should contain text/plain mimetype.",
                data.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN));
        assertTrue(
                "The clip data should contain text/vnd.android.intent mimetype.",
                data.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT));
    }

    @Test
    public void testBuildClipDataForTabDragWithItemFromBuilder() {
        testBuildClipDataForTabDragToCreateNewInstance(true);
    }

    @Test
    public void testBuildClipDataForTabDragWithNullItemFromBuilder() {
        testBuildClipDataForTabDragToCreateNewInstance(false);
    }

    @Test
    public void testBuildFlags_dropDataHasNoTab() {
        MultiWindowTestUtils.enableMultiInstance();
        int originalFlag = 0;
        var dropData = new ChromeDropDataAndroid.Builder().build();
        var flags = mDelegate.buildFlags(originalFlag, dropData);
        assertEquals("Original flag should not be modified.", originalFlag, flags);
    }

    @Test
    public void testBuildFlags_dropDataHasTabAndTabDragToCreateInstanceNotAllowed() {
        MultiWindowTestUtils.enableMultiInstance();
        int originalFlag = 0;
        var dropData = createTabDropData(1, false);
        var flags = mDelegate.buildFlags(originalFlag, dropData);
        assertEquals("Original flag should not be modified.", originalFlag, flags);
    }

    @Test
    public void testBuildFlags_dropDataHasTabAndTabDragToCreateInstanceAllowed() {
        MultiWindowTestUtils.enableMultiInstance();
        int originalFlag = 0;
        var dropData = createTabDropData(1, true);
        var flags = mDelegate.buildFlags(originalFlag, dropData);
        assertTrue(
                "Drag flags should contain DRAG_FLAG_GLOBAL_SAME_APPLICATION.",
                (flags & (1 << 12)) != 0);
        assertTrue(
                "Drag flags should contain DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG.",
                (flags & (1 << 13)) != 0);
    }

    private ChromeDropDataAndroid createTabDropData(
            int tabId, boolean allowDragToCreateNewInstance) {
        Tab tab = MockTab.createAndInitialize(tabId, mProfile);
        return new ChromeDropDataAndroid.Builder()
                .withTab(tab)
                .withAllowDragToCreateInstance(allowDragToCreateNewInstance)
                .build();
    }

    private void testBuildClipDataForTabDragToCreateNewInstance(boolean withItem) {
        MultiWindowTestUtils.enableMultiInstance();
        var tab = MockTab.createAndInitialize(1, mProfile);
        var dropData = createTabDropData(1, true);
        var item =
                withItem
                        ? new Item(
                                DragAndDropLauncherActivity.getTabIntent(
                                        mApplicationContext,
                                        tab,
                                        MultiWindowUtils.INVALID_INSTANCE_ID))
                        : null;
        ChromeDragAndDropBrowserDelegate.setClipDataItemWithPendingIntentForTesting(item);

        var data = mDelegate.buildClipData(dropData);
        assertNotNull("The clip data should have an intent set.", data.getItemAt(0).getIntent());
        assertEquals(
                "Tab id extra is incorrect.",
                tab.getId(),
                data.getItemAt(0)
                        .getIntent()
                        .getIntExtra(IntentHandler.EXTRA_DRAGGED_TAB_ID, Tab.INVALID_TAB_ID));
    }
}