/* * Copyright (c) 2020. BoostTag E.I.R.L. Romell D.Z. * All rights reserved * porfile.romellfudi.com */ package com.romellfudi.ussdlibrary; import android.accessibilityservice.AccessibilityService; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import com.elvishew.xlog.XLog; import com.google.gson.Gson; import java.util.ArrayList; import java.util.List; /** * AccessibilityService for ussd windows on Android mobile Telcom * * @author Romell Dominguez * @version 1.1.c 27/09/2018 * @since 1.0.a */ public class USSDService extends AccessibilityService { private static String TAG = "USSDServiceUSSD"; public static AccessibilityEvent event; @Override public void onAccessibilityEvent(AccessibilityEvent event) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { this.event = new AccessibilityEvent(event); } else { Parcel parcel = Parcel.obtain(); event.writeToParcel(parcel, 0); parcel.setDataPosition(0); AccessibilityEvent newEvent = AccessibilityEvent.CREATOR.createFromParcel(parcel); parcel.recycle(); this.event = newEvent; } XLog.i("USSDService Event Json : " + getEventNowRootInActiveWindow(event)); XLog.i(String.format("USSDService onAccessibilityEvent: [type] %s [class] %s [package] %s [time] %s [text] %s" + " [ContentChangeTypes] %s [WindowChanges] %s", event.getEventType(), event.getClassName(), event.getPackageName(), event.getEventTime(), event.getText(), event.getContentChangeTypes(),event.getWindowChanges())); if (USSDController.instance == null || !USSDController.instance.isRunning) { XLog.e("USSDService Error : USSDController.instance = " + (USSDController.instance == null)); if (USSDController.instance != null) { XLog.e("USSDService Error : USSDController.instance.isRunning = " + (USSDController.instance.isRunning)); } return; } String response = event.getText().toString(); if (LoginView(event) && notInputText(event)) { XLog.i("USSDService: 1"); clickOnButton(event, 0); USSDController.instance.isRunning = false; if (USSDController.instance.send) USSDController.instance.callbackMessage.over(response); else USSDController.instance.callbackInvoke.over(response); } else if (problemView(event) || LoginView(event)) { XLog.i("USSDService: 2"); clickOnButton(event, 1); if (USSDController.instance.send) USSDController.instance.callbackMessage.over(response); else USSDController.instance.callbackInvoke.over(response); } else if (isUSSDWidget(event)) { XLog.i("USSDService: 3"); if (notInputText(event)) { XLog.i("USSDService: 4"); clickOnButton(event, 0); USSDController.instance.isRunning = false; if (USSDController.instance.send) USSDController.instance.callbackMessage.over(response); else USSDController.instance.callbackInvoke.over(response); } else { XLog.i("USSDService: 5"); if (USSDController.instance.send) USSDController.instance.callbackMessage.responseMessage(response); else USSDController.instance.callbackInvoke.responseInvoke(response); } } else { XLog.e("USSDService Error : 未走进任何逻辑循环"); } } /** * Send whatever you want via USSD * * @param text any string */ public static void send(String text) { setTextIntoField(event, text); clickOnButton(event, 1); } /** * Cancel USSD */ public static void cancel() { clickOnButton(event, 0); } /** * set text into input text at USSD widget * * @param event AccessibilityEvent * @param data Any String */ private static void setTextIntoField(AccessibilityEvent event, String data) { USSDController ussdController = USSDController.instance; Bundle arguments = new Bundle(); arguments.putCharSequence( AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, data); for (AccessibilityNodeInfo leaf : getLeaves(event)) { if (leaf.getClassName().equals("android.widget.EditText") && !leaf.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)) { ClipboardManager clipboardManager = ((ClipboardManager) ussdController.context .getSystemService(Context.CLIPBOARD_SERVICE)); if (clipboardManager != null) { clipboardManager.setPrimaryClip(ClipData.newPlainText("text", data)); } leaf.performAction(AccessibilityNodeInfo.ACTION_PASTE); } } } /** * Method evaluate if USSD widget has input text * * @param event AccessibilityEvent * @return boolean has or not input text */ protected static boolean notInputText(AccessibilityEvent event) { boolean flag = true; for (AccessibilityNodeInfo leaf : getLeaves(event)) { if (leaf.getClassName().equals("android.widget.EditText")) flag = false; } return flag; } /** * The AccessibilityEvent is instance of USSD Widget class * * @param event AccessibilityEvent * @return boolean AccessibilityEvent is USSD */ private boolean isUSSDWidget(AccessibilityEvent event) { return (event.getClassName().equals("amigo.app.AmigoAlertDialog") || event.getClassName().equals("android.app.AlertDialog") || event.getClassName().equals("com.android.phone.oppo.settings.LocalAlertDialog") || event.getClassName().equals("com.zte.mifavor.widget.AlertDialog") || event.getClassName().equals("color.support.v7.app.AlertDialog") || event.getClassName().equals("com.transsion.widgetslib.dialog.PromptDialog") || event.getClassName().equals("miuix.appcompat.app.AlertDialog") || event.getClassName().equals("androidx.appcompat.app.AlertDialog")); } /** * The View has a login message into USSD Widget * * @param event AccessibilityEvent * @return boolean USSD Widget has login message */ private boolean LoginView(AccessibilityEvent event) { return isUSSDWidget(event) && USSDController.instance.map.get(USSDController.KEY_LOGIN) .contains(event.getText().get(0).toString()); } /** * The View has a problem message into USSD Widget * * @param event AccessibilityEvent * @return boolean USSD Widget has problem message */ protected boolean problemView(AccessibilityEvent event) { return isUSSDWidget(event) && USSDController.instance.map.get(USSDController.KEY_ERROR) .contains(event.getText().get(0).toString()); } /** * click a button using the index * * @param event AccessibilityEvent * @param index button's index */ protected static void clickOnButton(AccessibilityEvent event, int index) { int count = -1; for (AccessibilityNodeInfo leaf : getLeaves(event)) { if (leaf.getClassName().toString().toLowerCase().contains("button")) { count++; if (count == index) { leaf.performAction(AccessibilityNodeInfo.ACTION_CLICK); } } } } private static List getLeaves(AccessibilityEvent event) { List leaves = new ArrayList<>(); if (event.getSource() != null) { getLeaves(leaves, event.getSource()); } return leaves; } private static void getLeaves(List leaves, AccessibilityNodeInfo node) { if (node.getChildCount() == 0) { leaves.add(node); return; } for (int i = 0; i < node.getChildCount(); i++) { getLeaves(leaves, node.getChild(i)); } } /** * Active when SO interrupt the application */ @Override public void onInterrupt() { Log.d(TAG, "onInterrupt"); } /** * Configure accessibility server from Android Operative System */ @Override protected void onServiceConnected() { super.onServiceConnected(); Log.d(TAG, "onServiceConnected"); } public static String getEventNowRootInActiveWindow(AccessibilityEvent event) { String PageJson = "{}"; AccessibilityNodeInfo node = event.getSource(); if (node != null) { NodeInfoWrapper nodeInfoWrapper = traverseNode(node); Gson gson = new Gson(); PageJson = gson.toJson(nodeInfoWrapper); } return PageJson; } private static class NodeInfoWrapper { public String txt; public String vId; public String cName; public String pName; public int hashcode; public int childCount; //用于判断节点是否可以打开弹出窗口(popup)canOpenPopup()方法用于判断给定的节点是否具有打开弹出窗口的能力 public boolean canOpenPopup; //判断节点是否对用户可见 public boolean visibleToUser; //判断节点是否启用 public boolean isEnabled; public String hintText; //判断节点是否可点击 public boolean isClickable; //判断节点是否可长按 public boolean isLongClickable; //判断节点是否可编辑 public boolean isEditable; //判断节点是否可选择 public boolean isCheckable; //判断节点的选择状态 public boolean isChecked; //判断节点是否具有焦点 public boolean focused; //判断节点是否可滚动 public boolean scrollable; public boolean isSelected; public boolean isAccessibilityFocused; public boolean isContentInvalid; public boolean isContextClickable; public boolean isDismissable; public boolean isFocusable; public boolean isFocused; public boolean isHeading; public boolean isImportantForAccessibility; public boolean isMultiLine; public boolean isPassword; public boolean isScreenReaderFocusable; public boolean isScrollable; public boolean isShowingHintText; public boolean isTextEntryKey; public boolean isTextSelectable; public boolean isVisibleToUser; public List childNodes; } private static NodeInfoWrapper traverseNode(AccessibilityNodeInfo nodeInfo) { NodeInfoWrapper nodeInfoWrapper = new NodeInfoWrapper(); nodeInfoWrapper.txt = nodeInfo.getText() != null ? nodeInfo.getText().toString() : ""; nodeInfoWrapper.vId = nodeInfo.getViewIdResourceName(); nodeInfoWrapper.cName = nodeInfo.getClassName().toString(); nodeInfoWrapper.pName = nodeInfo.getPackageName().toString(); nodeInfoWrapper.hashcode = nodeInfo.hashCode(); nodeInfoWrapper.childCount = nodeInfo.getChildCount(); nodeInfoWrapper.canOpenPopup = nodeInfo.canOpenPopup(); nodeInfoWrapper.visibleToUser = nodeInfo.isVisibleToUser(); nodeInfoWrapper.isEnabled = nodeInfo.isEnabled(); nodeInfoWrapper.hintText = nodeInfo.getHintText() != null ? nodeInfo.getText().toString() : ""; nodeInfoWrapper.isClickable = nodeInfo.isClickable(); nodeInfoWrapper.isLongClickable = nodeInfo.isLongClickable(); nodeInfoWrapper.isEditable = nodeInfo.isEditable(); nodeInfoWrapper.isCheckable = nodeInfo.isCheckable(); nodeInfoWrapper.isChecked = nodeInfo.isChecked(); nodeInfoWrapper.focused = nodeInfo.isFocused(); nodeInfoWrapper.scrollable = nodeInfo.isScrollable(); nodeInfoWrapper.isSelected = nodeInfo.isSelected(); nodeInfoWrapper.isAccessibilityFocused = nodeInfo.isAccessibilityFocused(); nodeInfoWrapper.isContentInvalid = nodeInfo.isContentInvalid(); nodeInfoWrapper.isContextClickable = nodeInfo.isContextClickable(); nodeInfoWrapper.isDismissable = nodeInfo.isDismissable(); nodeInfoWrapper.isFocusable = nodeInfo.isFocusable(); nodeInfoWrapper.isFocused = nodeInfo.isFocused(); nodeInfoWrapper.isHeading = nodeInfo.isHeading(); nodeInfoWrapper.isImportantForAccessibility = nodeInfo.isImportantForAccessibility(); nodeInfoWrapper.isMultiLine = nodeInfo.isMultiLine(); nodeInfoWrapper.isPassword = nodeInfo.isPassword(); nodeInfoWrapper.isScreenReaderFocusable = nodeInfo.isScreenReaderFocusable(); nodeInfoWrapper.isScrollable = nodeInfo.isScrollable(); nodeInfoWrapper.isShowingHintText = nodeInfo.isShowingHintText(); nodeInfoWrapper.isTextEntryKey = nodeInfo.isTextEntryKey(); nodeInfoWrapper.isVisibleToUser = nodeInfo.isVisibleToUser(); List childNodes = new ArrayList<>(); for (int i = 0; i < nodeInfo.getChildCount(); i++) { AccessibilityNodeInfo childNode = nodeInfo.getChild(i); if (childNode != null) { NodeInfoWrapper childNodeWrapper = traverseNode(childNode); childNodes.add(childNodeWrapper); } } nodeInfoWrapper.childNodes = childNodes; return nodeInfoWrapper; } }