com.linroid.pushapp.service.ApkAutoInstallService.java Source code

Java tutorial

Introduction

Here is the source code for com.linroid.pushapp.service.ApkAutoInstallService.java

Source

/*
 * Copyright (c) linroid 2015.
 *
 * 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.linroid.pushapp.service;

import android.accessibilityservice.AccessibilityService;
import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Handler;
import android.support.v4.app.NotificationCompat;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

import com.linroid.pushapp.App;
import com.linroid.pushapp.BuildConfig;
import com.linroid.pushapp.Constants;
import com.linroid.pushapp.R;
import com.linroid.pushapp.model.Pack;
import com.linroid.pushapp.util.AndroidUtil;
import com.linroid.pushapp.util.BooleanPreference;
import com.linroid.pushapp.util.DeviceUtil;
import com.linroid.pushapp.util.IntentUtil;

import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;

import hugo.weaving.DebugLog;
import timber.log.Timber;

/**
 * Created by linroid on 7/27/15.<br/>
 * ?ROOTApk<br/>
 *
 * See <a href="http://www.infoq.com/cn/articles/android-accessibility-installing">Android Accessibility?Root?</a>
 */
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class ApkAutoInstallService extends AccessibilityService {
    public static final int AVAILABLE_API = 16;

    public static final int INSTALL_TIMEOUT = 60000;
    public static final int UNINSTALL_TIMEOUT = 5000;
    public static final int INSTALL_RETRY_INTERVAL = 1000;

    private static final String CLASS_NAME_APP_ALERT_DIALOG = "android.app.AlertDialog";
    private static final String CLASS_NAME_LENOVO_SAFECENTER = "com.lenovo.safecenter";
    private static final String CLASS_NAME_PACKAGE_INSTALLER = "com.android.packageinstaller";
    private static final String CLASS_NAME_GOOGLE_PACKAGE_INSTALLER = "com.google.android.packageinstaller";
    private static final String CLASS_NAME_PACKAGE_INSTALLER_ACTIVITY = "com.android.packageinstaller.PackageInstallerActivity";
    private static final String CLASS_NAME_PACKAGE_INSTALLER_PERMSEDITOR = "com.android.packageinstaller.PackageInstallerPermsEditor";
    private static final String CLASS_NAME_PACKAGE_INSTALLER_PROGRESS = "com.android.packageinstaller.InstallAppProgress";
    private static final String CLASS_NAME_PACKAGE_UNINSTALLER_ACTIVITY = "com.android.packageinstaller.UninstallerActivity";
    private static final String CLASS_NAME_PACKAGE_UNINSTALLER_PROGRESS = "com.android.packageinstaller.UninstallAppProgress";
    private static final String CLASS_NAME_WIDGET_BUTTON = "android.widget.Button";
    private static final String CLASS_NAME_WIDGET_LISTVIEW = "android.widget.ListView";
    private static final String CLASS_NAME_WIDGET_TEXTVIEW = "android.widget.TextView";

    private static boolean enable = false;

    // ???
    private static SparseArray<Pack> sPrepareList = new SparseArray<>();
    private static SparseArray<Pack> sInstallList = new SparseArray<>();
    private static SparseArray<Pack> sUninstallList = new SparseArray<>();

    @Inject
    @Named(Constants.SP_AUTO_OPEN)
    public BooleanPreference autoOpen;
    @Inject
    NotificationManager notificationManager;

    Handler handler;

    Runnable handleInstallTimeout = new Runnable() {
        @Override
        public void run() {
            AccessibilityNodeInfo validInfo = getRootInActiveWindow();
            if (validInfo != null) {
                if (autoOpen.getValue()) {
                    boolean openSuccess = openAfterInstalled(null);
                    Timber.d("?%s", openSuccess);
                }
                String label = validInfo.getText().toString();
                removePackFromListByAppName(sInstallList, label, true);
                validInfo.recycle();
            }
        }
    };

    Runnable handleUninstallTimeout = new Runnable() {
        @Override
        public void run() {
            // ???????????
            AccessibilityNodeInfo eventInfo = getRootInActiveWindow();
            if (eventInfo != null && sUninstallList != null && eventInfo.getText() != null) {
                String label = eventInfo.getText().toString();
                removePackFromListByAppName(sUninstallList, label); // waring ??
                boolean success = performEventAction(eventInfo, getString(R.string.btn_accessibility_ok), false)
                        || performEventAction(eventInfo, getString(R.string.btn_accessibility_know), false);
                eventInfo.recycle();
            }
            processPrepareInstall(null);
        }
    };

    /**
     * 
     *
     * @param pack
     */
    public static void addInstallPackage(Pack pack) {
        enable = true;

        if (pack != null) {
            sInstallList.put(pack.getId(), pack);
        }
    }

    /**
     * ?
     *
     * @param pack
     */
    public static void addUninstallApplication(Pack pack) {
        enable = true;
        if (pack != null) {
            sUninstallList.put(pack.getId(), pack);
        }
    }

    /**
     * ??
     *
     * @param pack
     */
    public static void addPrepareInstallApplication(Pack pack) {
        enable = true;
        if (pack != null) {
            sUninstallList.put(pack.getId(), pack);
            sPrepareList.put(pack.getId(), pack);
        }
    }

    public static void reset() {
        enable = false;
        if (sInstallList != null) {
            sInstallList.clear();
        }
        if (sUninstallList != null) {
            sUninstallList.clear();
        }
    }

    public static boolean available() {
        return VERSION.SDK_INT >= AVAILABLE_API;
    }

    /**
     * ???
     */
    private void processPrepareInstall(String label) {
        Pack pack = null;
        if (sPrepareList.size() == 0) {
            return;
        }

        if (label != null) {
            for (int i = 0; i < sPrepareList.size(); i++) {
                int key = sPrepareList.keyAt(i);
                pack = sPrepareList.get(key);
                sPrepareList.remove(key);
            }
        }

        if (pack == null) {
            pack = sPrepareList.get(sPrepareList.size() - 1);
            sPrepareList.removeAt(sPrepareList.size() - 1);
        }

        addInstallPackage(pack);
        startActivity(IntentUtil.installApk(pack.getPath()));
    }

    @DebugLog
    @Override
    public void onCreate() {
        super.onCreate();
        App.from(this).component().inject(this);
        handler = new Handler();
    }

    @DebugLog
    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (enable && available()) {
            try {
                onProcessAccessibilityEvent(event);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    @DebugLog
    public void onInterrupt() {
    }

    @DebugLog
    @Override
    protected boolean onKeyEvent(KeyEvent event) {
        return true;
    }

    private void onProcessAccessibilityEvent(AccessibilityEvent event) {
        if (event.getSource() == null) {
            return;
        }
        String packageName = event.getPackageName().toString();
        String className = event.getClassName().toString();
        String sourceText = event.getSource().getText() == null ? BuildConfig.VERSION_NAME
                : event.getSource().getText().toString().trim();
        if (packageName.equals(CLASS_NAME_PACKAGE_INSTALLER)
                || packageName.equals(CLASS_NAME_GOOGLE_PACKAGE_INSTALLER)) {
            if (isApplicationInstallEvent(event, className, sourceText)) {
                // 
                onApplicationInstall(event);
            } else if (hasAccessibilityNodeInfoByText(event,
                    getString(R.string.str_accessibility_install_blocked))) {
                // 
                onApplicationInstall(event, false);
            } else if (isApplicationInstalledEvent(event, className, sourceText)) {
                // ?
                onApplicationInstalled(event);
            } else if (isApplicationInstallFailedEvent(event, className, sourceText)) {
                // 
                onInstallFail(event);
            } else if (className.equalsIgnoreCase(CLASS_NAME_APP_ALERT_DIALOG)) {
                // ??
                processAlertDialogEvent(event, className, sourceText);
            } else if (isApplicationUninstallEvent(event, className, sourceText)) {
                // ?
                onApplicationUninstall(event);
            } else if (isApplicationUninstalledEvent(event, className, sourceText)) {
                // ??
                onApplicationUninstalled(event);
            }
        } else if (packageName.equals(CLASS_NAME_LENOVO_SAFECENTER)) {
            processLenovoEvent(event, className, sourceText);
        }
    }

    /**
     * ?
     *
     * @param event
     * @param className
     * @param sourceText
     * @return
     */
    private boolean isApplicationInstallEvent(AccessibilityEvent event, String className, String sourceText) {
        return className.equalsIgnoreCase(CLASS_NAME_PACKAGE_INSTALLER_ACTIVITY)
                || sourceText.contains(getString(R.string.btn_accessibility_install));
    }

    /**
     * ??
     *
     * @param event
     * @param className
     * @param sourceText
     * @return
     */
    private boolean isApplicationInstallFailedEvent(AccessibilityEvent event, String className, String sourceText) {
        return className.equalsIgnoreCase(CLASS_NAME_WIDGET_TEXTVIEW)
                && (sourceText.contains(getString(R.string.str_accessibility_installed4))
                        || sourceText.contains(getString(R.string.str_accessibility_installed5)));
    }

    /**
     * ??
     *
     * @param event
     * @param className
     * @param sourceText
     * @return
     */
    private boolean isApplicationInstalledEvent(AccessibilityEvent event, String className, String sourceText) {
        return sourceText.equalsIgnoreCase(getString(R.string.btn_accessibility_open))
                || sourceText.equalsIgnoreCase(getString(R.string.btn_accessibility_run))
                || sourceText.contains(getString(R.string.str_accessibility_installed))
                || sourceText.contains(getString(R.string.str_accessibility_installed2))
                || sourceText.contains(getString(R.string.str_accessibility_installed3))

                || hasAccessibilityNodeInfoByText(event, getString(R.string.str_accessibility_installed))
                || hasAccessibilityNodeInfoByText(event, getString(R.string.str_accessibility_installed2))
                || hasAccessibilityNodeInfoByText(event, getString(R.string.str_accessibility_installed3));
    }

    /**
     * ??
     *
     * @param event
     * @param className
     * @param sourceText
     * @return
     */
    private boolean isApplicationUninstallEvent(AccessibilityEvent event, String className, String sourceText) {
        return className.equalsIgnoreCase(CLASS_NAME_PACKAGE_UNINSTALLER_ACTIVITY)
                || sourceText.contains(getString(R.string.str_accessibility_uninstall));
    }

    /**
     * ???
     *
     * @param event
     * @param className
     * @param sourceText
     * @return
     */
    private boolean isApplicationUninstalledEvent(AccessibilityEvent event, String className, String sourceText) {

        return hasAccessibilityNodeInfoByText(event, R.string.str_accessibility_uninstalled)
                || hasAccessibilityNodeInfoByText(event, R.string.str_accessibility_uninstalled2)
                || hasAccessibilityNodeInfoByText(event, R.string.str_accessibility_uninstalled3);
    }

    /**
     * ??
     *
     * @param event
     * @param className
     * @param sourceText
     */
    private void processAlertDialogEvent(AccessibilityEvent event, String className, String sourceText) {
        Timber.d("");
        String eventText = event.getText().toString();

        if (eventText.contains(getString(R.string.str_accessibility_error))) {
            onInstallFail(event);
        } else if (!eventText.contains(getString(R.string.str_accessibility_uninstall))) {
            AccessibilityNodeInfo nodeInfo = getAccessibilityNodeInfoByText(event,
                    getString(R.string.btn_accessibility_ok));
            if (nodeInfo != null) {
                performClick(nodeInfo);
                nodeInfo.recycle();
            }
        } else if (eventText.contains(getString(R.string.str_accessibility_replace))
                || eventText.contains(getString(R.string.str_accessibility_replace1))) {
            AccessibilityNodeInfo nodeInfo = getAccessibilityNodeInfoByText(event,
                    getString(R.string.btn_accessibility_ok));
            if (nodeInfo != null) {
                performClick(nodeInfo);
                nodeInfo.recycle();
            }
        }
    }

    private void processLenovoEvent(AccessibilityEvent event, String className, String sourceText) {
        Timber.d("?");
        if (sourceText.contains(getString(R.string.str_accessibility_installed3))) {
            onApplicationInstalled(event);
        } else if (sourceText.contains(getString(R.string.str_accessibility_uninstalled3))) {
            onApplicationUninstalled(event);
        } else {
            onApplicationInstall(event, CLASS_NAME_WIDGET_TEXTVIEW);
        }
    }

    /**
     * 
     *
     * @param event
     */
    private void onInstallFail(AccessibilityEvent event) {
        Timber.e("");
        performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);

        //  sInstallList  prepare ???
        if (sInstallList != null && sInstallList.size() > 0) {
            AccessibilityNodeInfo validInfo = getValidAccessibilityNodeInfo(event, sInstallList);
            if (validInfo != null && processApplicationUninstalled(event) && validInfo.getText() != null) {
                String label = validInfo.getText().toString();
                removePackFromListByAppName(sInstallList, label);
                for (int i = 0; i < sInstallList.size(); i++) {
                    int key = sInstallList.keyAt(i);
                    Pack pack = sInstallList.get(key);
                    if (pack.getAppName().equals(label)) {
                        addPrepareInstallApplication(pack);
                        sInstallList.remove(key);
                        startActivity(IntentUtil.uninstallApp(pack.getPath()));
                        break;
                    }
                }
                validInfo.recycle();
            }
        }

    }

    private void onApplicationInstall(AccessibilityEvent event) {
        onApplicationInstall(event, DeviceUtil.isFlyme() ? CLASS_NAME_WIDGET_TEXTVIEW : CLASS_NAME_WIDGET_BUTTON,
                true, false);
    }

    private void onApplicationInstall(AccessibilityEvent event, boolean maybeValidate) {
        onApplicationInstall(event, DeviceUtil.isFlyme() ? CLASS_NAME_WIDGET_TEXTVIEW : CLASS_NAME_WIDGET_BUTTON,
                maybeValidate, false);
    }

    private void onApplicationInstall(AccessibilityEvent event, String nodeClassName) {
        onApplicationInstall(event, nodeClassName, true, false);
    }

    /**
     * 
     *
     * @param event Accessibility ?
     * @param nodeClassName ???
     * @param maybeValidate ??
     * @param isRetry ??
     */
    private void onApplicationInstall(final AccessibilityEvent event, final String nodeClassName,
            final boolean maybeValidate, boolean isRetry) {
        Timber.d("");

        // ????
        handler.postDelayed(handleInstallTimeout, INSTALL_TIMEOUT);

        if (!maybeValidate || isValidPackageEvent(event, sInstallList)) {
            AccessibilityNodeInfo nodeInfo = getAccessibilityNodeInfoByText(event,
                    getString(R.string.btn_accessibility_install));
            if (nodeInfo != null) {
                performClick(nodeInfo);
                nodeInfo.recycle();
                return;
            }
            nodeInfo = getAccessibilityNodeInfoByText(event, getString(R.string.btn_accessibility_allow_once));
            if (nodeInfo != null) {
                performClick(nodeInfo);
                nodeInfo.recycle();
                return;
            }
            nodeInfo = getAccessibilityNodeInfoByText(event, getString(R.string.btn_accessibility_next));
            if (nodeInfo != null) {
                performClick(nodeInfo);
                onApplicationInstall(event);
                nodeInfo.recycle();
            }
            nodeInfo = getAccessibilityNodeInfoByText(event,
                    getString(R.string.str_accessibility_replace_continue));
            if (nodeInfo != null) {
                performClick(nodeInfo);
                onApplicationInstall(event);
                nodeInfo.recycle();
            }

            // ?????
            if (nodeInfo == null && !isRetry) {
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        onApplicationInstall(event, nodeClassName, maybeValidate, true);
                    }
                }, INSTALL_RETRY_INTERVAL);
            }
        }

    }

    /**
     * ?
     *
     * @param event
     */
    private void onApplicationInstalled(AccessibilityEvent event) {
        Timber.d("?");

        // ??
        handler.removeCallbacks(handleInstallTimeout);

        AccessibilityNodeInfo validInfo = getValidAccessibilityNodeInfo(event, sInstallList);
        if (validInfo != null) {
            if (autoOpen.getValue()) {
                boolean openSuccess = openAfterInstalled(event);
                Timber.d("?%s", openSuccess);
            }
            String label = validInfo.getText().toString();
            removePackFromListByAppName(sInstallList, label, true);
            validInfo.recycle();
        }
    }

    private void removePackFromListByAppName(SparseArray<Pack> sInstallList, String label) {
        removePackFromListByAppName(sInstallList, label, false);
    }

    /**
     * ??
     *
     * @param list             ?
     * @param appName          ??
     * @param becauseInstalled ??
     */
    private void removePackFromListByAppName(SparseArray<Pack> list, String appName, boolean becauseInstalled) {
        for (int i = 0; i < list.size(); i++) {
            int key = list.keyAt(i);
            Pack pack = list.get(key);
            if (pack.getAppName().equals(appName)) {
                sInstallList.remove(key);
                if (becauseInstalled) {
                    showInstalledNotification(pack);
                }
                break;
            }
        }
        if (sInstallList.size() == 0 && sUninstallList.size() == 0) {
            enable = false;
        }
    }

    /**
     * ?
     *
     * @param pack
     */
    private void showInstalledNotification(Pack pack) {
        PendingIntent intent = PendingIntent.getActivity(this, 0,
                AndroidUtil.getOpenAppIntent(this, pack.getPackageName()), 0);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.msg_download_title, pack.getAppName(), pack.getVersionName()))
                .setSmallIcon(R.drawable.ic_stat_complete).setAutoCancel(true).setContentIntent(intent)
                .setContentText(getString(R.string.msg_install_complete));

        AndroidUtil.openApplication(this, pack.getPackageName());
        notificationManager.notify(pack.getId(), builder.build());
    }

    /**
     * ??
     *
     * @param event
     * @return
     */
    private boolean openAfterInstalled(AccessibilityEvent event) {
        AccessibilityNodeInfo eventInfo;
        if (event != null && event.getSource() != null) {
            eventInfo = event.getSource();
        } else {
            eventInfo = getRootInActiveWindow();
        }
        boolean success = false;
        if (eventInfo != null) {
            success = performEventAction(eventInfo, getString(R.string.btn_accessibility_run), false)
                    || performEventAction(eventInfo, getString(R.string.btn_accessibility_open), false);
            eventInfo.recycle();
        }
        return success;
    }

    /**
     * ???
     *
     * @param event
     * @return
     */
    private boolean closeAfterInstalled(AccessibilityEvent event) {
        performGlobalAction(GLOBAL_ACTION_BACK);
        return true;
        /*AccessibilityNodeInfo eventInfo;
        if (event != null && event.getSource() != null) {
        eventInfo = event.getSource();
        } else {
        eventInfo = getRootInActiveWindow();
        }
        boolean success = false;
        if (eventInfo != null) {
        success =
                performEventAction(eventInfo, getString(R.string.btn_accessibility_ok), false)
                        || performEventAction(eventInfo, getString(R.string.btn_accessibility_done), false)
                        || performEventAction(eventInfo, getString(R.string.btn_accessibility_complete), false)
                        || performEventAction(eventInfo, getString(R.string.btn_accessibility_know), false)
                        || performEventAction(eventInfo, getString(R.string.btn_accessibility_know), false)
                        || performEventAction(eventInfo, getString(R.string.btn_accessibility_run), true)
                        || performEventAction(eventInfo, getString(R.string.btn_accessibility_open), true);
        eventInfo.recycle();
        }
        return success;*/
    }

    /**
     * ?
     *
     * @param event
     */
    private void onApplicationUninstall(AccessibilityEvent event) {
        Timber.d("?");

        // ??????
        handler.postDelayed(handleUninstallTimeout, UNINSTALL_TIMEOUT);

        if (isValidPackageEvent(event, sUninstallList)) {
            AccessibilityNodeInfo nodeInfo = getAccessibilityNodeInfoByText(event,
                    getString(R.string.btn_accessibility_uninstall));
            if (nodeInfo != null) {
                performClick(nodeInfo);
                return;
            }
            nodeInfo = getAccessibilityNodeInfoByText(event, getString(R.string.btn_accessibility_ok));
            if (nodeInfo != null) {
                performClick(nodeInfo);
                nodeInfo.recycle();
            }
        }
    }

    /**
     * ??
     *
     * @param event
     */
    private void onApplicationUninstalled(AccessibilityEvent event) {
        Timber.d("??");

        // ???
        handler.removeCallbacks(handleUninstallTimeout);

        AccessibilityNodeInfo validInfo = getValidAccessibilityNodeInfo(event, sUninstallList);
        String label = null;
        if (validInfo != null && processApplicationUninstalled(event) && sUninstallList != null
                && validInfo.getText() != null) {
            label = validInfo.getText().toString();
            removePackFromListByAppName(sUninstallList, label);
            validInfo.recycle();
        }

        // ???Apk
        processPrepareInstall(label);
    }

    /**
     * ??
     *
     * @param event
     * @return
     */
    private boolean processApplicationUninstalled(AccessibilityEvent event) {
        performGlobalAction(GLOBAL_ACTION_BACK);
        return true;
        /*AccessibilityNodeInfo eventInfo = null;
        if (event != null && event.getSource() != null) {
        eventInfo = event.getSource();
        } else {
        eventInfo = getRootInActiveWindow();
        }
        boolean success = false;
        if (eventInfo != null) {
        success = performEventAction(eventInfo, getString(R.string.btn_accessibility_ok), false)
                || performEventAction(eventInfo, getString(R.string.btn_accessibility_know), false);
        eventInfo.recycle();
        }
        return success;*/
    }

    private boolean hasAccessibilityNodeInfoByText(AccessibilityEvent event, int resId) {
        return hasAccessibilityNodeInfoByText(event, getString(resId));
    }

    /**
     * ?AccessibilityNodeInfo
     *
     * @param event
     * @param text
     * @return
     */
    private boolean hasAccessibilityNodeInfoByText(AccessibilityEvent event, String text) {
        List<AccessibilityNodeInfo> nodes = null;
        if (event != null && event.getSource() != null) {
            nodes = event.getSource().findAccessibilityNodeInfosByText(text);
        } else {
            AccessibilityNodeInfo info = getRootInActiveWindow();
            if (info != null) {
                nodes = info.findAccessibilityNodeInfosByText(text);
            }
        }
        return !(nodes == null || nodes.size() <= 0);
    }

    /**
     * ?
     *
     * @param event
     * @param validPackageList
     * @return
     */
    private boolean isValidPackageEvent(AccessibilityEvent event, SparseArray<Pack> validPackageList) {
        return getValidAccessibilityNodeInfo(event, validPackageList) != null;
    }

    /**
     * ?AccessibilityNodeInfo
     *
     * @param event
     * @param validPackageList 
     * @return
     */
    private AccessibilityNodeInfo getValidAccessibilityNodeInfo(AccessibilityEvent event,
            SparseArray<Pack> validPackageList) {
        if (validPackageList != null && validPackageList.size() > 0) {
            for (int i = 0; i < validPackageList.size(); i++) {
                int key = validPackageList.keyAt(i);
                Pack pack = validPackageList.get(key);
                AccessibilityNodeInfo nodeInfo = getAccessibilityNodeInfoByText(event, pack.getAppName());
                if (nodeInfo != null) {
                    return nodeInfo;
                }
            }
        }
        return null;
    }

    /**
     * ?AccessibilityNodeInfo 
     *
     * @param event
     * @param text
     * @return
     */
    private AccessibilityNodeInfo getAccessibilityNodeInfoByText(AccessibilityEvent event, String text) {
        List<AccessibilityNodeInfo> nodes = null;
        // try-catch? event.getSource  NullPointerException
        try {
            if (event != null && event.getSource() != null) {
                nodes = event.getSource().findAccessibilityNodeInfosByText(text);
            }
        } catch (Exception e) {

        }

        // ?else???
        if (nodes == null || nodes.size() == 0) {
            AccessibilityNodeInfo info = getRootInActiveWindow();
            if (info != null) {
                nodes = info.findAccessibilityNodeInfosByText(text);
            }
        }
        if (nodes != null && nodes.size() > 0) {
            for (AccessibilityNodeInfo nodeInfo : nodes) {
                String nodeText = nodeInfo.getText() == null ? BuildConfig.VERSION_NAME
                        : nodeInfo.getText().toString();
                //nodeInfo.getClassName().equals(className) &&
                if (nodeText.equalsIgnoreCase(text)) {
                    return nodeInfo;
                }
                nodeInfo.recycle();
            }
        }
        return null;
    }

    /**
     * ?
     *
     * @param info
     * @param text
     * @param isGlobalAction ?
     * @return ??
     */
    private boolean performEventAction(AccessibilityNodeInfo info, String text, boolean isGlobalAction) {
        if (info == null) {
            return false;
        }
        List<AccessibilityNodeInfo> nodes = info.findAccessibilityNodeInfosByText(text);
        if (nodes != null && nodes.size() > 0) {
            for (AccessibilityNodeInfo nodeInfo : nodes) {
                String nodeText = nodeInfo.getText() == null ? null : nodeInfo.getText().toString();
                if (text.equalsIgnoreCase(nodeText)) {
                    if (isGlobalAction) {
                        //
                        performGlobalAction(GLOBAL_ACTION_BACK);
                    } else {
                        performClick(nodeInfo);
                    }
                    return true;
                }
                nodeInfo.recycle();
            }
        }
        return false;
    }

    /**
     * ?
     *
     * @param node
     * @return ??
     */
    private boolean performClick(AccessibilityNodeInfo node) {
        return node != null && node.isEnabled() && node.isClickable()
                && node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
    }
}