blob: 4d7d6a9d1511d29d09f0a8a8a58ab7df562a4e0d [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.v7.app.AlertDialog;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.content_public.browser.test.NativeLibraryTestRule;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.net.test.EmbeddedTestServer;
import java.io.InputStream;
import java.util.ArrayList;
/** Test suite for Web Share (navigator.share) functionality. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class WebShareTest {
@Rule
public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
new ChromeActivityTestRule<>(ChromeActivity.class);
@Rule
public final NativeLibraryTestRule mNativeLibraryTestRule = new NativeLibraryTestRule();
private static final String TEST_FILE = "/content/test/data/android/webshare.html";
private static final String TEST_FILE_APK = "/content/test/data/android/webshare-apk.html";
private static final String TEST_FILE_DEX = "/content/test/data/android/webshare-dex.html";
private static final String TEST_FILE_OGG = "/content/test/data/android/webshare-ogg.html";
private EmbeddedTestServer mTestServer;
private Tab mTab;
private WebShareUpdateWaiter mUpdateWaiter;
private Intent mReceivedIntent;
/** Waits until the JavaScript code supplies a result. */
private class WebShareUpdateWaiter extends EmptyTabObserver {
private CallbackHelper mCallbackHelper;
private String mStatus;
public WebShareUpdateWaiter() {
mCallbackHelper = new CallbackHelper();
}
@Override
public void onTitleUpdated(Tab tab) {
String title = mActivityTestRule.getActivity().getActivityTab().getTitle();
// Wait until the title indicates either success or failure.
if (!title.equals("Success") && !title.startsWith("Fail:")) return;
mStatus = title;
mCallbackHelper.notifyCalled();
}
public String waitForUpdate() throws Exception {
mCallbackHelper.waitForCallback(0);
return mStatus;
}
}
@Before
public void setUp() throws Exception {
mActivityTestRule.startMainActivityOnBlankPage();
mNativeLibraryTestRule.loadNativeLibraryNoBrowserProcess();
mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
mTab = mActivityTestRule.getActivity().getActivityTab();
mUpdateWaiter = new WebShareUpdateWaiter();
mTab.addObserver(mUpdateWaiter);
mReceivedIntent = null;
}
@After
public void tearDown() throws Exception {
if (mTab != null) mTab.removeObserver(mUpdateWaiter);
if (mTestServer != null) mTestServer.stopAndDestroyServer();
// Clean up some state that might have been changed by tests.
ShareHelper.setForceCustomChooserForTesting(false);
ShareHelper.setFakeIntentReceiverForTesting(null);
}
/**
* Verify that WebShare fails if called without a user gesture.
* @throws Exception
*/
@Test
@MediumTest
@Feature({"WebShare"})
public void testWebShareNoUserGesture() throws Exception {
mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE));
mActivityTestRule.runJavaScriptCodeInCurrentTab("initiate_share()");
Assert.assertEquals("Fail: NotAllowedError: "
+ "Must be handling a user gesture to perform a share request.",
mUpdateWaiter.waitForUpdate());
}
/**
* Verify WebShare fails if share is called from a user gesture, and canceled.
* This test tests functionality that is only available post Lollipop MR1.
* @throws Exception
*/
@Test
@MediumTest
@Feature({"WebShare"})
@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP_MR1)
public void testWebShareCancel() throws Exception {
// Set up ShareHelper to ignore the intent (without showing a picker). This simulates the
// user canceling the dialog.
ShareHelper.setFakeIntentReceiverForTesting(new FakeIntentReceiverPostLMR1(false));
mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE));
// Click (instead of directly calling the JavaScript function) to simulate a user gesture.
TouchCommon.singleClickView(mTab.getView());
Assert.assertEquals("Fail: AbortError: Share canceled", mUpdateWaiter.waitForUpdate());
}
/**
* Verify WebShare succeeds if share is called from a user gesture, and app chosen.
* This test tests functionality that is only available post Lollipop MR1.
* @throws Exception
*/
@Test
@MediumTest
@Feature({"WebShare"})
@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP_MR1)
public void testWebShareSuccess() throws Exception {
// Set up ShareHelper to immediately succeed (without showing a picker).
ShareHelper.setFakeIntentReceiverForTesting(new FakeIntentReceiverPostLMR1(true));
mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE));
// Click (instead of directly calling the JavaScript function) to simulate a user gesture.
TouchCommon.singleClickView(mTab.getView());
Assert.assertEquals("Success", mUpdateWaiter.waitForUpdate());
// The actual intent to be delivered to the target is in the EXTRA_INTENT of the chooser
// intent.
Assert.assertNotNull(mReceivedIntent);
Assert.assertTrue(mReceivedIntent.hasExtra(Intent.EXTRA_INTENT));
verifyDeliveredIntent(mReceivedIntent.getParcelableExtra(Intent.EXTRA_INTENT));
}
/**
* Verify WebShare of .ogg file succeeds if share is called from a user gesture, and app chosen.
* This test tests functionality that is only available post Lollipop MR1.
* @throws Exception
*/
@Test
@MediumTest
@Feature({"WebShare"})
@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP_MR1)
public void testWebShareOgg() throws Exception {
// Set up ShareHelper to immediately succeed (without showing a picker).
ShareHelper.setFakeIntentReceiverForTesting(new FakeIntentReceiverPostLMR1(true));
mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE_OGG));
// Click (instead of directly calling the JavaScript function) to simulate a user gesture.
TouchCommon.singleClickView(mTab.getView());
Assert.assertEquals("Success", mUpdateWaiter.waitForUpdate());
// The actual intent to be delivered to the target is in the EXTRA_INTENT of the chooser
// intent.
Assert.assertNotNull(mReceivedIntent);
Assert.assertTrue(mReceivedIntent.hasExtra(Intent.EXTRA_INTENT));
verifyDeliveredOggIntent(mReceivedIntent.getParcelableExtra(Intent.EXTRA_INTENT));
}
/**
* Verify WebShare fails if share of .apk is called from a user gesture.
* @throws Exception
*/
@Test
@MediumTest
@Feature({"WebShare"})
public void testWebShareApk() throws Exception {
mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE_APK));
// Click (instead of directly calling the JavaScript function) to simulate a user gesture.
TouchCommon.singleClickView(mTab.getView());
Assert.assertEquals(
"Fail: NotAllowedError: Permission denied", mUpdateWaiter.waitForUpdate());
}
/**
* Verify WebShare fails if share of .dex is called from a user gesture.
* @throws Exception
*/
@Test
@MediumTest
@Feature({"WebShare"})
public void testWebShareDex() throws Exception {
mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE_DEX));
// Click (instead of directly calling the JavaScript function) to simulate a user gesture.
TouchCommon.singleClickView(mTab.getView());
Assert.assertEquals(
"Fail: NotAllowedError: Permission denied", mUpdateWaiter.waitForUpdate());
}
/**
* Verify WebShare fails if share is called from a user gesture, and canceled.
*
* Simulates pre-Lollipop-LMR1 system (different intent picker).
*
* @throws Exception
*/
@Test
@MediumTest
@Feature({"WebShare"})
public void testWebShareCancelPreLMR1() throws Exception {
ShareHelper.setFakeIntentReceiverForTesting(new FakeIntentReceiverPreLMR1(false));
ShareHelper.setForceCustomChooserForTesting(true);
mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE));
// Click (instead of directly calling the JavaScript function) to simulate a user gesture.
TouchCommon.singleClickView(mTab.getView());
Assert.assertEquals("Fail: AbortError: Share canceled", mUpdateWaiter.waitForUpdate());
}
/**
* Verify WebShare succeeds if share is called from a user gesture, and app chosen.
*
* Simulates pre-Lollipop-LMR1 system (different intent picker).
*
* @throws Exception
*/
@Test
@MediumTest
@Feature({"WebShare"})
public void testWebShareSuccessPreLMR1() throws Exception {
ShareHelper.setFakeIntentReceiverForTesting(new FakeIntentReceiverPreLMR1(true));
ShareHelper.setForceCustomChooserForTesting(true);
mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE));
// Click (instead of directly calling the JavaScript function) to simulate a user gesture.
TouchCommon.singleClickView(mTab.getView());
Assert.assertEquals("Success", mUpdateWaiter.waitForUpdate());
verifyDeliveredIntent(mReceivedIntent);
}
/**
* Verify WebShare of .ogg succeeds if share is called from a user gesture, and app chosen.
*
* Simulates pre-Lollipop-LMR1 system (different intent picker).
*
* @throws Exception
*/
@Test
@MediumTest
@Feature({"WebShare"})
public void testWebShareOggPreLMR1() throws Exception {
ShareHelper.setFakeIntentReceiverForTesting(new FakeIntentReceiverPreLMR1(true));
ShareHelper.setForceCustomChooserForTesting(true);
mActivityTestRule.loadUrl(mTestServer.getURL(TEST_FILE_OGG));
// Click (instead of directly calling the JavaScript function) to simulate a user gesture.
TouchCommon.singleClickView(mTab.getView());
Assert.assertEquals("Success", mUpdateWaiter.waitForUpdate());
verifyDeliveredOggIntent(mReceivedIntent);
}
private static void verifyDeliveredIntent(Intent intent) {
Assert.assertNotNull(intent);
Assert.assertEquals(Intent.ACTION_SEND, intent.getAction());
Assert.assertTrue(intent.hasExtra(Intent.EXTRA_SUBJECT));
Assert.assertEquals("Test Title", intent.getStringExtra(Intent.EXTRA_SUBJECT));
Assert.assertTrue(intent.hasExtra(Intent.EXTRA_TEXT));
Assert.assertEquals(
"Test Text https://test.url/", intent.getStringExtra(Intent.EXTRA_TEXT));
}
private static void verifyDeliveredOggIntent(Intent intent) throws Exception {
Assert.assertNotNull(intent);
Assert.assertEquals(Intent.ACTION_SEND_MULTIPLE, intent.getAction());
Assert.assertEquals("video/ogg", intent.getType());
Assert.assertEquals(Intent.FLAG_GRANT_READ_URI_PERMISSION,
intent.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION);
ArrayList<Uri> fileUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
Assert.assertEquals(1, fileUris.size());
InputStream inputStream =
InstrumentationRegistry.getContext().getContentResolver().openInputStream(
fileUris.get(0));
byte[] buffer = new byte[1024];
int position = 0;
int read;
while ((read = inputStream.read(buffer, position, buffer.length - position)) > 0) {
position += read;
}
Assert.assertEquals("contents", new String(buffer, 0, position, "UTF-8"));
}
// Uses intent picker functionality that is only available since Lollipop MR1.
private class FakeIntentReceiverPostLMR1 implements ShareHelper.FakeIntentReceiver {
private final boolean mProceed;
private Intent mIntentToSendBack;
FakeIntentReceiverPostLMR1(boolean proceed) {
Assert.assertTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1);
mProceed = proceed;
}
@Override
public void setIntentToSendBack(Intent intent) {
mIntentToSendBack = intent;
}
@Override
public void onCustomChooserShown(AlertDialog dialog) {}
@Override
public void fireIntent(Context context, Intent intent) {
mReceivedIntent = intent;
if (!mProceed) {
// Click again to start another share, which cancels the current share.
// This is necessary to work around https://crbug.com/636274 (callback
// is not canceled until next share is initiated).
// This also serves as a regression test for https://crbug.com/640324.
TouchCommon.singleClickView(mTab.getView());
return;
}
if (context == null) return;
// Send the intent back, which indicates that the user made a choice. (Normally,
// this would have EXTRA_CHOSEN_COMPONENT set, but for the test, we do not set any
// chosen target app.)
context.sendBroadcast(mIntentToSendBack);
}
}
// Uses intent picker functionality that is available before Lollipop MR1.
private class FakeIntentReceiverPreLMR1 implements ShareHelper.FakeIntentReceiver {
private final boolean mProceed;
FakeIntentReceiverPreLMR1(boolean proceed) {
mProceed = proceed;
}
@Override
public void setIntentToSendBack(Intent intent) {}
@Override
public void onCustomChooserShown(AlertDialog dialog) {
if (!mProceed) {
// Cancel the chooser dialog.
dialog.dismiss();
return;
}
// Click on an app (it doesn't matter which, because we will hook the intent).
Assert.assertTrue(dialog.getListView().getCount() > 0);
dialog.getListView().performItemClick(
null, 0, dialog.getListView().getItemIdAtPosition(0));
}
@Override
public void fireIntent(Context context, Intent intent) {
mReceivedIntent = intent;
}
}
}