| /* |
| * Copyright (C) 2014 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 android.support.test.espresso.action; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| |
| import android.support.test.espresso.UiController; |
| import android.support.test.espresso.action.MotionEvents.DownResultHolder; |
| |
| import android.os.Build; |
| import android.util.Log; |
| import android.view.MotionEvent; |
| import android.view.ViewConfiguration; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| |
| /** |
| * Executes different click types to given position. |
| */ |
| public enum Tap implements Tapper { |
| SINGLE { |
| @Override |
| public Tapper.Status sendTap(UiController uiController, float[] coordinates, |
| float[] precision) { |
| Tapper.Status stat = sendSingleTap(uiController, coordinates, precision); |
| if (Tapper.Status.SUCCESS == stat) { |
| // Wait until the touch event was processed by the main thread. |
| long singlePressTimeout = (long) (ViewConfiguration.getTapTimeout() * 1.5f); |
| uiController.loopMainThreadForAtLeast(singlePressTimeout); |
| } |
| return stat; |
| } |
| }, |
| LONG { |
| @Override |
| public Tapper.Status sendTap(UiController uiController, float[] coordinates, |
| float[] precision) { |
| checkNotNull(uiController); |
| checkNotNull(coordinates); |
| checkNotNull(precision); |
| |
| MotionEvent downEvent = MotionEvents.sendDown(uiController, coordinates, precision).down; |
| try { |
| // Duration before a press turns into a long press. |
| // Factor 1.5 is needed, otherwise a long press is not safely detected. |
| // See android.test.TouchUtils longClickView |
| long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); |
| uiController.loopMainThreadForAtLeast(longPressTimeout); |
| |
| if (!MotionEvents.sendUp(uiController, downEvent)) { |
| MotionEvents.sendCancel(uiController, downEvent); |
| return Tapper.Status.FAILURE; |
| } |
| } finally { |
| downEvent.recycle(); |
| downEvent = null; |
| } |
| return Tapper.Status.SUCCESS; |
| } |
| }, |
| DOUBLE { |
| @Override |
| public Tapper.Status sendTap(UiController uiController, float[] coordinates, |
| float[] precision) { |
| checkNotNull(uiController); |
| checkNotNull(coordinates); |
| checkNotNull(precision); |
| Tapper.Status stat = sendSingleTap(uiController, coordinates, precision); |
| if (stat == Tapper.Status.FAILURE) { |
| return Tapper.Status.FAILURE; |
| } |
| |
| |
| if (0 < DOUBLE_TAP_MIN_TIMEOUT) { |
| uiController.loopMainThreadForAtLeast(DOUBLE_TAP_MIN_TIMEOUT); |
| } |
| |
| Tapper.Status secondStat = sendSingleTap(uiController, coordinates, precision); |
| |
| if (secondStat == Tapper.Status.FAILURE) { |
| return Tapper.Status.FAILURE; |
| } |
| |
| |
| if (secondStat == Tapper.Status.WARNING || stat == Tapper.Status.WARNING) { |
| return Tapper.Status.WARNING; |
| } else { |
| return Tapper.Status.SUCCESS; |
| } |
| } |
| }; |
| |
| private static final String TAG = Tap.class.getSimpleName(); |
| private static final int DOUBLE_TAP_MIN_TIMEOUT; |
| static { |
| int timeVal = 0; |
| if (Build.VERSION.SDK_INT > 18) { |
| try { |
| Method getDoubleTapMinTimeMethod = ViewConfiguration.class.getDeclaredMethod( |
| "getDoubleTapMinTime"); |
| timeVal = (Integer) getDoubleTapMinTimeMethod.invoke(null); |
| } catch (NoSuchMethodException nsme) { |
| Log.w(TAG, "Expected to find getDoubleTapMinTime", nsme); |
| } catch (InvocationTargetException ite) { |
| Log.w(TAG, "Unable to query double tap min time!", ite); |
| } catch (IllegalAccessException iae) { |
| Log.w(TAG, "Unable to query double tap min time!", iae); |
| } |
| } |
| DOUBLE_TAP_MIN_TIMEOUT = timeVal; |
| } |
| |
| private static Tapper.Status sendSingleTap(UiController uiController, |
| float[] coordinates, float[] precision) { |
| checkNotNull(uiController); |
| checkNotNull(coordinates); |
| checkNotNull(precision); |
| DownResultHolder res = MotionEvents.sendDown(uiController, coordinates, precision); |
| try { |
| if (!MotionEvents.sendUp(uiController, res.down)) { |
| Log.d(TAG, "Injection of up event as part of the click failed. Send cancel event."); |
| MotionEvents.sendCancel(uiController, res.down); |
| return Tapper.Status.FAILURE; |
| } |
| } finally { |
| res.down.recycle(); |
| } |
| return res.longPress ? Tapper.Status.WARNING : Tapper.Status.SUCCESS; |
| } |
| |
| } |