| // Copyright 2014 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.media.remote; |
| |
| import android.app.Dialog; |
| import android.app.Instrumentation; |
| import android.graphics.Rect; |
| import android.os.StrictMode; |
| import android.os.SystemClock; |
| import android.support.v4.app.DialogFragment; |
| import android.support.v4.app.FragmentManager; |
| import android.view.MotionEvent; |
| import android.view.View; |
| |
| import junit.framework.Assert; |
| |
| import org.chromium.base.Log; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.ChromeActivity; |
| import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState; |
| import org.chromium.chrome.browser.tab.Tab; |
| import org.chromium.chrome.test.ChromeActivityTestCaseBase; |
| import org.chromium.chrome.test.util.TestHttpServerClient; |
| import org.chromium.content.browser.ContentViewCore; |
| import org.chromium.content.browser.test.util.DOMUtils; |
| import org.chromium.content.browser.test.util.JavaScriptUtils; |
| import org.chromium.content.browser.test.util.TestTouchUtils; |
| import org.chromium.content.browser.test.util.UiUtils; |
| import org.chromium.content_public.browser.WebContents; |
| |
| import java.util.ArrayList; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.TimeoutException; |
| |
| /** |
| * Base class for tests of Clank Cast. Contains functions for setting up a cast connection and other |
| * utility functions. |
| */ |
| public abstract class CastTestBase extends ChromeActivityTestCaseBase<ChromeActivity> { |
| private class TestListener implements MediaRouteController.UiListener { |
| @Override |
| public void onRouteSelected(String name, MediaRouteController mediaRouteController) { |
| } |
| |
| @Override |
| public void onRouteUnselected(MediaRouteController mediaRouteController) { |
| } |
| |
| @Override |
| public void onPrepared(MediaRouteController mediaRouteController) { |
| } |
| |
| @Override |
| public void onError(int errorType, String message) { |
| } |
| |
| @Override |
| public void onPlaybackStateChanged(PlayerState oldState, PlayerState newState) { |
| if (newState == PlayerState.PLAYING) { |
| mPlaying = true; |
| } else if (newState != PlayerState.PLAYING) { |
| mPlaying = false; |
| } |
| } |
| |
| @Override |
| public void onDurationUpdated(long durationMillis) {} |
| |
| @Override |
| public void onPositionChanged(long positionMillis) {} |
| |
| @Override |
| public void onTitleChanged(String title) { |
| } |
| |
| } |
| |
| // The name of the route provided by the dummy cast device. |
| protected static final String CAST_TEST_ROUTE = "Cast Test Route"; |
| |
| // URLs of the default test page and video. |
| protected static final String DEFAULT_VIDEO_PAGE = |
| "chrome/test/data/android/media/simple_video.html"; |
| protected static final String DEFAULT_VIDEO = "chrome/test/data/android/media/test.mp4"; |
| |
| // Constants used to find the default video and maximise button on the page |
| protected static final String VIDEO_ELEMENT = "video"; |
| |
| // Max time to open a view. |
| protected static final int MAX_VIEW_TIME_MS = 10000; |
| |
| // Time to let a video run to ensure that it has started. |
| protected static final int RUN_TIME_MS = 1000; |
| // Time to allow for the UI to react to video controls, |
| protected static final int STABILIZE_TIME_MS = 3000; |
| |
| // Retry interval when looking for a view. |
| protected static final int VIEW_RETRY_MS = 100; |
| |
| protected static final String TEST_VIDEO_PAGE_2 = |
| "chrome/test/data/android/media/simple_video2.html"; |
| |
| protected static final String TEST_VIDEO_2 = "chrome/test/data/android/media/test2.mp4"; |
| |
| protected static final String TWO_VIDEO_PAGE = "chrome/test/data/android/media/two_videos.html"; |
| |
| private static final String TAG = "CastTestBase"; |
| |
| private boolean mPlaying = false; |
| |
| private MediaRouteController mMediaRouteController; |
| |
| private StrictMode.ThreadPolicy mOldPolicy; |
| |
| public CastTestBase() { |
| super(ChromeActivity.class); |
| } |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| // Temporary until support library is updated, see http://crbug.com/576393. |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| mOldPolicy = StrictMode.allowThreadDiskReads(); |
| StrictMode.allowThreadDiskWrites(); |
| } |
| }); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| // Temporary until support library is updated, see http://crbug.com/576393. |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| StrictMode.setThreadPolicy(mOldPolicy); |
| } |
| }); |
| super.tearDown(); |
| } |
| |
| @Override |
| public void startMainActivity() throws InterruptedException { |
| startMainActivityOnBlankPage(); |
| } |
| |
| protected void castAndPauseDefaultVideoFromPage(String pagePath) throws InterruptedException, |
| TimeoutException { |
| Rect videoRect = castDefaultVideoFromPage(pagePath); |
| |
| final Tab tab = getActivity().getActivityTab(); |
| |
| Rect pauseButton = playPauseButton(videoRect); |
| |
| // Make sure the video has made some progress |
| Thread.sleep(RUN_TIME_MS); |
| |
| tapButton(tab, pauseButton); |
| assertTrue("Not paused", waitForState(PlayerState.PAUSED)); |
| } |
| |
| private boolean videoReady(String videoElement, WebContents webContents) { |
| // Create a javascript function to check if the video meta-data has been loaded. |
| StringBuilder sb = new StringBuilder(); |
| sb.append("(function() {"); |
| sb.append(" var node = document.getElementById('" + videoElement + "');"); |
| sb.append(" if (!node) return null;"); |
| // Any video readyState value greater than 0 means that at least the meta-data has been |
| // loaded but we also need the a document readyState of complete to ensure that page has |
| // been laid out with the correct video size, and everything is drawn. |
| sb.append(" return node.readyState > 0 && document.readyState == 'complete';"); |
| sb.append("})();"); |
| String javascriptResult; |
| try { |
| javascriptResult = JavaScriptUtils.executeJavaScriptAndWaitForResult( |
| webContents, sb.toString()); |
| Assert.assertFalse("Failed to retrieve contents for " + videoElement, |
| javascriptResult.trim().equalsIgnoreCase("null")); |
| |
| Boolean ready = javascriptResult.trim().equalsIgnoreCase("true"); |
| return ready; |
| } catch (InterruptedException e) { |
| Assert.fail("Interrupted"); |
| } catch (TimeoutException e) { |
| Assert.fail("Javascript execution timed out"); |
| } |
| return false; |
| } |
| |
| protected void waitUntilVideoReady(String videoElement, WebContents webContents) { |
| for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) { |
| try { |
| if (videoReady(videoElement, webContents)) return; |
| } catch (Exception e) { |
| fail(e.toString()); |
| } |
| sleepNoThrow(VIEW_RETRY_MS); |
| } |
| Assert.fail("Video not ready"); |
| } |
| |
| protected Rect prepareDefaultVideofromPage(String pagePath, Tab currentTab) |
| throws InterruptedException, TimeoutException { |
| |
| loadUrl(TestHttpServerClient.getUrl(pagePath)); |
| |
| WebContents webContents = currentTab.getWebContents(); |
| |
| waitUntilVideoReady(VIDEO_ELEMENT, webContents); |
| |
| return DOMUtils.getNodeBounds(webContents, VIDEO_ELEMENT); |
| } |
| |
| protected Rect castDefaultVideoFromPage(String pagePath) |
| throws InterruptedException, TimeoutException { |
| final Tab tab = getActivity().getActivityTab(); |
| final Rect videoRect = prepareDefaultVideofromPage(pagePath, tab); |
| |
| castVideoAndWaitUntilPlaying(CAST_TEST_ROUTE, tab, videoRect); |
| |
| return videoRect; |
| } |
| |
| protected void castVideoAndWaitUntilPlaying(final String chromecastName, final Tab tab, |
| final Rect videoRect) { |
| castVideo(chromecastName, tab, videoRect); |
| |
| assertTrue("Video didn't start playing", waitUntilPlaying()); |
| |
| } |
| |
| protected void castVideo(final String chromecastName, final Tab tab, final Rect videoRect) { |
| Log.i(TAG, "castVideo, videoRect = " + videoRect); |
| |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| RemoteMediaPlayerController playerController = |
| RemoteMediaPlayerController.instance(); |
| mMediaRouteController = playerController.getMediaRouteController( |
| TestHttpServerClient.getUrl(DEFAULT_VIDEO), |
| TestHttpServerClient.getUrl(DEFAULT_VIDEO_PAGE)); |
| assertNotNull("Could not get MediaRouteController", mMediaRouteController); |
| mMediaRouteController.addUiListener(new TestListener()); |
| } |
| }); |
| tapCastButton(tab, videoRect); |
| |
| // Wait for the test device to appear in the device list. |
| try { |
| UiUtils.settleDownUI(getInstrumentation()); |
| } catch (InterruptedException e) { |
| fail(); |
| } |
| |
| View testRouteButton = waitForRouteButton(chromecastName); |
| assertNotNull("Test route not found", testRouteButton); |
| |
| mouseSingleClickView(getInstrumentation(), testRouteButton); |
| } |
| |
| protected View waitForRouteButton(final String chromecastName) { |
| return waitForView(new Callable<View>() { |
| @Override |
| public View call() { |
| FragmentManager fm = getActivity().getSupportFragmentManager(); |
| if (fm == null) return null; |
| DialogFragment mediaRouteListFragment = (DialogFragment) fm.findFragmentByTag( |
| "android.support.v7.mediarouter:MediaRouteChooserDialogFragment"); |
| if (mediaRouteListFragment == null || mediaRouteListFragment.getDialog() == null) { |
| return null; |
| } |
| View mediaRouteList = |
| mediaRouteListFragment.getDialog().findViewById(R.id.mr_chooser_list); |
| if (mediaRouteList == null) return null; |
| ArrayList<View> routesWanted = new ArrayList<View>(); |
| mediaRouteList.findViewsWithText(routesWanted, chromecastName, |
| View.FIND_VIEWS_WITH_TEXT); |
| if (routesWanted.size() == 0) return null; |
| |
| return routesWanted.get(0); |
| } |
| }, MAX_VIEW_TIME_MS); |
| } |
| |
| protected void checkDisconnected() { |
| for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) { |
| if (isDisconnected()) break; |
| sleepNoThrow(VIEW_RETRY_MS); |
| } |
| // Could use assertTrue(isDisconnected()) here, but retesting the individual aspects of |
| // disconnection gives more specific error messages. |
| NotificationTransportControl notificationTransportControl = |
| NotificationTransportControl.getIfExists(); |
| if (notificationTransportControl != null && notificationTransportControl.isShowing()) { |
| fail("Failed to close notification"); |
| } |
| assertEquals("Video still playing?", null, getUriPlaying()); |
| assertTrue("RemoteMediaPlayerController not stopped", !isPlayingRemotely()); |
| } |
| |
| protected void clickDisconnectFromRoute(Tab tab, Rect videoRect) { |
| // Click on the cast control button to stop casting |
| tapCastButton(tab, videoRect); |
| |
| // Wait for the disconnect button |
| final View disconnectButton = waitForView(new Callable<View>() { |
| @Override |
| public View call() { |
| FragmentManager fm = getActivity().getSupportFragmentManager(); |
| if (fm == null) return null; |
| DialogFragment mediaRouteControllerFragment = (DialogFragment) fm.findFragmentByTag( |
| "android.support.v7.mediarouter:MediaRouteControllerDialogFragment"); |
| if (mediaRouteControllerFragment == null) return null; |
| Dialog dialog = mediaRouteControllerFragment.getDialog(); |
| if (dialog == null) return null; |
| // The stop button (previously called disconnect) simply uses 'button1' in the |
| // latest version of the support library. See: |
| // https://cs.corp.google.com/#android/frameworks/support/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java&l=90. |
| // TODO(aberent) remove dependency on internals of support library |
| // https://crbug/548599 |
| return dialog.findViewById(android.R.id.button1); |
| } |
| }, MAX_VIEW_TIME_MS); |
| |
| assertNotNull("No disconnect button", disconnectButton); |
| |
| clickButton(disconnectButton); |
| } |
| |
| /* |
| * Check that a (non-YouTube) video has started playing, and that all the controls have been |
| * correctly set up. |
| */ |
| protected void checkVideoStarted(String testVideo) { |
| // Check we have a notification |
| NotificationTransportControl notificationTransportControl = waitForCastNotification(); |
| assertNotNull("No notification controller", notificationTransportControl); |
| waitForCastNotificationService(notificationTransportControl); |
| assertTrue("No notification", notificationTransportControl.isShowing()); |
| // Check that we are playing the right video |
| waitUntilVideoCurrent(testVideo); |
| assertEquals( |
| "Wrong video playing", TestHttpServerClient.getUrl(testVideo), getUriPlaying()); |
| |
| // Check that the RemoteMediaPlayerController and the (YouTube)MediaRouteController have |
| // been set up correctly |
| waitUntilPlaying(); |
| RemoteMediaPlayerController playerController = RemoteMediaPlayerController.getIfExists(); |
| assertNotNull("No RemoteMediaPlayerController", playerController); |
| assertTrue("Video not playing", isPlayingRemotely()); |
| assertTrue("Wrong sort of MediaRouteController", (playerController |
| .getCurrentlyPlayingMediaRouteController() instanceof DefaultMediaRouteController)); |
| } |
| |
| /** |
| * Click a button. Unlike {@link CastTestBase#mouseSingleClickView} this directly accesses the |
| * view and does not send motion events though the message queue. As such it doesn't require the |
| * view to have been created by the instrumented activity, but gives less flexibility than |
| * mouseSingleClickView. For example, if the view is hierachical, then clickButton will always |
| * act on specified view, whereas mouseSingleClickView will send the events to the appropriate |
| * child view. It is hence only really appropriate for simple views such as buttons. |
| * |
| * @param button the button to be clicked. |
| */ |
| protected void clickButton(final View button) { |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| // Post the actual click to the button's message queue, to ensure that it has been |
| // inflated before the click is received. |
| button.post(new Runnable() { |
| @Override |
| public void run() { |
| button.performClick(); |
| } |
| }); |
| } |
| }); |
| } |
| |
| protected void sleepNoThrow(long timeout) { |
| try { |
| Thread.sleep(timeout); |
| } catch (InterruptedException e) { |
| fail(e.toString()); |
| } |
| } |
| |
| protected void tapVideoFullscreenButton(final Tab tab, final Rect videoRect) { |
| tapButton(tab, fullscreenButton(videoRect)); |
| } |
| |
| protected void tapCastButton(final Tab tab, final Rect videoRect) { |
| tapButton(tab, castButton(videoRect)); |
| } |
| |
| protected void tapPlayPauseButton(final Tab tab, final Rect videoRect) { |
| tapButton(tab, playPauseButton(videoRect)); |
| } |
| |
| protected View waitForView(Callable<View> getViewCallable, int timeoutMs) { |
| for (int time = 0; time < timeoutMs; time += VIEW_RETRY_MS) { |
| try { |
| View result = ThreadUtils.runOnUiThreadBlocking(getViewCallable); |
| if (result != null) return result; |
| } catch (Exception e) { |
| fail(e.toString()); |
| } |
| sleepNoThrow(VIEW_RETRY_MS); |
| } |
| return null; |
| } |
| |
| protected NotificationTransportControl waitForCastNotification() { |
| for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) { |
| NotificationTransportControl result = ThreadUtils.runOnUiThreadBlockingNoException( |
| new Callable<NotificationTransportControl>() { |
| @Override |
| public NotificationTransportControl call() { |
| return NotificationTransportControl.getIfExists(); |
| } |
| }); |
| if (result != null) { |
| return result; |
| } |
| sleepNoThrow(VIEW_RETRY_MS); |
| } |
| return null; |
| } |
| |
| protected NotificationTransportControl.ListenerService waitForCastNotificationService( |
| final NotificationTransportControl notification) { |
| for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) { |
| NotificationTransportControl.ListenerService service = |
| ThreadUtils.runOnUiThreadBlockingNoException( |
| new Callable<NotificationTransportControl.ListenerService>() { |
| @Override |
| public NotificationTransportControl.ListenerService call() { |
| return notification.getService(); |
| } |
| }); |
| if (service != null) { |
| return service; |
| } |
| sleepNoThrow(VIEW_RETRY_MS); |
| } |
| return null; |
| } |
| |
| protected boolean waitForState(final RemoteVideoInfo.PlayerState state) { |
| for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) { |
| boolean result = ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| RemoteMediaPlayerController playerController = |
| RemoteMediaPlayerController.getIfExists(); |
| return playerController != null |
| && playerController.getCurrentlyPlayingMediaRouteController() != null |
| && playerController.getCurrentlyPlayingMediaRouteController() |
| .getPlayerState() |
| == state; |
| } |
| }); |
| if (result) return true; |
| sleepNoThrow(VIEW_RETRY_MS); |
| } |
| return false; |
| } |
| |
| protected boolean waitUntilPlaying() { |
| for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) { |
| boolean playing = ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| return mPlaying; |
| } |
| }); |
| if (playing) return true; |
| sleepNoThrow(VIEW_RETRY_MS); |
| } |
| return false; |
| } |
| |
| private boolean isDisconnected() { |
| return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| NotificationTransportControl notificationTransportControl = |
| NotificationTransportControl.getIfExists(); |
| if (notificationTransportControl != null |
| && notificationTransportControl.isShowing()) { |
| return false; |
| } |
| if (getUriPlaying() != null) return false; |
| return !isPlayingRemotely(); |
| } |
| }); |
| } |
| |
| private boolean waitUntilVideoCurrent(String testVideo) { |
| for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) { |
| if (TestHttpServerClient.getUrl(testVideo).equals(getUriPlaying())) { |
| return true; |
| } |
| sleepNoThrow(VIEW_RETRY_MS); |
| } |
| return false; |
| } |
| |
| protected String getUriPlaying() { |
| return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<String>() { |
| @Override |
| public String call() { |
| if (mMediaRouteController == null) return ""; |
| return mMediaRouteController.getUriPlaying(); |
| } |
| }); |
| } |
| |
| protected long getRemotePositionMs() { |
| return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Long>() { |
| @Override |
| public Long call() { |
| return getMediaRouteController().getPosition(); |
| } |
| }); |
| } |
| |
| protected long getRemoteDurationMs() { |
| return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Long>() { |
| @Override |
| public Long call() { |
| return getMediaRouteController().getDuration(); |
| } |
| }); |
| } |
| |
| protected boolean isPlayingRemotely() { |
| return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| RemoteMediaPlayerController playerController = |
| RemoteMediaPlayerController.getIfExists(); |
| if (playerController == null) return false; |
| MediaRouteController routeController = |
| playerController.getCurrentlyPlayingMediaRouteController(); |
| if (routeController == null) return false; |
| return routeController.isPlaying(); |
| } |
| }); |
| } |
| |
| protected MediaRouteController getMediaRouteController() { |
| return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<MediaRouteController>() { |
| @Override |
| public MediaRouteController call() { |
| RemoteMediaPlayerController playerController = |
| RemoteMediaPlayerController.getIfExists(); |
| assertNotNull("No RemoteMediaPlayerController", playerController); |
| MediaRouteController routeController = |
| playerController.getCurrentlyPlayingMediaRouteController(); |
| assertNotNull("No MediaRouteController", routeController); |
| return routeController; |
| } |
| }); |
| } |
| |
| /* |
| * Functions to find the controls Unfortunately the controls are invisible to the code outside |
| * Blink, so this is highly dependent on the geometry defined in Blink css (see |
| * MediaControls.css & MediaControlsAndroid.css). |
| */ |
| private static final int CONTROLS_HEIGHT = 35; |
| private static final int BUTTON_WIDTH = 35; |
| private static final int CONTROL_BAR_MARGIN = 5; |
| private static final int BUTTON_RIGHT_MARGIN = 9; |
| private static final int PLAY_BUTTON_LEFT_MARGIN = 9; |
| private static final int FULLSCREEN_BUTTON_LEFT_MARGIN = -5; |
| |
| private Rect controlBar(Rect videoRect) { |
| int left = videoRect.left + CONTROL_BAR_MARGIN; |
| int right = videoRect.right - CONTROL_BAR_MARGIN; |
| int bottom = videoRect.bottom - CONTROL_BAR_MARGIN; |
| int top = videoRect.bottom - CONTROLS_HEIGHT; |
| return new Rect(left, top, right, bottom); |
| } |
| |
| private Rect playPauseButton(Rect videoRect) { |
| Rect bar = controlBar(videoRect); |
| int left = bar.left + PLAY_BUTTON_LEFT_MARGIN; |
| int right = left + BUTTON_WIDTH; |
| return new Rect(left, bar.top, right, bar.bottom); |
| } |
| |
| private Rect fullscreenButton(Rect videoRect) { |
| Rect bar = controlBar(videoRect); |
| int right = bar.right - BUTTON_RIGHT_MARGIN; |
| int left = right - BUTTON_WIDTH; |
| return new Rect(left, bar.top, right, bar.bottom); |
| } |
| |
| private Rect castButton(Rect videoRect) { |
| Rect fullscreenButton = fullscreenButton(videoRect); |
| int right = fullscreenButton.left - BUTTON_RIGHT_MARGIN - FULLSCREEN_BUTTON_LEFT_MARGIN; |
| int left = right - BUTTON_WIDTH; |
| return new Rect(left, fullscreenButton.top, right, fullscreenButton.bottom); |
| } |
| |
| private void tapButton(Tab tab, Rect rect) { |
| ContentViewCore core = tab.getContentViewCore(); |
| int clickX = |
| (int) core.getRenderCoordinates().fromLocalCssToPix( |
| ((float) (rect.left + rect.right)) / 2); |
| int clickY = |
| (int) core.getRenderCoordinates().fromLocalCssToPix( |
| ((float) (rect.top + rect.bottom)) / 2) |
| + core.getTopControlsHeightPix(); |
| // Click using a virtual mouse, since a touch may result in a disambiguation pop-up. |
| mouseSingleClickView(getInstrumentation(), tab.getView(), clickX, clickY); |
| } |
| |
| private static void sendMouseAction(Instrumentation instrumentation, int action, long downTime, |
| float x, float y) { |
| long eventTime = SystemClock.uptimeMillis(); |
| MotionEvent.PointerCoords coords[] = new MotionEvent.PointerCoords[1]; |
| coords[0] = new MotionEvent.PointerCoords(); |
| coords[0].x = x; |
| coords[0].y = y; |
| MotionEvent.PointerProperties properties[] = new MotionEvent.PointerProperties[1]; |
| properties[0] = new MotionEvent.PointerProperties(); |
| properties[0].id = 0; |
| properties[0].toolType = MotionEvent.TOOL_TYPE_MOUSE; |
| MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, 1, properties, coords, |
| 0, 0, 0.0f, 0.0f, 0, 0, 0, 0); |
| instrumentation.sendPointerSync(event); |
| instrumentation.waitForIdleSync(); |
| } |
| |
| /** |
| * Sends (synchronously) a single mosue click to an absolute screen coordinates. |
| * |
| * @param instrumentation Instrumentation object used by the test. |
| * @param x Screen absolute x location. |
| * @param y Screen absolute y location. |
| */ |
| private static void mouseSingleClick(Instrumentation instrumentation, float x, float y) { |
| long downTime = SystemClock.uptimeMillis(); |
| sendMouseAction(instrumentation, MotionEvent.ACTION_DOWN, downTime, x, y); |
| sendMouseAction(instrumentation, MotionEvent.ACTION_UP, downTime, x, y); |
| } |
| |
| /** |
| * Sends (synchronously) a single mouse click to the View at the specified coordinates. |
| * |
| * @param instrumentation Instrumentation object used by the test. |
| * @param v The view the coordinates are relative to. |
| * @param x Relative x location to the view. |
| * @param y Relative y location to the view. |
| */ |
| private static void mouseSingleClickView(Instrumentation instrumentation, View v, int x, |
| int y) { |
| int location[] = TestTouchUtils.getAbsoluteLocationFromRelative(v, x, y); |
| int absoluteX = location[0]; |
| int absoluteY = location[1]; |
| mouseSingleClick(instrumentation, absoluteX, absoluteY); |
| } |
| |
| /** |
| * Sends (synchronously) a single mouse click to the center of the View. |
| * |
| * @param instrumentation Instrumentation object used by the test. |
| * @param v The view the coordinates are relative to. |
| */ |
| private static void mouseSingleClickView(Instrumentation instrumentation, View v) { |
| int x = v.getWidth() / 2; |
| int y = v.getHeight() / 2; |
| mouseSingleClickView(instrumentation, v, x, y); |
| } |
| |
| } |