package com.android.testing.uiautomation; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.IEventListener; import android.content.Context; import android.graphics.Rect; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityManager; import android.widget.EditText; import java.util.List; public class ProviderImpl extends Provider.Stub { private static final String LOGTAG = "ProviderImpl"; private static final String TYPE_CLASSNAME = "classname"; private static final String TYPE_TEXT = "text"; private Context mContext; private InteractionProvider mInteractionProvider; private IAccessibilityServiceConnection mAccessibilityServiceConnection; protected AccessibilityNodeInfo mCurrentWindow = null; protected AccessibilityNodeInfo mCurrentFocused = null; protected String mCurrentActivityName = null; protected String mCurrentActivityClass = null; protected String mCurrentActivityPackage = null; public ProviderImpl(Context context) throws RemoteException { IEventListener listener = new IEventListener.Stub() { @Override public void setConnection(IAccessibilityServiceConnection connection) throws RemoteException { AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; info.feedbackType = AccessibilityServiceInfo.FEEDBACK_VISUAL; info.notificationTimeout = 0; info.flags = AccessibilityServiceInfo.DEFAULT; connection.setServiceInfo(info); } @Override public void onInterrupt() { } @Override public void onAccessibilityEvent(AccessibilityEvent event) throws RemoteException { // delegate the call to parent ProviderImpl.this.onAccessibilityEvent(event); } }; IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(ServiceManager .getService(Context.ACCESSIBILITY_SERVICE)); mContext = context; mAccessibilityServiceConnection = manager.registerEventListener(listener); mInteractionProvider = new InteractionProvider(); } private IAccessibilityServiceConnection getConnection() { return mAccessibilityServiceConnection; } private void onAccessibilityEvent(AccessibilityEvent event) throws RemoteException { Log.d(LOGTAG, "ProviderImpl=" + this.toString()); Log.d(LOGTAG, event.toString()); switch (event.getEventType()) { case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: if (mCurrentWindow != null) { mCurrentWindow.recycle(); } mCurrentWindow = event.getSource(); if (shouldDumpWindow()) AccessibilityNodeInfoHelper.dumpWindowToFile(mCurrentWindow); mCurrentActivityClass = event.getClassName().toString(); mCurrentActivityPackage = event.getPackageName().toString(); if (event.getText().size() > 0) { mCurrentActivityName = event.getText().get(0).toString(); } else { mCurrentActivityName = null; } break; case AccessibilityEvent.TYPE_VIEW_FOCUSED: if (mCurrentFocused != null) { mCurrentFocused.recycle(); } mCurrentFocused = event.getSource(); default: break; } } @Override public boolean isEnabled(String selector) throws RemoteException { AccessibilityNodeInfo node = findNodeOrThrow(getConnection(), selector); boolean b = node.isEnabled(); node.recycle(); return b; } @Override public boolean isFocused(String selector) throws RemoteException { AccessibilityNodeInfo node = findNodeOrThrow(getConnection(), selector); boolean b = node.isFocused(); node.recycle(); return b; } @Override public int getChildCount(String selector) throws RemoteException { AccessibilityNodeInfo node = findNodeOrThrow(getConnection(), selector); int count = node.getChildCount(); node.recycle(); return count; } @Override public String getText(String selector) throws RemoteException { AccessibilityNodeInfo node = findNode(getConnection(), selector); if (node == null) { Log.w(LOGTAG, "node not found, selector=" + selector); return null; } else { String s = node.getText().toString(); node.recycle(); return s; } } @Override public String getClassName(String selector) throws RemoteException { AccessibilityNodeInfo node = findNode(getConnection(), selector); if (node == null) { Log.w(LOGTAG, "node not found, selector=" + selector); return null; } else { String s = node.getClassName().toString(); node.recycle(); return s; } } @Override public boolean click(String selector) throws RemoteException { AccessibilityNodeInfo node = findNode(getConnection(), selector); if (node == null) { Log.w(LOGTAG, "node not found, selector=" + selector); return false; } return click(node); } protected boolean click(AccessibilityNodeInfo node) throws RemoteException { // TODO: do a click here Rect b = new Rect(); node.getBoundsInScreen(b); return mInteractionProvider.tap(b.centerX(), b.centerY()); } @Override public String getCurrentActivityName() throws RemoteException { return mCurrentActivityName; } @Override public String getCurrentActivityPackage() throws RemoteException { return mCurrentActivityPackage; } @Override public String getCurrentActivityClass() throws RemoteException { return mCurrentActivityClass; } @Override public boolean sendText(String text) throws RemoteException { return mInteractionProvider.sendText(text); } @Override public boolean setTextFieldByLabel(String label, String text) throws RemoteException { // first index of a text field, first index of a text field after the // matching label int firstIndex = -1, firstAfterIndex = -1, labelIndex = -1; Log.d(LOGTAG, "I'm here..."); AccessibilityNodeInfo node = findNode(getConnection(), "text:" + label); if (node == null) { Log.w(LOGTAG, "label node not found: " + label); return false; } AccessibilityNodeInfo parent = node.getParent(); node.recycle(); node = null; int count = parent.getChildCount(); for (int i = 0; i < count; i++) { AccessibilityNodeInfo child = parent.getChild(i); CharSequence csText = child.getText(); CharSequence csClass = child.getClassName(); if (csText != null && label.contentEquals(csText)) { labelIndex = i; } if (csClass != null && EditText.class.getName().contentEquals(csClass)) { if (labelIndex == -1) { firstIndex = i; } else { firstAfterIndex = i; } } child.recycle(); } if (firstAfterIndex != -1) node = parent.getChild(firstAfterIndex); else if (firstIndex != -1) node = parent.getChild(firstIndex); parent.recycle(); if (node == null) { Log.w(LOGTAG, "Cannot find an EditorText for label: " + label); return false; } AccessibilityNodeInfoHelper.dumpNode(node); click(node); node.recycle(); return mInteractionProvider.sendText(text); } @Override public boolean checkUiVerificationEnabled() throws RemoteException { return AccessibilityManager.getInstance(mContext).isEnabled(); } private AccessibilityNodeInfo findNodeOrThrow(IAccessibilityServiceConnection connection, String selector) throws RemoteException { AccessibilityNodeInfo node = findNode(connection, selector); if (node == null) { Log.e(LOGTAG, "node not found, selector=" + selector); throw new RemoteException(); } return node; } private AccessibilityNodeInfo findNode(IAccessibilityServiceConnection connection, String selector) throws RemoteException { // a selector should be in the format of "[selector type]:[matcher] // example: // classname:android.widget.Text // text:Click Me // id:id/username int pos = selector.indexOf(':'); if (pos != -1) { String selectorType = selector.substring(0, pos); String matcher = selector.substring(pos + 1); if (TYPE_TEXT.equals(selectorType)) { List nodes = connection .findAccessibilityNodeInfosByViewTextInActiveWindow(matcher); if (nodes != null && nodes.size() > 0) { // keep the first one, recycle the rest // TODO: find better way to handle multiple matches for (int i = 1; i < nodes.size(); i++) { nodes.get(i).recycle(); } return nodes.get(0); } } // more type matchers to be added here } return null; } private static boolean shouldDumpWindow() { return SystemProperties.getBoolean("uiauto.dump", false); } }