package com.robotium.solo;
import android.app.Instrumentation;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.UiAutomation;
import android.content.Context;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
import static com.robotium.solo.Solo.LOG_TAG;
public class AcrossApplication {
private Context context;
private String pkg;
private Instrumentation instrumentation;
private Solo.Config config;
private UiAutomation uiAutomation;
private final static String QQ = "com.tencent.mobileqq";
private final static String WECHAT = "com.tencent.mm";
private final static String WEIBO = "com.sina.weibo";
public AcrossApplication(Context context, Instrumentation instrumentation, Solo.Config config){
this.context = context;
this.pkg = config.PACKAGE;
this.instrumentation = instrumentation;
this.config = config;
uiAutomation = instrumentation.getUiAutomation();
}
/**
* Work across application boundaries for permission.
*/
public void acrossForPermission(){
Permission permission = new Permission(context, pkg, instrumentation);
permission.requestPermissions();
}
/**
* Work across application boundaries for camera.
* @param viewId The fully qualified resource name of the view id to find. e.g: com.sec.android.app.camera:id/okay
*/
public void acrossForCamera(String viewId){
Log.d(LOG_TAG, "acrossForCamera()");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return;
UiAutomation uiAutomation = instrumentation.getUiAutomation();
uiAutomation.setOnAccessibilityEventListener(new UiAutomation.OnAccessibilityEventListener() {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
if (viewId.contains(event.getPackageName())) {
if (event.getSource() != null) {
List<AccessibilityNodeInfo> infoList = event.getSource().findAccessibilityNodeInfosByViewId(viewId);
if (infoList == null || infoList.isEmpty()) return;
performClick(infoList.get(0));
}
}
}
}
});
sleep(config.sleepDuration * 4);
shellCommand("input keyevent 27");
}
/**
* Work across application boundaries for QQ login.
*/
public void acrossForQQLogin(String account, String password){
boolean installed = isInstalled(QQ);
if (!installed) {
Log.w(LOG_TAG, "QQ is not installed.");
return;
}
uiAutomation.setOnAccessibilityEventListener(new UiAutomation.OnAccessibilityEventListener() {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(LOG_TAG, "Event: " + event.toString());
if (event.getEventType() == TYPE_WINDOW_STATE_CHANGED && QQ.contains(event.getPackageName())){
handleQQLogin(event, account, password);
handleAuthorization(event);
}
}
});
}
private void handleAuthorization(AccessibilityEvent event) {
if (event.getClassName().toString().contains("com.tencent.open.agent.AuthorityActivity")) {
Log.i(LOG_TAG, "QQ Login: " + event.toString());
sleep(config.sleepDuration * 4);
AccessibilityNodeInfo nodeInfo = uiAutomation.getRootInActiveWindow();
IterationNode(nodeInfo, "android.widget.Button");
}
}
/**
* Loop through the view and click.
* @param nodeInfo node
* @param name class name, e.g: android.widget.Button
*/
private void IterationNode(AccessibilityNodeInfo nodeInfo, String name) {
for (int i = 0; i < nodeInfo.getChildCount(); i ++){
AccessibilityNodeInfo node = nodeInfo.getChild(i);
Log.i(LOG_TAG, "QQ Login nodeInfo.getChild(i): " + node.getClassName());
if(name.contains(node.getClassName())) {
performClick(node);
break;
} else IterationNode(node, name);
}
}
private void handleQQLogin(AccessibilityEvent event, String account, String password) {
if (event.getClassName().toString().contains("com.tencent.qqconnect.wtlogin.Login")) {
AccessibilityNodeInfo nodeInfo = uiAutomation.getRootInActiveWindow();
List<AccessibilityNodeInfo> infoList = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mobileqq:id/account");
if (infoList == null || infoList.isEmpty()) return;
// Click the account edit text and enter text.
performClick(infoList.get(0));
acrossForEnterText(account);
infoList = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mobileqq:id/password");
if (infoList == null || infoList.isEmpty()) return;
// Click the password edit text and enter text.
performClick(infoList.get(0));
acrossForEnterText(password);
sleep(config.sleepDuration * 5);
IterationNode(nodeInfo, "android.widget.Button");
}
}
/**
* Enter text for the edit text.
* @param text the text, Does not support Chinese.
*/
public void acrossForEnterText(String text){
shellCommand("input text " + text);
}
/**
* Work across application boundaries for click.
* @param x the x coordinate
* @param y the y coordinate
*/
public void acrossForClick(float x, float y){
MotionEvent motionDown = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), KeyEvent.ACTION_DOWN,
x, y, 0);
motionDown.setSource(InputDevice.SOURCE_TOUCHSCREEN);
uiAutomation.injectInputEvent(motionDown, true);
MotionEvent motionUp = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), KeyEvent.ACTION_UP,
x, y, 0);
motionUp.setSource(InputDevice.SOURCE_TOUCHSCREEN);
uiAutomation.injectInputEvent(motionUp, true);
motionUp.recycle();
motionDown.recycle();
}
/**
* Work across application boundaries for click.
* @param nodeInfo {@link AccessibilityNodeInfo}
*/
public void acrossForClick(AccessibilityNodeInfo nodeInfo){
performClick(nodeInfo);
}
/**
* Work across application boundaries for notification.
* Listen to the notification bar 10 seconds at a time.
*/
public void acrossForNotification(){
UiAutomation uiAutomation = instrumentation.getUiAutomation();
uiAutomation.setOnAccessibilityEventListener(new UiAutomation.OnAccessibilityEventListener() {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
if (config.PACKAGE.contains(event.getPackageName())) {
if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
Notification notification = (Notification) event.getParcelableData();
PendingIntent pendingIntent = notification.contentIntent;
try {
android.util.Log.d(LOG_TAG, "Notification: " + event.toString());
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
}
}
}
});
sleep(1000 * 10);
}
/**
* Performs click on the node.
* @param nodeInfo {@link AccessibilityNodeInfo}
*/
private void performClick(AccessibilityNodeInfo nodeInfo) {
if(nodeInfo == null){
Log.w(LOG_TAG, "performClick: nodeInfo is null.");
return;
}
if(nodeInfo.isClickable()) nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
else performClick(nodeInfo.getParent());
}
/**
* Sleeps the current thread for <code>time</code> milliseconds.
*
* @param time the length of the sleep in milliseconds
*/
public void sleep(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException ignored) {}
}
/**
* The application is installed on the device.
* @param pkg package
* @return the application is installed on the device.
*/
private boolean isInstalled(String pkg) {
String s = shellCommand("pm list packages " + pkg);
return s.contains(pkg);
}
public static StringBuilder getRuntime(String command){
StringBuilder sb = new StringBuilder();
Process p = null;
try {
p = Runtime.getRuntime().exec(command);
} catch (IOException e) {
e.printStackTrace();
}
if (p != null){
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
try {
while ((line = br.readLine()) != null) {
sb.append(line).append(System.getProperty("line.separator"));
}
in.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb;
}
/**
* Executes a shell command. This method returs a file descriptor that points
* to the standard output stream. The command execution is similar to running
* "adb shell <command>" from a host connected to the device.
* @param command The command to execute.
* @return result
*/
private String shellCommand(String command){
StringBuilder sb = new StringBuilder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ParcelFileDescriptor parcelFileDescriptor = uiAutomation.executeShellCommand(command);
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(parcelFileDescriptor);
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append(System.getProperty("line.separator"));
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} else return getRuntime(command).toString();
return sb.toString();
}
}