/*
* Copyright (C) 2011 Baidu.com Inc
*
* 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.baidu.cafe.local;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import junit.framework.Assert;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
import android.os.Build;
import android.os.SystemClock;
import android.text.format.Time;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.CheckedTextView;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.ScrollView;
import android.widget.TabWidget;
import android.widget.TextView;
import com.baidu.cafe.CafeTestCase;
import com.baidu.cafe.CafeTestRunner;
import com.baidu.cafe.local.record.ViewRecorder;
import com.baidu.cafe.local.traveler.APPTraveler;
import com.baidu.cafe.local.traveler.Logger;
import com.baidu.cafe.utils.CommandResult;
import com.baidu.cafe.utils.ReflectHelper;
import com.baidu.cafe.utils.ShellExecute;
import com.baidu.cafe.utils.Strings;
import com.jayway.android.robotium.solo.Solo;
import com.jayway.android.robotium.solo.WebElement;
import dalvik.system.DexFile;
/**
* It can help you as below.
*
* 1.get or set a object's private property and invoke a object's private
* function
*
* 2.find view by text or resid
*
* 3.get views which is generated dynamically
*
* 4.record human operations and generate Cafe codes
*
* @author luxiaoyu01@baidu.com
* @date 2011-5-17
* @version
* @todo
*/
public class LocalLib extends Solo {
public final static int SEARCHMODE_COMPLETE_MATCHING = 1;
public final static int SEARCHMODE_DEFAULT = 1;
public final static int SEARCHMODE_INCLUDE_MATCHING = 2;
private final static int WAIT_INTERVAL = 1000;
private final static int MINISLEEP = 100;
private final static int SMALL_WAIT_TIMEOUT = 10000;
public static String mTestCaseName = null;
public static String mPackageName = null;
public static int[] mTheLastClick = new int[2];
public static Instrumentation mInstrumentation;
public RecordReplay recordReplay = null;
private boolean mHasBegin = false;
private ArrayList<View> mViews = null;
private Activity mActivity;
private Context mContext = null;
public LocalLib(Instrumentation instrumentation, Activity activity) {
super(instrumentation, activity);
mInstrumentation = instrumentation;
mActivity = activity;
mContext = instrumentation.getContext();
mTheLastClick[0] = -1;
mTheLastClick[1] = -1;
recordReplay = new RecordReplay();
}
private static void print(String message) {
if (Log.IS_DEBUG) {
Log.i("LocalLib", message);
}
}
/**
* invoke object's private method
*
* @param owner
* : target object
* @param classLevel
* : 0 means itself, 1 means it's father, and so on...
* @param methodName
* : name of the target method
* @param parameterTypes
* : types of the target method's parameters
* @param parameters
* : parameters of the target method
* @return result of invoked method
*
* @throws NoSuchMethodException
* @throws SecurityException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public Object invoke(Object owner, String targetClass, String methodName,
Class<?>[] parameterTypes, Object[] parameters) throws SecurityException,
NoSuchMethodException, IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
return ReflectHelper.invoke(owner, targetClass, methodName, parameterTypes, parameters);
}
/**
* Set object's field with custom value even it's private.
*
* @param owner
* : target object
* @param classLevel
* : 0 means itself, 1 means it's father, and so on...
* @param fieldName
* : name of the target field
* @param value
* : new value of the target field
* @throws NoSuchFieldException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public void setField(Object owner, String targetClass, String fieldName, Object value)
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
ReflectHelper.setField(owner, targetClass, fieldName, value);
}
/**
* get object's private property
*
* @param owner
* : target object
* @param classLevel
* : 0 means itself, 1 means it's father, and so on...
* @param fieldName
* : name of the target field
* @return value of the target field
* @throws NoSuchFieldException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public Object getField(Object owner, String targetClass, String fieldName)
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
return ReflectHelper.getField(owner, targetClass, fieldName);
}
/**
* get object's private property by type
*
* @param owner
* target object
* @param classLevel
* 0 means itself, 1 means it's father, and so on...
* @param typeString
* e.g. java.lang.String
* @return ArrayList<String> of property's name
*/
public ArrayList<String> getFieldNameByType(Object owner, String targetClass, Class<?> type) {
return ReflectHelper.getFieldNameByType(owner, targetClass, type);
}
/**
* @param owner
* target object
* @param classLevel
* 0 means itself, 1 means it's father, and so on...
* @param valueType
* e.g. String.class
* @param value
* value of the target fields
* @return ArrayList<String> of property's name
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public ArrayList<String> getFieldNameByValue(Object owner, String targetClass,
Class<?> valueType, Object value) throws IllegalArgumentException,
IllegalAccessException {
return ReflectHelper.getFieldNameByValue(owner, targetClass, valueType, value);
}
/**
* hook listeners on all views for generating Cafe code automatically
*/
public void beginRecordCode() {
new ViewRecorder(this).beginRecordCode();
}
/**
* Get listener from view. e.g. (OnClickListener) getListener(view,
* "mOnClickListener"); means get click listener. Listener is a private
* property of a view, that's why this function is written.
*
* @param view
* target view
* @param targetClass
* the class which fieldName belong to
* @param fieldName
* target listener. e.g. mOnClickListener, mOnLongClickListener,
* mOnTouchListener, mOnKeyListener
* @return listener object; null means no listeners has been found
*/
public Object getListener(View view, Class<?> targetClass, String fieldName) {
int level = countLevelFromViewToFather(view, targetClass);
if (-1 == level) {
return null;
}
try {
if (!(view instanceof AdapterView) && Build.VERSION.SDK_INT > 14) {// API Level 14: Android 4.0
Object mListenerInfo = ReflectHelper.getField(view, targetClass.getName(),
"mListenerInfo");
return null == mListenerInfo ? null : ReflectHelper.getField(mListenerInfo, null,
fieldName);
} else {
return ReflectHelper.getField(view, targetClass.getName(), fieldName);
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
// eat it
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* This method is used to replace listener.setOnListener().
* listener.setOnListener() is probably overrided by application, so its
* behavior can not be expected.
*
* @param view
* @param targetClass
* @param fieldName
* @param value
*/
public void setListener(View view, Class<?> targetClass, String fieldName, Object value) {
int level = countLevelFromViewToFather(view, targetClass);
if (-1 == level) {
return;
}
try {
if (!(view instanceof AdapterView) && Build.VERSION.SDK_INT > 14) {// API
// Level:
// 14.
// Android
// 4.0
Object mListenerInfo = ReflectHelper.getField(view, targetClass.getName(),
"mListenerInfo");
if (null == mListenerInfo) {
return;
}
ReflectHelper.setField(mListenerInfo, null, fieldName, value);
} else {
ReflectHelper.setField(view, targetClass.getName(), fieldName, value);
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
// eat it
// e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* find parent until parent is father or java.lang.Object(to the end)
*
* @param view
* target view
* @param father
* target father
* @return positive means level from father; -1 means not found
*/
public int countLevelFromViewToFather(View view, Class<?> father) {
if (null == view) {
return -1;
}
int level = 0;
Class<?> originalClass = view.getClass();
// find its parent
while (true) {
if (originalClass.equals(Object.class)) {
return -1;
} else if (originalClass.equals(father)) {
return level;
} else {
level++;
originalClass = originalClass.getSuperclass();
}
}
}
public String getViewText(View view) {
try {
Method method = view.getClass().getMethod("getText");
return (String) (method.invoke(view));
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
// eat it
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassCastException e) {
// eat it
}
return "";
}
/**
* find views via view's text, it only needs part of target view's text
*
* @param text
* the text of the view
* @return a ArrayList<View> contains views found
*/
@Deprecated
public ArrayList<View> findViewsByText(String text) {
ArrayList<View> allViews = getViews();
ArrayList<View> views = new ArrayList<View>();
int viewNumber = allViews.size();
for (int i = 0; i < viewNumber; i++) {
View view = allViews.get(i);
String t = getViewText(view);
if (t.indexOf(text) != -1) {
views.add(view);
}
}
return views;
}
/**
* call this function before new views appear
*/
public void getNewViewsBegin() {
mViews = getViews();
mHasBegin = true;
}
/**
* call this function after new views appear
*
* @return A ArrayList<View> contains views which are new. Null means no new
* views
*/
public ArrayList<View> getNewViewsEnd() {
if (!mHasBegin) {
return null;
}
ArrayList<View> views = getViews();
ArrayList<View> diffViews = new ArrayList<View>();
int sizeOfNewViews = views.size();
int sizeOfOldViews = mViews.size();
boolean duplicate;
for (int i = 0; i < sizeOfNewViews; i++) {
duplicate = false;
for (int j = 0; j < sizeOfOldViews; j++) {
if (views.get(i).equals(mViews.get(j))) {
duplicate = true;
}
}
if (!duplicate) {
diffViews.add(views.get(i));
}
}
return diffViews;
}
public String getRIdNameByValue(String packageName, int value) {
Class<?> idClass = Strings.getRClass(packageName, "id");
if (null == idClass) {
return "";
}
try {
for (Field field : idClass.getDeclaredFields()) {
Integer id = (Integer) field.get(idClass.newInstance());
if (id == value) {
return field.getName();
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return "";
}
/**
* get R.string.yourTargetString from test package
*
* @param stringName
* name of your target string
* @return string value
*/
public String getTestRString(String stringName) {
return mContext.getResources().getString(
Strings.getRStringId(mContext.getPackageName(), stringName));
}
/**
* get R.string.yourTargetString from tested package
*
* @param stringName
* name of your target string
* @return string value
*/
@Deprecated
public String getTestedRString(String stringName) {
return getString(Strings.getRStringId(mActivity.getPackageName(), stringName));
}
/**
* you can use this function when getActivtiy is hanging. When you want to
* reinit solo you should recall public void init(Activity macy)
*
* @param activityName
* example: the activity "TestAcy" you wanted, the param is
* "TestAcy.class.getName()"
* @return activity
*/
public Activity getActivityAsync(String activityName) {
return mInstrumentation.waitForMonitor(mInstrumentation.addMonitor(activityName, null,
false));
}
/**
* run shell command with tested app's permission
*
* @param command
* e.g. new String[]{"ls", "-l"}
* @param directory
* e.g. "/sdcard"
* @return the result string of the command
*/
public static CommandResult executeOnDevice(String command, String directory) {
return new ShellExecute().execute(command, directory);
}
/**
* run shell command with tested app's permission
*
* @param command
* e.g. new String[]{"ls", "-l"}
* @param directory
* e.g. "/sdcard"
* @param timeout
* Millis. e.g. 5000 means 5s
*
* @return the result string of the command
*/
public static CommandResult executeOnDevice(String command, String directory, long timeout) {
return new ShellExecute().execute(command, directory, timeout);
}
/**
* Waits for a view to vanish
*
* @param resId
* the id you see in hierarchy . for example in Launcher
* "id/workspace" timeout is default SMALL_WAIT_TIMEOUT scroll is
* default true only visible is default true
* @return true we get it
*/
public boolean waitForViewVanishById(String resId) {
return waitForViewVanishById(resId, 0, SMALL_WAIT_TIMEOUT, true);
}
/**
* Waits for a view to vanish
*
* @param resId
* the id you see in hierarchy . for example in Launcher
* "id/workspace" timeout is default SMALL_WAIT_TIMEOUT scroll is
* default true only visible is default true
* @param index
* specify resId with a given index.
* @return true we get it
*/
public boolean waitForViewVanishById(String resId, int index) {
return waitForViewVanishById(resId, index, SMALL_WAIT_TIMEOUT, true);
}
/**
* Waits for a view to vanish
*
* @param resId
* the id you see in hierarchy. for example in Launcher
* "id/workspace"
* @param index
* specify resId with a given index.
* @param timeout
* the delay milliseconds scroll is default true only visible is
* default true
* @return true we get it
*/
public boolean waitForViewVanishById(String resId, int index, long timeout) {
return waitForViewVanishById(resId, index, timeout, true);
}
/**
* Waits for a view vanished
*
* @param resName
* the id you see in hierarchy. for example in Launcher
* "id/workspace"
* @param timeout
* the delay milliseconds
* @param scroll
* true you want to scroll
* @param onlyvisible
* true we only deal with the view visible
* @return true we get it
*/
public boolean waitForViewVanishById(String resName, int index, long timeout, boolean scroll) {
Long end = System.currentTimeMillis() + timeout;
while (true) {
if (System.currentTimeMillis() > end) {
return false;
}
if (!waitforViewByResName(resName, index, WAIT_INTERVAL, scroll)) {
return true;
}
}
}
/**
* Waits for a text to vanish.
*
* @param text
* the text to wait for
* @return {@code true} if text is shown and {@code false} if it is not
* shown before the timeout
*
*/
public boolean waitForTextVanish(String text) {
return waitForTextVanish(text, 0, 8000, false);
}
/**
* Waits for a text to vanish.
*
* @param text
* the text to wait for
* @param minimumNumberOfMatches
* the minimum number of matches that are expected to be shown.
* {@code 0} means any number of matches
* @return {@code true} if text is shown and {@code false} if it is not
* shown before the timeout
*
*/
public boolean waitForTextVanish(String text, int minimumNumberOfMatches) {
return waitForTextVanish(text, minimumNumberOfMatches, 8000, false);
}
/**
* Waits for a text to vanish.
*
* @param text
* the text to wait for
* @param minimumNumberOfMatches
* the minimum number of matches that are expected to be shown.
* {@code 0} means any number of matches
* @param timeout
* the amount of time in milliseconds to wait
* @return {@code true} if text is shown and {@code false} if it is not
* shown before the timeout
*
*/
public boolean waitForTextVanish(String text, int minimumNumberOfMatches, long timeout) {
return waitForTextVanish(text, minimumNumberOfMatches, timeout, false);
}
/**
* Waits for a text to vanish.
*
* @param text
* the text to wait for
* @param minimumNumberOfMatches
* the minimum number of matches that are expected to be shown.
* {@code 0} means any number of matches
* @param timeout
* the amount of time in milliseconds to wait
* @param scroll
* {@code true} if scrolling should be performed
* @return {@code true} if text is shown and {@code false} if it is not
* shown before the timeout
*
*/
public boolean waitForTextVanish(String text, int minimumNumberOfMatches, long timeout,
boolean scroll) {
Long end = System.currentTimeMillis() + timeout;
while (true) {
if (System.currentTimeMillis() > end) {
return false;
}
if (!waitForText(text, minimumNumberOfMatches, WAIT_INTERVAL, scroll)) {
return true;
}
}
}
/**
* Waits for value from WaitCallBack.getActualVaule() equaling to expect
* value until time is out.
*
* @param expect
* @param callBack
* @return true: WaitCallBack.getActualVaule() equals to expectation; false:
* WaitCallBack.getActualVaule() differs from expectation
*/
public boolean waitEqual(String expect, WaitCallBack callBack) {
return waitEqual(expect, callBack, 10000);
}
public interface WaitCallBack {
String getActualValue();
}
/**
* Waits for value from WaitCallBack.getActualVaule() equaling to expect
* value until time is out.
*
* @param expect
* @param callBack
* @param timeout
* @return true: WaitCallBack.getActualVaule() equals to expectation; false:
* WaitCallBack.getActualVaule() differs from expectation
*/
public boolean waitEqual(String expect, WaitCallBack callBack, long timeout) {
Long end = System.currentTimeMillis() + timeout;
while (true) {
if (System.currentTimeMillis() > end) {
return false;
}
if (expect.equals(callBack.getActualValue())) {
return true;
}
}
}
/**
* zoom screen
*
* @param start
* the start position e.g. new int[]{0,0,1,2}; means two pointers
* start at {0,0} and {1,2}
* @param end
* the end position e.g. new int[]{100,110,200,220}; means two
* pointers end at {100,110} and {200,220}
*/
public void zoom(int[] start, int[] end) {
sendMultiTouchMotionEvent(2, start, end, 10, 0, 0, 0);
}
/**
* send a Multi-Touch Motion Event
*
* @param pointerNumber
* the number of pointer
* @param start
* the start position e.g. new int[]{0,0,1,2}; means two pointers
* start at {0,0} and {1,2}
* @param end
* the end position e.g. new int[]{100,110,200,220}; means two
* pointers end at {100,110} and {200,220}
* @param step
* the move step
* @param downDelay
* the delay after down event was sent
* @param moveDelay
* the delay after each move event was sent
* @param upDelay
* the delay before sending up event
*/
@SuppressLint("Recycle")
@SuppressWarnings("deprecation")
public void sendMultiTouchMotionEvent(int pointerNumber, int[] start, int[] end, int step,
int downDelay, int moveDelay, int upDelay) {
double[] delta = new double[pointerNumber * 2];
int[] pointerIds = new int[pointerNumber];
PointerCoords[] pointerPositions = new PointerCoords[pointerNumber];
int temp = 0;
for (int i = 0; i < pointerNumber; i++) {
pointerPositions[i] = new PointerCoords();
pointerPositions[i].pressure = 1.0f;
temp = i * 2;
delta[temp] = (end[temp] - start[temp]) / (double) step;
pointerPositions[i].x = start[temp];
temp++;
delta[temp] = (end[temp] - start[temp]) / (double) step;
pointerPositions[i].y = start[temp];
pointerIds[i] = i;
}
long myTime = SystemClock.uptimeMillis();
mInstrumentation.sendPointerSync(MotionEvent.obtain(myTime, myTime,
MotionEvent.ACTION_DOWN, pointerNumber, pointerIds, pointerPositions, 0, 0.1f,
0.1f, 0, 0, 0, 0));
this.sleep(downDelay);
for (int i = 0; i < step; i++) {
for (int j = 0; j < pointerNumber; j++) {
temp = j * 2;
pointerPositions[j].x = (float) (start[temp] + delta[temp] * (i + 1));
temp++;
pointerPositions[j].y = (float) (start[temp] + delta[temp] * (i + 1));
}
myTime = SystemClock.uptimeMillis();
mInstrumentation.sendPointerSync(MotionEvent.obtain(myTime, myTime,
MotionEvent.ACTION_MOVE, pointerNumber, pointerIds, pointerPositions, 0, 0.1f,
0.1f, 0, 0, 0, 0));
this.sleep(moveDelay);
}
this.sleep(upDelay);
myTime = SystemClock.uptimeMillis();
mInstrumentation.sendPointerSync(MotionEvent.obtain(myTime, myTime, MotionEvent.ACTION_UP,
pointerNumber, pointerIds, pointerPositions, 0, 0.1f, 0.1f, 0, 0, 0, 0));
}
/**
* set CheckedTextView checked or not
*
* @param index
* @param checked
* @return if set ok return true
*/
public boolean setCheckedTextView(int index, boolean checked) {
ArrayList<CheckedTextView> checkedTextViews = getCurrentViews(CheckedTextView.class);
if (index < checkedTextViews.size()) {
final CheckedTextView checkedTextView = checkedTextViews.get(index);
final boolean fChecked = checked;
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
checkedTextView.setChecked(fChecked);
}
});
return true;
}
return false;
}
/**
* Returns an ArrayList with the Tab located in the current activity
*
* @return ArrayList of the Tabs contained in the current activity
*/
public ArrayList<TabWidget> getCurrentTabs() {
ArrayList<TabWidget> tabList = new ArrayList<TabWidget>();
ArrayList<View> viewList = getCurrentViews();
for (View view : viewList) {
if (view instanceof android.widget.TabWidget) {
tabList.add((TabWidget) view);
}
}
return tabList;
}
/**
* This method returns a tab with a certain index.
*
* @param index
* the index of the Tab
* @return the tab with the specific index
*/
public TabWidget getTab(int index) {
ArrayList<TabWidget> tabList = getCurrentTabs();
TabWidget tab = null;
try {
tab = tabList.get(index);
} catch (Throwable e) {
}
return tab;
}
/**
* Click on a tab with a certain item
*
* @param index
* the index of the tab
* @param item
* the item of the tab will be clicked
*/
public void clickOnTab(int index, int item) {
TabWidget tab = null;
try {
tab = getTab(index);
Assert.assertTrue("Tab is null", tab != null);
clickOnView(tab.getChildAt(item));
} catch (IndexOutOfBoundsException e) {
Assert.assertTrue("Index is not valid", false);
}
}
/**
* click on screen, the point is on the right
*/
public void clickOnScreenRight() {
float x = getDisplayX();
float y = getDisplayY();
clickOnScreen(x / 4, y / 2);
}
/**
* click on screen, the point is on the left
*/
public void clickOnScreenLeft() {
float x = getDisplayX();
float y = getDisplayY();
clickOnScreen(x - x / 4, y / 2);
}
/**
* click on screen, the point is on the up
*/
public void clickOnScreenUp() {
float x = getDisplayX();
float y = getDisplayY();
clickOnScreen(x / 2, y / 4);
}
/**
* click on screen, the point is on the down
*/
public void clickOnScreenDown() {
float x = getDisplayX();
float y = getDisplayY();
clickOnScreen(x / 2, y - y / 4);
}
/**
* drag on screen to right
*/
public void dragScreenToLeft(int stepCount) {
float x = getDisplayX();
float y = getDisplayY();
drag(x - x / 4, x / 4, y / 2, y / 2, stepCount);
}
/**
* drag on screen to Left
*/
public void dragScreenToRight(int stepCount) {
float x = getDisplayX();
float y = getDisplayY();
drag(x / 4, x - x / 4, y / 2, y / 2, stepCount);
}
/**
* drag on screen to up
*/
public void dragScreenToUp(int stepCount) {
float x = getDisplayX();
float y = getDisplayY();
drag(x / 2, x / 2, y - y / 4, y / 4, stepCount);
}
/**
* drag on screen to Down
*/
public void dragScreenToDown(int stepCount) {
float x = getDisplayX();
float y = getDisplayY();
drag(x / 2, x / 2, y / 4, y - y / 4, stepCount);
}
/**
* wait for a specified view
*
* @param resName
* the id you see in hierarchy. for example in Launcher
* "id/workspace" timeout is default 3000 scroll is default true
* onlyVisible is default true
* @return true we get it
*/
public boolean waitforViewByResName(String resName) {
return waitforViewByResName(resName, 0, SMALL_WAIT_TIMEOUT, true);
}
/**
* wait for a specified view
*
* @param resName
* the name you see in hierarchy. for example in Launcher
* "id/workspace" timeout is default 3000 scroll is default true
* onlyVisible is default true
* @return true we get it
*/
public boolean waitforViewByResName(String resName, int index) {
return waitforViewByResName(resName, index, SMALL_WAIT_TIMEOUT, true);
}
/**
* wait for a specified view
*
* @param resName
* the name you see in hierarchy. for example in Launcher
* "id/workspace"
* @param timeout
* the delay millisecond scroll is default true onlyVisible is
* default true
* @return true we get it
*/
public boolean waitforViewByResName(String resName, int index, long timeout) {
return waitforViewByResName(resName, index, timeout, true);
}
/**
* wait for a specified view
*
* @param resName
* the id you see in hierarchy. for example in Launcher
* "id/workspace"
* @param timeout
* the delay millisecond
* @param scroll
* true you want to scroll
* @param onlyVisible
* true we only deal with the view visible
* @return true we get it
*/
public boolean waitforViewByResName(String resName, int index, long timeout, boolean scroll) {
final long endTime = System.currentTimeMillis() + timeout;
while (System.currentTimeMillis() < endTime) {
View view = getViewByResName(resName, 0);
if (view != null) {
return true;
}
try {
Object down = ReflectHelper.getField(scroller, null, "DOWN");
// mScroller.scroll(mScroller.DOWN)
if (scroll
&& !(Boolean) ReflectHelper.invoke(scroller, null, "scroll",
new Class[] { int.class }, new Object[] { down })) {
continue;
}
ReflectHelper.invoke(sleeper, null, "sleep", new Class[] {}, new Object[] {});// mSleeper.sleep();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
return false;
}
/**
* Click the first specified view by resource name.
*
* @param resName
* the id you see in hierarchy. for example in Launcher
* "id/workspace"
* @return true we got it
*/
public void clickViewByResName(String resName) {
clickViewByResName(resName, 0);
}
/**
* Click a specified view by resource name.
*
* @param resName
* the id you see in hierarchy. for example in Launcher
* "id/workspace"
* @param index
* Clicks on an resId with a given index.
*/
public void clickViewByResName(String resName, int index) {
final View view = getViewByResName(resName, index);
Assert.assertTrue("null == view at" + Log.getThreadInfo(), null != view);
clickOnView(view);
}
/**
* Get the first view by resource name
*
* @param resName
* resource name
* @return null means not found
*/
public View getViewByResName(String resName) {
return getViewByResName(resName, 0);
}
/**
* @param resId
* @param index
* @return null means not found
*/
public View getViewByResName(String resId, int index) {
ArrayList<View> views = getCurrentViews();
int count = 0;
for (View view : views) {
if (getResName(view).equals(resId)) {
count++;
}
if (count - 1 == index) {
return view;
}
}
return null;
}
/**
* @param view
* @return empty string means no id
*/
private String getResName(View view) {
int resid = view.getId();
if (View.NO_ID == resid) {
return "";
}
try {
// view.getResources().getResourceName(resid); sometimes throws java.lang.NullPointerException
String resIdString = getCurrentActivity().getResources().getResourceName(resid);
return resIdString.split(":")[1].trim();
} catch (Exception e) {
return "";
}
}
/**
* This api is only for ViewRecorder not for user.
*
* @param view
* @return -1 means no res id or view is not found
*/
public int getResIdIndex(View targetView) {
int index = -1;
String resId = getResName(targetView);
if ("".equals(resId)) {
return index;
}
for (View view : getCurrentViews()) {
if (getResName(view).equals(resId)) {
index++;
}
if (view.equals(targetView)) {
return index;
}
}
return -1;
}
/**
* Search text from parent view
*
* @param parent
* parent view
* @param text
* text you want to search
* @param searchMode
* include SEARCHMODE_COMPLETE_MATCHING, SEARCHMODE_DEFAULT and
* SEARCHMODE_INCLUDE_MATCHING
* @return true means found otherwise false
*/
@SuppressWarnings("unchecked")
public boolean searchTextFromParent(View parent, String text, int searchMode) {
try {
ArrayList<TextView> textViews = (ArrayList<TextView>) ReflectHelper.invoke(viewFetcher,
null, "getCurrentViews", new Class[] { Class.class, View.class }, new Object[] {
TextView.class, parent });// mViewFetcher.getCurrentViews(TextView.class, parent);
for (TextView textView : textViews) {
switch (searchMode) {
case SEARCHMODE_COMPLETE_MATCHING:
if (textView.getText().equals(text)) {
return true;
}
break;
case SEARCHMODE_INCLUDE_MATCHING:
if (textView.getText().toString().contains(text)) {
return true;
}
break;
default:
Assert.assertTrue("Unknown searchMode!" + Log.getThreadInfo(), false);
}
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return false;
}
/**
* Take an activity snapshot named 'timestamp', and you can get it by adb
* pull /data/data/<packagename>/xxxxx.jpg .
*/
public void screenShotNamedTimeStamp() {
screenShot(getTimeStamp());
}
public void screenShotNamedCaseName(String suffix) {
screenShot(mTestCaseName + "_" + suffix);
}
public void screenShotNamedSuffix(String suffix) {
screenShot(getTimeStamp() + "_" + suffix);
}
private static String getTimeStamp() {
Time localTime = new Time("Asia/Hong_Kong");
localTime.setToNow();
return localTime.format("%Y-%m-%d_%H-%M-%S");
}
public void screenShot(final String fileName) {
String packagePath = CafeTestCase.mTargetFilesDir;
File targetFilesDir = new File(packagePath);
if (!targetFilesDir.exists()) {
targetFilesDir.mkdir();
}
// chmod for adb pull /data/data/<package_name>/files .
// executeOnDevice("chmod 777 " + packagePath, "/", 200);
runOnMainSync(new Runnable() {
public void run() {
takeActivitySnapshot(fileName + ".jpg");
}
});
}
public void takeWebViewSnapshot(final WebView webView, final String savePath) {
// SnapshotHelper.takeWebViewSnapshot(webView, savePath);
SnapshotHelper.dumpPic(webView, savePath);
}
/**
* screencap can only be invoked from shell not app process
*/
// public void screencap(String fileName) {
// String path = String.format("screencap -p %s/%s.png",
// mInstrumentation.getTargetContext()
// .getFilesDir().toString(), fileName);
// executeOnDevice(path, "/system/bin");
// }
/**
* Take an activity snapshot.
*/
public void takeActivitySnapshot(final String path) {
View decorView = getRecentDecorView();
/*
* try { invokeObjectMethod(this, 2, "wrapAllGLViews", new Class[] {
* View.class }, new Object[] { decorView });//
* solo.wrapAllGLViews(decorView); } catch (SecurityException e) {
* e.printStackTrace(); } catch (IllegalArgumentException e) {
* e.printStackTrace(); } catch (NoSuchMethodException e) {
* e.printStackTrace(); ReflectHelper.listObject(this, 2); } catch
* (IllegalAccessException e) { e.printStackTrace(); } catch
* (InvocationTargetException e) { e.printStackTrace(); }
*/
SnapshotHelper.takeViewSnapshot(decorView, path);
}
public View getRecentDecorView() {
View[] views = getWindowDecorViews();
if (null == views || 0 == views.length) {
print("0 == views.length at getRecentDecorView");
return null;
}
View recentDecorview = getRecentDecorView(views);
if (null == recentDecorview) {
// print("null == rview; use views[0]: " + views[0]);
recentDecorview = views[0];
}
return recentDecorview;
}
/**
* get all class names from a package via its dex file
*
* @param packageName
* e.g. "com.baidu.cafe"
* @return names of classes
*/
public ArrayList<String> getAllClassNamesFromPackage(String packageName) {
ArrayList<String> classes = new ArrayList<String>();
try {
String path = mContext.getPackageManager().getApplicationInfo(packageName,
PackageManager.GET_META_DATA).sourceDir;
DexFile dexfile = new DexFile(path);
Enumeration<String> entries = dexfile.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement();
if (name.indexOf('$') == -1) {
classes.add(name);
}
}
} catch (NameNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
public void hideInputMethod() {
for (EditText editText : getCurrentViews(EditText.class)) {
hideInputMethod(editText);
}
}
public void hideInputMethod(EditText editText) {
InputMethodManager inputMethodManager = (InputMethodManager) mContext
.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}
public void showInputMethod(EditText editText) {
InputMethodManager inputMethodManager = (InputMethodManager) mContext
.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInputFromWindow(editText.getWindowToken(),
InputMethodManager.SHOW_FORCED, 0);
}
public ActivityInfo[] getActivitiesFromPackage(String packageName) {
ActivityInfo[] activities = null;
try {
activities = mContext.getPackageManager().getPackageInfo(packageName,
PackageManager.GET_ACTIVITIES).activities;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return activities;
}
/**
* Returns the WindorDecorViews shown on the screen
*
* @return the WindorDecorViews shown on the screen
*/
public View[] getWindowDecorViews() {
try {
return (View[]) ReflectHelper.invoke(viewFetcher, null, "getWindowDecorViews",
new Class[] {}, new Object[] {});// mViewFetcher.getActiveDecorView();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
/**
* Returns the most recent DecorView
*
* @param views
* the views to check
* @return the most recent DecorView
*/
public View getRecentDecorView(View[] views) {
try {
return (View) ReflectHelper.invoke(viewFetcher, null, "getRecentDecorView",
new Class[] { View[].class }, new Object[] { views });
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
/**
* print FPS of current activity at logcat with TAG FPS
*/
public void traceFPS() {
FPSTracer.trace(this);
}
/**
* count how many bytes from tcp app received until now
*
* @param packageName
* @return
*/
public static int getPackageRcv(String packageName) {
return NetworkUtils.getPackageRcv(packageName);
}
/**
* count how many bytes from tcp app sent until now
*
* @param packageName
* @return
*/
public static int getPackageSnd(String packageName) {
return NetworkUtils.getPackageSnd(packageName);
}
public String getAppNameByPID(int pid) {
ActivityManager manager = (ActivityManager) mInstrumentation.getTargetContext()
.getSystemService(Context.ACTIVITY_SERVICE);
for (RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
return "";
}
public float getDisplayX() {
DisplayMetrics dm = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);
return dm.widthPixels;
}
public float getDisplayY() {
DisplayMetrics dm = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);
return dm.heightPixels;
}
public <T extends View> ArrayList<T> removeInvisibleViews(ArrayList<T> viewList) {
ArrayList<T> tmpViewList = new ArrayList<T>(viewList.size());
for (T view : viewList) {
if (view != null && view.isShown() && isInScreen(view) && !isSize0(view)) {
tmpViewList.add(view);
}
}
return tmpViewList;
}
boolean hasFocus = false;
/**
* set focus on a view
*
* @param view
* @return
*/
public boolean requestFocus(final View view) {
if (null == view) {
return false;
}
runOnMainSync(new Runnable() {
public void run() {
view.setFocusable(true);
view.setFocusableInTouchMode(true);
hasFocus = view.requestFocus();
}
});
return hasFocus;
}
/**
* These classes can not be used directly, only their class names can be
* used.Because of com.android.internal.view.menu.MenuView.ItemView can not
* be compiled with sdk.
*/
final static String[] MENU_INTERFACES = new String[] { "android.view.MenuItem",
"com.android.internal.view.menu.MenuView" };
/**
* judge a view wether is a menu.
*
* @param view
* @return true means it is a menu, otherwise return fasle
*/
public boolean isMenu(View view) {
return ReflectHelper.getInterfaces(view, MENU_INTERFACES).size() > 0 ? true : false;
}
public <T extends View> ArrayList<T> getCurrentViews(Class<T> classToFilterBy, boolean visible) {
ArrayList<T> views = getCurrentViews(classToFilterBy);
return visible ? removeInvisibleViews(views) : views;
}
@SuppressWarnings("unchecked")
public <T extends View> ArrayList<T> getViews(Class<T> classToFilterBy,
boolean onlySufficientlyVisible) {
ArrayList<T> targetViews = new ArrayList<T>();
try {
ArrayList<View> views = (ArrayList<View>) ReflectHelper.invoke(viewFetcher, null,
"getViews", new Class[] { View.class, boolean.class }, new Object[] { null,
onlySufficientlyVisible });// viewFetcher.getViews(null, false);
for (View view : views) {
if (view != null && classToFilterBy.isAssignableFrom(view.getClass())) {
targetViews.add(classToFilterBy.cast(view));
}
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return targetViews;
}
public void performClick(final View view, final boolean longClick) {
Assert.assertTrue("null == view at" + Log.getThreadInfo(), null != view);
view.post(new Runnable() {
public void run() {
int[] xy = getViewCenter(view);
try {
boolean ret = false;
if (longClick) {
ret = view.performLongClick();
} else {
ret = view.performClick();
}
print("clickViaPerformClick:" + ret + " " + xy[0] + "," + xy[1] + " " + view);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public static int[] getViewCenter(View view) {
if (null == view) {
return new int[] { -1, -1 };
}
int[] xy = new int[2];
view.getLocationOnScreen(xy);
float x = xy[0] + (view.getWidth() / 2.0f);
float y = xy[1] + (view.getHeight() / 2.0f);
return new int[] { (int) x, (int) y };
}
/**
* Sets an {@code EditText} text. This method is protected by assert.
*
* @param index
* the index of the {@code EditText}
* @param text
* the text that should be set
* @param keepPreviousText
* true means append text after old text
*/
public void enterText(int index, final String text, final boolean keepPreviousText) {
ArrayList<EditText> editTexts = removeInvisibleViews(getCurrentViews(EditText.class));
Assert.assertTrue(
String.format("editTexts.size()[%s] < index[%s]", editTexts.size(), index)
+ Log.getThreadInfo(), editTexts.size() > index);
final EditText editText = editTexts.get(index);
Assert.assertTrue("null == editText [" + index + "]", null != editText);
Assert.assertTrue("EditText is not enabled [" + index + "]", editText.isEnabled());
final String previousText = editText.getText().toString();
runOnMainSync(new Runnable() {
public void run() {
editText.setInputType(0);
editText.performClick();
if (keepPreviousText) {
editText.setText(previousText + text);
} else {
editText.setText(text);
}
editText.setCursorVisible(false);
}
});
sleep(500);
// showInputMethod(editText);
}
public void runOnMainSync(Runnable r) {
mInstrumentation.runOnMainSync(r);
}
public void runOnUiThread(Runnable r) {
getCurrentActivity().runOnUiThread(r);
}
public Instrumentation getInstrumentation() {
return mInstrumentation;
}
/**
* @param R
* @return
*/
public View getViewByRString(String R) {
Class<?> idClass = Strings.getRClass(mActivity.getPackageName(), "id");
if (null == idClass) {
return null;
}
try {
Integer id = (Integer) idClass.getDeclaredField(R).get(idClass.newInstance());
if (null == id) {
return null;
}
// getCurrentActivity().findViewById(id) dose not work
return getRecentDecorView().findViewById(id);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return null;
}
public View getFocusView(ArrayList<View> views) {
for (View view : views) {
if (view.isFocused()) {
return view;
}
}
return null;
}
public boolean isInScreen(View view) {
int[] location = new int[2];
view.getLocationOnScreen(location);
int leftX = location[0];
int righX = location[0] + view.getWidth();
int leftY = location[1];
int righY = location[1] + view.getHeight();
return righX < 0 || leftX > getDisplayX() || righY < 0 || leftY > getDisplayY() ? false
: true;
}
public <T extends View> ArrayList<T> removeOutOfScreenViews(ArrayList<T> viewList) {
ArrayList<T> views = new ArrayList<T>(viewList.size());
for (T view : viewList) {
if (view != null && isInScreen(view)) {
views.add(view);
}
}
return views;
}
/**
* judge a view wether be covered
*
* @param view
* @return
*/
public boolean isViewCovered(final View view) {
View currentView = view;
Rect currentViewRect = new Rect();
boolean partVisible = currentView.getGlobalVisibleRect(currentViewRect);
boolean totalHeightVisible = (currentViewRect.bottom - currentViewRect.top) >= view
.getMeasuredHeight();
boolean totalWidthVisible = (currentViewRect.right - currentViewRect.left) >= view
.getMeasuredWidth();
boolean totalViewVisible = partVisible && totalHeightVisible && totalWidthVisible;
// if any part of the view is clipped by any of its parents,return true
if (!totalViewVisible) {
return true;
}
while (currentView.getParent() instanceof ViewGroup) {
ViewGroup currentParent = (ViewGroup) currentView.getParent();
// if the parent of view is not visible,return true
if (currentParent.getVisibility() != View.VISIBLE) {
return true;
}
int start = indexOfViewInParent(currentView, currentParent);
for (int i = start + 1; i < currentParent.getChildCount(); i++) {
Rect viewRect = new Rect();
view.getGlobalVisibleRect(viewRect);
View otherView = currentParent.getChildAt(i);
Rect otherViewRect = new Rect();
otherView.getGlobalVisibleRect(otherViewRect);
// if view intersects its older brother(covered),return true
if (Rect.intersects(viewRect, otherViewRect)) {
return true;
}
}
currentView = currentParent;
}
return false;
}
private int indexOfViewInParent(View view, ViewGroup parent) {
int index;
for (index = 0; index < parent.getChildCount(); index++) {
if (parent.getChildAt(index) == view)
break;
}
return index;
}
/**
* This method will cost 100ms to judge whether scrollview stoped.
*
* @param scrollView
* @return true means scrolling is stop, otherwise return fasle
*/
public boolean isScrollStoped(final ScrollView scrollView) {
int x1 = scrollView.getScrollX();
int y1 = scrollView.getScrollY();
sleep(100);
int x2 = scrollView.getScrollX();
int y2 = scrollView.getScrollY();
return x1 == x2 && y1 == y2 ? true : false;
}
public boolean isSize0(final View view) {
return view.getHeight() == 0 || view.getWidth() == 0;
}
/**
* This class is only for ViewRecorder not for user.
*/
public class RecordReplay {
/**
* Simulate touching a specific location and dragging to a new location
* by percent.
*
* @param fromX
* X coordinate of the initial touch, in screen coordinates
* @param toX
* Xcoordinate of the drag destination, in screen coordinates
* @param fromY
* X coordinate of the initial touch, in screen coordinates
* @param toY
* Y coordinate of the drag destination, in screen
* coordinates
* @param stepCount
* stepCount How many move steps to include in the drag
*
*/
public void dragPercent(float fromXPersent, float toXPersent, float fromYPersent,
float toYPersent, int stepCount) {
float fromX = toScreenX(fromXPersent);
float toX = toScreenX(toXPersent);
float fromY = toScreenY(fromYPersent);
float toY = toScreenY(toYPersent);
// long begin = System.currentTimeMillis();
drag(fromX, toX, fromY, toY, stepCount);
print(String.format("fromX:%s toX:%s fromY:%s toY:%s", fromX, toX, fromY, toY));
// print("duration:" + (System.currentTimeMillis() - begin) + " step:" + stepCount);
}
public float toScreenX(float persent) {
return getDisplayX() * persent;
}
public float toScreenY(float persent) {
return getDisplayY() * persent;
}
public float toPercentX(float x) {
return x / getDisplayX();
}
public float toPercentY(float y) {
return y / getDisplayY();
}
private final static String CLASSNAME_DECORVIEW = "com.android.internal.policy.impl.PhoneWindow$DecorView";
public String getFamilyString(View v) {
View view = v;
String familyString = "";
while (view.getParent() instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) view.getParent();
if (null == parent) {
print("null == parent at getFamilyString");
return rmTheLastChar(familyString);
}
if (Build.VERSION.SDK_INT >= 14
&& parent.getClass().getName().equals(CLASSNAME_DECORVIEW)) {
} else {
familyString += getChildIndex(parent, view) + "-";
}
view = parent;
}
return rmTheLastChar(familyString);
}
private String rmTheLastChar(String str) {
return str.length() == 0 ? str : str.substring(0, str.length() - 1);
}
private int getChildIndex(ViewGroup parent, View child) {
int countInvisible = 0;
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getChildAt(i).equals(child)) {
return i - countInvisible;
}
/*
if (parent.getChildAt(i).getVisibility() != View.VISIBLE) {
countInvisible++;
}*/
}
return -1;
}
/**
* only for getting selected view from AdapterView at clickInList(final
* int position, String... args)
*/
private View targetViewInList = null;
/**
* @param position
* @param args
* it could be familyString or (resid, index)
*/
public void clickInList(final int position, String... args) {
try {
AdapterView<?> targetView = null;
if (args.length == 1) {
targetView = (AdapterView<?>) waitForView("android.widget.AdapterView",
args[0], true);
} else if (args.length == 2) {
targetView = (AdapterView<?>) waitForView(args[0], args[1], true);
} else {
print("invalid parameters at clickInList");
}
final AdapterView<?> adapterView = targetView;
Assert.assertTrue("null == adapterView at" + Log.getThreadInfo(),
null != adapterView);
targetViewInList = null;
final long end = System.currentTimeMillis() + 1000;
runOnMainSync(new Runnable() {
@Override
public void run() {
while (null == targetViewInList && System.currentTimeMillis() < end) {
// solution A
adapterView.setSelection(position);
adapterView.requestFocusFromTouch();
adapterView.setSelection(position);
sleep(300);// wait setSelection is done
targetViewInList = adapterView.getSelectedView();
// solution B
if (null == targetViewInList
|| adapterView.getPositionForView(targetViewInList) != position) {
for (int i = 0; i < adapterView.getChildCount(); i++) {
View child = adapterView.getChildAt(i);
if (adapterView.getPositionForView(child) == position) {
print("child index: " + i);
print("getLastVisiblePosition:"
+ adapterView.getLastVisiblePosition());
targetViewInList = child;
}
}
}
}
}
});
// this sleep is necessary
sleep(1000);
mTheLastClick = getViewCenter(targetViewInList);
sleep(1000);
int[] center = mTheLastClick;
print("click list[" + adapterView + "] on " + center[0] + ", " + center[1]);
print("targetViewInList:" + targetViewInList);
clickOnScreen(center[0], center[1]);
targetViewInList.performClick();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Waits for a WebElement by family string.
*
* @param familyString
* timeout how long the function waits if there is no
* satisfied web element scroll true if need to scroll the
* screen, false otherwise
* @return WebElement wait for
*/
@SuppressWarnings("unchecked")
public WebElement waitForWebElementByFamilyString(String familyString, int timeout,
boolean scroll) {
final long endTime = SystemClock.uptimeMillis() + timeout;
while (true) {
final boolean timedOut = SystemClock.uptimeMillis() > endTime;
try {
if (timedOut) {
// searcher.logMatchesFound(familyString);
ReflectHelper.invoke(searcher, null, "logMatchesFound",
new Class[] { String.class }, new Object[] { familyString });
return null;
}
ReflectHelper.invoke(sleeper, null, "sleep", new Class[] {}, new Object[] {});
String js = "function familyString(s) {var e=document.body;var a=s.split('-');"
+ "for(var i in a) {e=e.childNodes[parseInt(a[i])];}"
+ "if(e != null){var id=e.id;var text=e.textContent;"
+ "var name=e.getAttribute('name');var className=e.className;"
+ "var tagName=e.tagName;var rect=e.getBoundingClientRect();"
+ "prompt(id+';,'+text+';,'+name+';,'+className+';,'+tagName+';"
+ ",'+rect.left+';,'+rect.top+';,'+rect.width+';,'+rect.height);}finished();}"
+ "familyString('" + familyString + "');";
// executeJavaScriptFunction(js);
boolean javaScriptWasExecuted = (Boolean) ReflectHelper.invoke(webUtils, null,
"executeJavaScriptFunction", new Class[] { String.class },
new Object[] { js });
// getSufficientlyShownWebElements(javaScriptWasExecuted);
ArrayList<WebElement> viewsFromScreen = (ArrayList<WebElement>) ReflectHelper
.invoke(webUtils, null, "getSufficientlyShownWebElements",
new Class[] { boolean.class },
new Object[] { javaScriptWasExecuted });
List<WebElement> webElements = (List<WebElement>) ReflectHelper.getField(
searcher, null, "webElements");
// searcher.addViewsToList(webElements, viewsFromScreen);
ReflectHelper
.invoke(searcher, null, "addViewsToList", new Class[] { List.class,
List.class }, new Object[] { webElements, viewsFromScreen });
// searcher.getViewFromList(webElements, 1);
WebElement webElementToReturn = (WebElement) ReflectHelper.invoke(searcher,
null, "getViewFromList", new Class[] { List.class, int.class },
new Object[] { webElements, 1 });
if (webElementToReturn != null) {
return webElementToReturn;
}
Object down = ReflectHelper.getField(scroller, null, "DOWN");
if (scroll) {
// mScroller.scroll(mScroller.DOWN)
ReflectHelper.invoke(scroller, null, "scroll", new Class[] { int.class },
new Object[] { down });
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
/**
* Clicks on the WebElement by the given family string.
*
* @param webElement
* the WebElement to click
*
*/
public void clickOnWebElementByFamilyString(String familyString) {
WebElement webElement = waitForWebElementByFamilyString(familyString, WAIT_TIMEOUT,
true);
Assert.assertTrue("There is no web element with familyString : " + familyString,
webElement != null);
clickOnWebElement(webElement);
}
/**
* Enters text in a WebElement by family string
*
* @param familyString
* the String object, used to locates an identified element
* @param text
* the text to enter
*
*/
public void enterTextInWebElementByFamilyString(String familyString, String text) {
if (waitForWebElementByFamilyString(familyString, WAIT_TIMEOUT, false) == null) {
Assert.assertTrue("There is no web element with familyString : " + familyString,
false);
}
String js = "function enterTextByFamilyString(s,t) {var e=document;var a=s.split('-');for(var i in a) {e=e.childNodes[parseInt(a[i])];}if(e != null){e.value=t}finished();}"
+ "enterTextByFamilyString('" + familyString + "','" + text + "');";
try {
ReflectHelper.invoke(webUtils, null, "executeJavaScriptFunction",
new Class[] { String.class }, new Object[] { js });// executeJavaScriptFunction(js);
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private View pickViewByFamilyString(String className, String familyString) {
ArrayList<View> views = getCurrentViews();
for (View view : views) {
if (getFamilyString(view).equals(familyString)) {
if (null == className) {
return view;
}
String viewClassName = view.getClass().getName();
try {
if (viewClassName.equals(className)
|| Class.forName(className).isAssignableFrom(view.getClass())) {
return view;
}
} catch (ClassNotFoundException e) {
// ignore it
}
}
}
return null;
}
/**
* for debug when get view failed
*/
private void printViews(String characteristic) {
ArrayList<View> invisibleViews = getCurrentViews();
for (View view : invisibleViews) {
String familyString = getFamilyString(view);
String resId = getResName(view);
String star = "";
if (familyString.equals(characteristic) || resId.equals(characteristic)) {
star = "*";
int[] xy = new int[2];
view.getLocationOnScreen(xy);
print(xy[0] + "," + xy[1]);
print("isSize0:" + isSize0(view));
print("isShown:" + view.isShown());
}
print(String.format("%s[%s][%s][%s][%s]", star, familyString, resId, view,
getViewText(view)));
}
}
/**
* This method is protected by assert.
*
* @param familyString
* @param text
*/
public void waitForTextByFamilyString(String familyString, String text) {
View view = waitForView(null, familyString, true);
String actual = getViewText(view);
Assert.assertTrue(String.format("Except text [%s], Actual text [%s]", text, actual),
actual.equals(text));
}
private final static int WAIT_TIMEOUT = 20000;
/**
* This method is protected by assert. If arg1 contains "id/", arg1 will
* be judged to resid otherwise it will be judged to className.
*
* @param arg1
* it could be className or resid
* @param arg2
* it could be familyString or index
* @return the view picked
*/
public View waitForView(String arg1, String arg2, boolean isAssert) {
boolean useResId = arg1 != null && arg1.contains("id/") ? true : false;
long endTime = System.currentTimeMillis() + WAIT_TIMEOUT;
while (System.currentTimeMillis() < endTime) {
View targetView = null;
// it must be the same as ViewRecorder.getTargetViews()
if (useResId) {
targetView = getViewByResName(arg1, Integer.valueOf(arg2));
} else {
targetView = pickViewByFamilyString(arg1, arg2);
}
if (targetView != null) {
return targetView;
}
sleep(500);
}
if (useResId) {
printViews(arg1);
} else {
printViews(arg2);
}
String failMessage = String.format("waitForView failed! arg1[%s] arg2[%s]", arg1, arg2);
if (isAssert) {
Assert.assertTrue(failMessage, false);
} else {
print(failMessage);
}
return null;
}
/**
* Clicks on a {@code View} of a specific class with a certain
* familyString or a specific resource id with a index.
*
* This method is protected by assert.
*
* @param arg1
* it could be className or resid
* @param arg2
* it could be familyString or index
* @param longClick
* true means long click
*/
public void clickOn(String arg1, String arg2, boolean longClick) {
clickOn(arg1, arg2, longClick, true);
}
public void clickOn(String arg1, String arg2, boolean longClick, boolean isAssert) {
try {
View view = waitForView(arg1, arg2, isAssert);
if (view != null) {
performClick(view, longClick);
}
// clickOnView();
} catch (Exception e) {
e.printStackTrace();
}
}
public void clickOnExpandableListView(final String familyString, final int flatListPosition) {
final ExpandableListView expandableListView = (ExpandableListView) waitForView(
"android.widget.ExpandableListView", familyString, true);
Assert.assertTrue("null == adapterView at" + Log.getThreadInfo(),
null != expandableListView);
runOnMainSync(new Runnable() {
@Override
public void run() {
View v = expandableListView.getChildAt(flatListPosition);
mTheLastClick = getViewCenter(v);
long id = expandableListView.getItemIdAtPosition(flatListPosition);
expandableListView.performItemClick(v, flatListPosition, id);
}
});
}
public void scrollListToLine(final int line, String... args) {
AbsListView targetView = null;
if (args.length == 1) {
targetView = (AbsListView) waitForView("android.widget.AbsListView", args[0], true);
} else if (args.length == 2) {
targetView = (AbsListView) waitForView(args[0], args[1], true);
} else {
print("invalid parameters at clickInList");
}
Assert.assertTrue("null == absListView at" + Log.getThreadInfo(), null != targetView);
try {
ReflectHelper.invoke(scroller, null, "scrollListToLine", new Class[] {
AbsListView.class, int.class }, new Object[] { targetView, line });
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public void scrollScrollViewTo(final String familyString, final int x, final int y) {
final ScrollView scrollView = (ScrollView) waitForView("android.widget.ScrollView",
familyString, true);
runOnMainSync(new Runnable() {
public void run() {
scrollView.scrollBy(x, y);
}
});
}
}
public static double countDistance(float x1, float y1, float x2, float y2) {
return Math.sqrt(Math.abs(x1 - x2) * Math.abs(x1 - x2) + Math.abs(y1 - y2)
* Math.abs(y1 - y2));
}
/**
* get view index by its class at current activity
*
* @param view
* @return -1 means not found;otherwise is then index of view
*/
public int getCurrentViewIndex(View view) {
if (null == view) {
return -1;
}
ArrayList<? extends View> views = removeInvisibleViews(getCurrentViews(view.getClass()));
for (int i = 0; i < views.size(); i++) {
if (views.get(i).equals(view)) {
return i;
}
}
return -1;
}
public void dumpPage() {
ArrayList<String> webElementsString = getWebElementsString();
print("############# dumpPage begin #################");
for (String line : webElementsString) {
print(line);
}
print("############# dumpPage end #################");
}
public ArrayList<String> getWebElementsString() {
ArrayList<String> webElementsString = new ArrayList<String>();
ArrayList<WebElement> elements = getCurrentWebElements();
for (WebElement element : elements) {
StringBuilder sb = new StringBuilder();
sb.append('(').append(element.getLocationX()).append(',')
.append(element.getLocationY()).append(") , {tagName : ")
.append(element.getTagName()).append("} , {id : ").append(element.getId())
.append("} , {className : ").append(element.getClassName())
.append("} , {name : ").append(element.getName()).append("} , {text : ")
.append(element.getText()).append('}');
webElementsString.add(sb.toString());
}
return webElementsString;
}
private static String mUrl = null;
private static ArrayList<String> mTexts = new ArrayList<String>();
/**
* dump text of activity including webview
*
* @param saveDuplicate
* true means save duplicate text
*/
public void dumpActivityText(boolean saveDuplicate) {
String activity = getCurrentActivity().getClass().getName();
for (TextView textView : getCurrentViews(TextView.class)) {
String text = textView.getText().toString();
if ("".equals(text)) {
continue;
}
int[] xy = new int[2];
textView.getLocationOnScreen(xy);
float size = textView.getTextSize();
String color = "0x" + Integer.toHexString(textView.getTextColors().getDefaultColor());
if (saveDuplicate) {
print(String.format("[%s][%s][%s]", activity, size, text));
continue;
} else if (!isContains(text)) {
mTexts.add(text);
print(String.format("[%s][%s,%s][%s][%s][%s]", activity, xy[0], xy[1], size, color,
text));
}
}
// dump web
print(new Strings(getWebElementsString()).toString());
final ArrayList<WebView> webViews = getCurrentViews(WebView.class);
if (webViews.size() < 1) {
return;
}
runOnMainSync(new Runnable() {
@Override
public void run() {
mUrl = webViews.get(0).getUrl();
}
});
if (!isContains(mUrl)) {
mTexts.add(mUrl);
print(String.format("[URL][%s][%s]", activity, mUrl));
}
}
private boolean isContains(String target) {
for (String str : mTexts) {
if (str.equals(target)) {
return true;
}
}
return false;
}
@SuppressLint("Recycle")
public void clickViewWithoutAssert(View view) {
if (null == view) {
Logger.println("null == view at clickViewWithoutAssert");
return;
}
ArrayList<TextView> textViews = APPTraveler.local.getCurrentViews(TextView.class, view);
for (TextView textView : textViews) {
String text = textView.getText().toString();
if (!"".equals(text)) {
Logger.printTextln(String.format("Click On [%s]", text));
break;
}
}
int[] xy = getViewCenter(view);
int x = xy[0];
int y = xy[1];
long downTime = SystemClock.uptimeMillis();
long eventTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y,
0);
MotionEvent event2 = MotionEvent
.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
try {
mInstrumentation.sendPointerSync(event);
mInstrumentation.sendPointerSync(event2);
sleep(MINISLEEP);
} catch (SecurityException e) {
e.printStackTrace();
// Assert.assertTrue("Click can not be completed!", false);
}
}
/**
* @param depth
* @param username
* @param password
*/
public void travel(int depth, String username, String password) {
new APPTraveler(CafeTestCase.remote, this, username, password).travel(depth);
}
public void travel(int depth) {
travel(depth, null, null);
}
public void travel() {
travel(4, null, null);
}
public String getStringFromArguments(String key) {
return CafeTestRunner.mArguments.getString(key);
}
/**
* DO NOT USE A WAKE LOCK
*
* This will make sure that the screen stays on while your window is in the
* foreground, and only while it is in the foreground.
*/
public void keepScreenOn() {
mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}