安卓端-APPUI自动化实战【上】

当前UI自动化测试存在以下问题:

1.投入产出比低:在目前版本快速迭代的大背景下,app更新较快,维护脚本成本高,导致投入产出比低

2.对测试人员要求较高:必须有一定的编程能力

3.运行稳定性较差,断言的可靠性不高。

 

如何解决以上问题,并且尽可能的减少重复造轮子的时间成本?

选择了支付宝开源的SoloPi自动化测试工具,在移动端上一个无线化、非侵入式、免Root的Android自动化专项测试工具,目前开放的有录制回放、性能测试、一机多控三项主要功能,能为测试开发人员节省宝贵时间。

github地址:GitHub - alipay/SoloPi: SoloPi 自动化测试工具

详细介绍:SoloPi:支付宝开源的Android专项测试工具_测试_温元良_InfoQ精选文章 

solopi的整体架构:这套方案中,底层依赖主要是“无线 ADB、系统辅助功能、Chrome 调试以及图像识别技术”。

accessibility service主要是获取视图,获取空间信息适用于native场景

ChromeDevTools主要适用于 h5和小程序场景,当前主流APP基本都是混合型APP,因此需要这种能力

图像识别 只要适用于游戏类APP

 

 

我们对solopiAPP进行了2次开发,主要实现了以下功能:

1.solopi可以与服务端(测试管理平台,以下简称服务端)通过websocket通信,无需数据线链接,在同一网段即可通信。

2.solopi录制用例,将用例上传到服务端,服务端集中管理用例,

3.服务端选择用例集下发到solopi,solopi接收后自动执行,执行完成后将结果上传到服务端,服务端可以查看测试报告。

  服务端下发用例支持顺序下发,

  服务端下发用例时可以实现模板替换,支持不同手机设备上动态化的替换指定的参数

 

关于solopi端:

一:通信

1.服务端链接地址配置:修改源码,手动输入服务端链接地址,并且点击确认按钮时,链接服务器(solopi链接服务端的时机)。

核心代码:

 /**
         * 注册点击事件
         */
        mServerSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() {
                    @Override
                    public void onDialogPositive(List<String> data) {
                        if (data.size() == 1) {//输入框弹窗输入数据长度判断为1,取第一个数据
                            String path = data.get(0);

                            //判断输入的服务端链接是否以ws开头,是则设置到textview里,不是则弹出弹窗
                            if (StringUtil.startWith(path, "ws")) {
                                SPService.putString(SPService.KEY_SERVER_SETTING, path);
                                RealTimeManage.openConnect();//每次修改服务端链接地址时,自动重连
                            } else {
                                DialogUtils.DialogInfo.Builder builder = new DialogUtils.DialogInfo.Builder();
                                builder.setTitle("地址有误");
                                builder.setShowContent("请设置webscoket地址,ws开头!!!");
                                builder.setPositiveButtonText("确认");
                                DialogUtils.createDialog(SettingsActivity.this,builder.build(), new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialogInterface, int i) {

                                    }
                                }).show();
                                return;
                            }

                            if (StringUtil.isEmpty(path)) {
                                mServerSettingInfo.setText(R.string.constant__not_config);
                            } else {
                                mServerSettingInfo.setText(path);
                            }
                        }
                    }
                }, getString(R.string.server_setting_url), Collections.singletonList(new Pair<>(getString(R.string.server_setting_url), SPService.getString(SPService.KEY_SERVER_SETTING))));
            }
        });

注册点击事件。判断输入的地址是否以ws开头,则成功设置并链接服务器,如果不是则弹窗提示。

详细代码如下:

/*
 * Copyright (C) 2015-present, Ant Financial Services Group
 *
 * 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.alipay.hulu.activity;

import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;

import androidx.appcompat.app.AlertDialog;

import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alipay.hulu.R;
import com.alipay.hulu.common.application.LauncherApplication;
import com.alipay.hulu.common.bean.DeviceInfo;
import com.alipay.hulu.common.service.SPService;
import com.alipay.hulu.common.tools.BackgroundExecutor;
import com.alipay.hulu.common.utils.AESUtils;
import com.alipay.hulu.common.utils.ClassUtil;
import com.alipay.hulu.common.utils.ContextUtil;
import com.alipay.hulu.common.utils.DeviceInfoUtil;
import com.alipay.hulu.common.utils.FileUtils;
import com.alipay.hulu.common.utils.LogUtil;
import com.alipay.hulu.common.utils.PatchProcessUtil;
import com.alipay.hulu.common.utils.StringUtil;
import com.alipay.hulu.common.utils.activity.FileChooseDialogActivity;
import com.alipay.hulu.common.utils.patch.PatchLoadResult;
import com.alipay.hulu.runManager.CaseRunMangage;
import com.alipay.hulu.runManager.JwebSocketClient;
import com.alipay.hulu.runManager.RealTimeManage;
import com.alipay.hulu.shared.io.bean.GeneralOperationLogBean;
import com.alipay.hulu.shared.io.bean.RecordCaseInfo;
import com.alipay.hulu.shared.io.db.GreenDaoManager;
import com.alipay.hulu.shared.io.db.RecordCaseInfoDao;
import com.alipay.hulu.shared.io.util.OperationStepUtil;
import com.alipay.hulu.shared.node.action.OperationMethod;
import com.alipay.hulu.shared.node.tree.export.bean.OperationStep;
import com.alipay.hulu.ui.HeadControlPanel;
import com.alipay.hulu.upgrade.PatchRequest;
import com.alipay.hulu.util.DialogUtils;
import com.alipay.hulu.util.DialogUtils.OnDialogResultListener;
import com.zhy.view.flowlayout.FlowLayout;
import com.zhy.view.flowlayout.TagAdapter;
import com.zhy.view.flowlayout.TagFlowLayout;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static com.alipay.hulu.util.DialogUtils.showMultipleEditDialog;

/**
 * Created by lezhou.wyl on 01/01/2018.
 */

public class SettingsActivity extends BaseActivity {
    private static final String TAG = "SettingsActivity";

    private static final int REQUEST_FILE_CHOOSE = 1101;

    private HeadControlPanel mPanel;

    private View mRecordUploadWrapper;
    private TextView mRecordUploadInfo;

    private View mServerSettingWrapper;
    private TextView mServerSettingInfo;

    private View mRecordScreenUploadWrapper;
    private TextView mRecordScreenUploadInfo;

    private View mPatchListWrapper;
    private TextView mPatchListInfo;

    private View mReplayOtherAppSettingWrapper;
    private TextView mReplayOtherAppInfo;

    private View mRestartAppSettingWrapper;
    private TextView mRestartAppInfo;

    private View mGlobalParamSettingWrapper;

    private View mResolutionSettingWrapper;
    private TextView mResolutionSettingInfo;

    private View mHightlightSettingWrapper;
    private TextView mHightlightSettingInfo;

    private View mLanguageSettingWrapper;
    private TextView mLanguageSettingInfo;

    private View mDisplaySystemAppSettingWrapper;
    private TextView mDisplaySystemAppSettingInfo;

    private View mAutoReplaySettingWrapper;
    private TextView mAutoReplaySettingInfo;

    private View mSkipAccessibilitySettingWrapper;
    private TextView mSkipAccessibilitySettingInfo;

    private View mMaxWaitSettingWrapper;
    private TextView mMaxWaitSettingInfo;

    private View mDefaultRotationSettingWrapper;
    private TextView mDefaultRotationSettingInfo;

    private View mChangeRotationSettingWrapper;
    private TextView mChangeRotationSettingInfo;

    private View mCheckUpdateSettingWrapper;
    private TextView mCheckUpdateSettingInfo;

    private View mBaseDirSettingWrapper;
    private TextView mBaseDirSettingInfo;

    private View mOutputCharsetSettingWrapper;
    private TextView mOutputCharsetSettingInfo;

    private View mAesSeedSettingWrapper;
    private TextView mAesSeedSettingInfo;

    private View mClearFilesSettingWrapper;
    private TextView mClearFilesSettingInfo;

    private View mHideLogSettingWrapper;
    private TextView mHideLogSettingInfo;

    private View mAdbServerSettingWrapper;
    private TextView mAdbServerSettingInfo;

    private View mImportCaseSettingWrapper;

    private View mImportPluginSettingWrapper;

    private View mAboutBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        initView();
        initListeners();
    }

    private void initListeners() {
        mPanel.setBackIconClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SettingsActivity.this.finish();
            }
        });

        mAboutBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                //startActivity(new Intent(SettingsActivity.this, InfoActivity.class));

                DeviceInfo deviceInfo = DeviceInfoUtil.generateDeviceInfo();

                CaseRunMangage.getInstance().run();


            }
        });

        mGlobalParamSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showGlobalParamEdit();
            }
        });

        mDefaultRotationSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setItems(R.array.default_screen_rotation, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                String item = getResources().getStringArray(R.array.default_screen_rotation)[which];
                                SPService.putInt(SPService.KEY_SCREEN_FACTOR_ROTATION, which);
                                if (which == 1 || which == 3) {
                                    SPService.putBoolean(SPService.KEY_SCREEN_ROTATION, true);
                                    mChangeRotationSettingInfo.setText(R.string.constant__yes);
                                } else {
                                    SPService.putBoolean(SPService.KEY_SCREEN_ROTATION, false);
                                    mChangeRotationSettingInfo.setText(R.string.constant__no);
                                }
                                mDefaultRotationSettingInfo.setText(item);
                            }
                        })
                        .setTitle(R.string.setting__set_screen_orientation)
                        .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        })
                        .show();
            }
        });

        mChangeRotationSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setMessage(R.string.setting__change_screen_axis)
                        .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SPService.putBoolean(SPService.KEY_SCREEN_ROTATION, true);
                                mChangeRotationSettingInfo.setText(R.string.constant__yes);
                            }
                        }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SPService.putBoolean(SPService.KEY_SCREEN_ROTATION, false);
                        mChangeRotationSettingInfo.setText(R.string.constant__no);
                    }
                }).show();
            }
        });

        mRecordUploadWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() {
                    @Override
                    public void onDialogPositive(List<String> data) {
                        if (data.size() == 1) {
                            String path = data.get(0);
                            SPService.putString(SPService.KEY_PERFORMANCE_UPLOAD, path);
                            if (StringUtil.isEmpty(path)) {
                                mRecordUploadInfo.setText(R.string.constant__not_config);
                            } else {
                                mRecordUploadInfo.setText(path);
                            }
                        }
                    }
                }, getString(R.string.settings__performance_upload_url), Collections.singletonList(new Pair<>(getString(R.string.settings__performance_upload_url), SPService.getString(SPService.KEY_PERFORMANCE_UPLOAD))));
            }
        });

        /**
         * 注册点击事件
         */
        mServerSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() {
                    @Override
                    public void onDialogPositive(List<String> data) {
                        if (data.size() == 1) {
                            String path = data.get(0);

                            //判断输入的服务端链接是否以ws开头,是则设置到textview里,不是则弹出弹窗
                            if (StringUtil.startWith(path, "ws")) {
                                SPService.putString(SPService.KEY_SERVER_SETTING, path);
                                RealTimeManage.openConnect();//每次修改服务端链接地址时,自动重连
                            } else {
                                DialogUtils.DialogInfo.Builder builder = new DialogUtils.DialogInfo.Builder();
                                builder.setTitle("地址有误");
                                builder.setShowContent("请设置webscoket地址,ws开头!!!");
                                builder.setPositiveButtonText("确认");
                                DialogUtils.createDialog(SettingsActivity.this,builder.build(), new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialogInterface, int i) {

                                    }
                                }).show();
                                return;
                            }

                            if (StringUtil.isEmpty(path)) {
                                mServerSettingInfo.setText(R.string.constant__not_config);
                            } else {
                                mServerSettingInfo.setText(path);
                            }
                        }
                    }
                }, getString(R.string.server_setting_url), Collections.singletonList(new Pair<>(getString(R.string.server_setting_url), SPService.getString(SPService.KEY_SERVER_SETTING))));
            }
        });

        mRecordScreenUploadWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() {
                    @Override
                    public void onDialogPositive(List<String> data) {
                        if (data.size() == 1) {
                            String path = data.get(0);
                            SPService.putString(SPService.KEY_RECORD_SCREEN_UPLOAD, path);
                            if (StringUtil.isEmpty(path)) {
                                mRecordScreenUploadInfo.setText(R.string.constant__not_config);
                            } else {
                                mRecordScreenUploadInfo.setText(path);
                            }
                        }
                    }
                }, getString(R.string.settings__record_upload_url), Collections.singletonList(new Pair<>(getString(R.string.settings__record_upload_url), SPService.getString(SPService.KEY_RECORD_SCREEN_UPLOAD))));
            }
        });

        mPatchListWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() {
                            @Override
                            public void onDialogPositive(List<String> data) {
                                if (data.size() == 1) {
                                    String path = data.get(0);
                                    SPService.putString(SPService.KEY_PATCH_URL, path);
                                    if (StringUtil.isEmpty(path)) {
                                        mPatchListInfo.setText(R.string.constant__not_config);
                                    } else {
                                        mPatchListInfo.setText(path);

                                        // 更新patch列表
                                        PatchRequest.updatePatchList(null);
                                    }
                                }
                            }
                        }, getString(R.string.settings__plugin_url),
                        Collections.singletonList(new Pair<>(getString(R.string.settings__plugin_url),
                                SPService.getString(SPService.KEY_PATCH_URL, "https://raw.githubusercontent.com/alipay/SoloPi/master/<abi>.json"))));
            }
        });

        mOutputCharsetSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new DialogUtils.OnDialogResultListener() {
                            @Override
                            public void onDialogPositive(List<String> data) {
                                if (data.size() == 1) {
                                    String charset = data.get(0);
                                    SPService.putString(SPService.KEY_OUTPUT_CHARSET, charset);
                                    mOutputCharsetSettingInfo.setText(charset);
                                }
                            }
                        }, getString(R.string.settings__output_charset),
                        Collections.singletonList(new Pair<>(getString(R.string.settings__output_charset),
                                SPService.getString(SPService.KEY_OUTPUT_CHARSET))));
            }
        });


        mLanguageSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setTitle(R.string.settings__language)
                        .setItems(R.array.language, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SPService.putInt(SPService.KEY_USE_LANGUAGE, which);
                                LauncherApplication.getInstance().setApplicationLanguage();

                                mLanguageSettingInfo.setText(getResources().getStringArray(R.array.language)[which]);
                                // 重启服务
                                LauncherApplication.getInstance().restartAllServices();

                                Intent intent = new Intent(SettingsActivity.this, SplashActivity.class);
                                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
                                startActivity(intent);
                                finish();
                            }
                        })
                        .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        }).show();
            }
        });

        mReplayOtherAppSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setMessage(R.string.settings__should_replay_in_other_app)
                        .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SPService.putBoolean(SPService.KEY_ALLOW_REPLAY_DIFFERENT_APP, true);
                                mReplayOtherAppInfo.setText(R.string.constant__yes);
                                dialog.dismiss();
                            }
                        }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SPService.putBoolean(SPService.KEY_ALLOW_REPLAY_DIFFERENT_APP, false);
                        mReplayOtherAppInfo.setText(R.string.constant__no);
                        dialog.dismiss();
                    }
                }).show();
            }
        });

        mRestartAppSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setMessage(R.string.settings__should_restart_before_replay)
                        .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SPService.putBoolean(SPService.KEY_RESTART_APP_ON_PLAY, true);
                                mRestartAppInfo.setText(R.string.constant__yes);
                                dialog.dismiss();
                            }
                        }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SPService.putBoolean(SPService.KEY_RESTART_APP_ON_PLAY, false);
                        mRestartAppInfo.setText(R.string.constant__no);
                        dialog.dismiss();
                    }
                }).show();
            }
        });

        mDisplaySystemAppSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setMessage(R.string.setting__display_system_app)
                        .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SPService.putBoolean(SPService.KEY_DISPLAY_SYSTEM_APP, true);
                                mDisplaySystemAppSettingInfo.setText(R.string.constant__yes);
                                MyApplication.getInstance().reloadAppList();
                                dialog.dismiss();
                            }
                        }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SPService.putBoolean(SPService.KEY_DISPLAY_SYSTEM_APP, false);
                        mDisplaySystemAppSettingInfo.setText(R.string.constant__no);
                        MyApplication.getInstance().reloadAppList();
                        dialog.dismiss();
                    }
                }).show();
            }
        });

        mAutoReplaySettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setMessage(R.string.setting__auto_replay)
                        .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SPService.putBoolean(SPService.KEY_REPLAY_AUTO_START, true);
                                mAutoReplaySettingInfo.setText(R.string.constant__yes);
                                dialog.dismiss();
                            }
                        }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SPService.putBoolean(SPService.KEY_REPLAY_AUTO_START, false);
                        mAutoReplaySettingInfo.setText(R.string.constant__no);
                        dialog.dismiss();
                    }
                }).show();
            }
        });

        mSkipAccessibilitySettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setMessage(R.string.setting__skip_accessibility)
                        .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SPService.putBoolean(SPService.KEY_SKIP_ACCESSIBILITY, true);
                                mSkipAccessibilitySettingInfo.setText(R.string.constant__yes);
                                dialog.dismiss();
                            }
                        }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SPService.putBoolean(SPService.KEY_SKIP_ACCESSIBILITY, false);
                        mSkipAccessibilitySettingInfo.setText(R.string.constant__no);
                        dialog.dismiss();
                    }
                }).show();
            }
        });

        mMaxWaitSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() {
                    @Override
                    public void onDialogPositive(List<String> data) {
                        if (data.size() == 1) {
                            String time = data.get(0);
                            SPService.putLong(SPService.KEY_MAX_WAIT_TIME, Long.parseLong(time));
                            mMaxWaitSettingInfo.setText(time + "ms");
                        }
                    }
                }, getString(R.string.settings__max_wait_time), Collections.singletonList(new Pair<>(getString(R.string.setting__max_wait_time), Long.toString(SPService.getLong(SPService.KEY_MAX_WAIT_TIME, 10000)))));
            }
        });

        mBaseDirSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FileChooseDialogActivity.startFileChooser(SettingsActivity.this,
                        REQUEST_FILE_CHOOSE, getString(R.string.settings__base_dir), "solopi",
                        FileUtils.getSolopiDir());
            }
        });

        mAesSeedSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() {
                    @Override
                    public void onDialogPositive(List<String> data) {
                        if (data.size() == 1) {
                            String seed = data.get(0);
                            String originSeed = SPService.getString(SPService.KEY_AES_KEY, getApplication().getPackageName());
                            SPService.putString(SPService.KEY_AES_KEY, seed);

                            // 发生了更新
                            if (!StringUtil.equals(originSeed, seed)) {
                                updateStoredRecords(originSeed, seed);
                            }
                            mAesSeedSettingInfo.setText(seed);
                        }
                    }
                }, getString(R.string.settings__encrept_key), Collections.singletonList(new Pair<>(getString(R.string.settings__encrept_key), SPService.getString(SPService.KEY_AES_KEY, "com.alipay.hulu"))));
            }
        });

        mClearFilesSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() {
                    @Override
                    public void onDialogPositive(List<String> data) {
                        if (data.size() == 1) {
                            String days = data.get(0);

                            if (StringUtil.isInteger(days)) {
                                int daysNum = Integer.parseInt(days);
                                if (daysNum < 0) {
                                    daysNum = -1;
                                }

                                SPService.putInt(SPService.KEY_AUTO_CLEAR_FILES_DAYS, daysNum);
                                mClearFilesSettingInfo.setText(days);
                            } else {
                                toastShort(R.string.settings__config_failed);
                            }
                        }
                    }
                }, getString(R.string.settings__auto_clean_time), Collections.singletonList(new Pair<>(getString(R.string.settings_auto_clean_hint), StringUtil.toString(SPService.getInt(SPService.KEY_AUTO_CLEAR_FILES_DAYS, 3)))));
            }
        });

        mResolutionSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                List<Pair<String, String>> data = new ArrayList<>(2);
                data.add(new Pair<>(getString(R.string.settings__screenshot_resolution), "" + SPService.getInt(SPService.KEY_SCREENSHOT_RESOLUTION, 720)));
                showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() {
                    @Override
                    public void onDialogPositive(List<String> data) {
                        if (data.size() != 2) {
                            LogUtil.e("SettingActivity", "获取编辑项少于两项");
                            return;
                        }

                        // 更新截图分辨率信息
                        SPService.putInt(SPService.KEY_SCREENSHOT_RESOLUTION, Integer.parseInt(data.get(0)));
                        mResolutionSettingInfo.setText(data.get(0) + "P");
                    }
                }, getString(R.string.settings__screenshot_setting), data);
            }
        });

        mHightlightSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setMessage(R.string.settings__highlight_node)
                        .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SPService.putBoolean(SPService.KEY_HIGHLIGHT_REPLAY_NODE, true);
                                mHightlightSettingInfo.setText(R.string.constant__yes);
                                dialog.dismiss();
                            }
                        }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SPService.putBoolean(SPService.KEY_HIGHLIGHT_REPLAY_NODE, false);
                        mHightlightSettingInfo.setText(R.string.constant__no);
                        dialog.dismiss();
                    }
                }).show();
            }
        });

        // check update
        mCheckUpdateSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme)
                        .setMessage(R.string.settings__check_update)
                        .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SPService.putBoolean(SPService.KEY_CHECK_UPDATE, true);
                                mCheckUpdateSettingInfo.setText(R.string.constant__yes);
                                dialog.dismiss();
                            }
                        }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SPService.putBoolean(SPService.KEY_CHECK_UPDATE, false);
                        mCheckUpdateSettingInfo.setText(R.string.constant__no);
                        dialog.dismiss();
                    }
                }).show();
            }
        });

        // adb调试地址
        mAdbServerSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showMultipleEditDialog(SettingsActivity.this, new DialogUtils.OnDialogResultListener() {
                            @Override
                            public void onDialogPositive(List<String> data) {
                                if (data.size() == 1) {
                                    String server = data.get(0);
                                    SPService.putString(SPService.KEY_ADB_SERVER, server);
                                    mAdbServerSettingInfo.setText(server);
                                }
                            }
                        }, getString(R.string.settings__adb_server),
                        Collections.singletonList(new Pair<>(getString(R.string.settings__adb_server),
                                SPService.getString(SPService.KEY_ADB_SERVER, "localhost:5555"))));
            }
        });

        mHideLogSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme).setMessage(R.string.settings__whether_hide_node_info)
                        .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                mHideLogSettingInfo.setText(R.string.constant__yes);
                                SPService.putBoolean(SPService.KEY_HIDE_LOG, true);
                            }
                        })
                        .setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                                mHideLogSettingInfo.setText(R.string.constant__no);
                                SPService.putBoolean(SPService.KEY_HIDE_LOG, false);
                            }
                        })
                        .setCancelable(true).show();
            }
        });

        mImportCaseSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showProgressDialog(getString(R.string.settings__load_extrenal_case));
                BackgroundExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        File importDir = FileUtils.getSubDir("import");
                        File[] subFiles = importDir.listFiles();

                        int count = 0;
                        if (subFiles != null) {
                            for (File sub : subFiles) {
                                // 格式校验
                                if (sub.isFile() && StringUtil.contains(sub.getName(), ".json")) {
                                    try {
                                        BufferedReader reader = new BufferedReader(new FileReader(sub));
                                        StringBuilder sb = new StringBuilder();
                                        char[] chars = new char[1024];
                                        int readCount;

                                        while ((readCount = reader.read(chars, 0, 1024)) > 0) {
                                            sb.append(chars, 0, readCount);
                                        }

                                        reader.close();

                                        // 加载实例
                                        RecordCaseInfo caseInfo = JSON.parseObject(sb.toString(), RecordCaseInfo.class);
                                        String operationLog = caseInfo.getOperationLog();
                                        GeneralOperationLogBean log = JSON.parseObject(operationLog, GeneralOperationLogBean.class);
                                        OperationStepUtil.beforeStore(log);
                                        caseInfo.setOperationLog(JSON.toJSONString(log));

                                        GreenDaoManager.getInstance().getRecordCaseInfoDao().insert(caseInfo);

                                        // 导入完毕后删除
                                        sub.delete();
                                        count++;
                                    } catch (FileNotFoundException e) {
                                        LogUtil.e(TAG, "Catch java.io.FileNotFoundException: " + e.getMessage(), e);
                                    } catch (IOException e) {
                                        LogUtil.e(TAG, "Catch java.io.IOException: " + e.getMessage(), e);
                                    } catch (JSONException e) {
                                        LogUtil.e(TAG, e, "无法解析文件【%s】", StringUtil.hide(sub.getAbsoluteFile()));
                                    } catch (Exception e) {
                                        LogUtil.e(TAG, "Catch Exception: " + e.getMessage(), e);
                                    }
                                }
                            }
                        }

                        dismissProgressDialog();

                        toastLong(getString(R.string.settings__load_count_case, count));
                    }
                });
            }
        });

        mImportPluginSettingWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showProgressDialog(getString(R.string.settings__load_plugin));
                BackgroundExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        File f = FileUtils.getSubDir("patch");
                        if (f.exists() && f.isDirectory()) {
                            File[] subFiles = f.listFiles(new FilenameFilter() {
                                @Override
                                public boolean accept(File dir, String name) {
                                    return name.endsWith(".zip");
                                }
                            });

                            if (subFiles != null && subFiles.length > 0) {
                                for (File patch : subFiles) {
                                    try {
                                        PatchLoadResult result = PatchProcessUtil.dynamicLoadPatch(patch);
                                        if (result != null) {
                                            ClassUtil.installPatch(result);
                                            toastShort(getString(R.string.settings__load_success, result.name));
                                            patch.delete();
                                        } else {
                                            LogUtil.e("Settings", "插件安装失败");
                                            toastShort(getString(R.string.settings__load_failed));
                                        }
                                    } catch (Throwable e) {
                                        LogUtil.e("Settings", "加载插件异常", e);
                                        toastShort(getString(R.string.settings__load_failed));
                                    }
                                }
                            }
                        }

                        // 隐藏进度
                        dismissProgressDialog();
                    }
                });
            }
        });
    }

    private void initView() {
        mPanel = (HeadControlPanel) findViewById(R.id.head_layout);
        mPanel.setMiddleTitle(getString(R.string.activity__setting));

        mRecordScreenUploadWrapper = findViewById(R.id.recordscreen_upload_setting_wrapper);
        mRecordScreenUploadInfo = (TextView) findViewById(R.id.recordscreen_upload_setting_info);
        String path = SPService.getString(SPService.KEY_RECORD_SCREEN_UPLOAD);
        if (StringUtil.isEmpty(path)) {
            mRecordScreenUploadInfo.setText(R.string.settings__unset);
        } else {
            mRecordScreenUploadInfo.setText(path);
        }

        mRecordUploadWrapper = findViewById(R.id.performance_upload_setting_wrapper);
        mRecordUploadInfo = (TextView) findViewById(R.id.performance_upload_setting_info);
        path = SPService.getString(SPService.KEY_PERFORMANCE_UPLOAD);
        if (StringUtil.isEmpty(path)) {
            mRecordUploadInfo.setText(R.string.settings__unset);
        } else {
            mRecordUploadInfo.setText(path);
        }


        mServerSettingWrapper = findViewById(R.id.server_setting_wrapper);
        mServerSettingInfo = (TextView) findViewById(R.id.server_setting_info);
        path = SPService.getString(SPService.KEY_SERVER_SETTING);
        if (StringUtil.isEmpty(path)) {
            mServerSettingInfo.setText(R.string.settings__unset);
        } else {
            mServerSettingInfo.setText(path);
        }


        mPatchListWrapper = findViewById(R.id.patch_list_setting_wrapper);
        mPatchListInfo = (TextView) findViewById(R.id.patch_list_setting_info);
        path = SPService.getString(SPService.KEY_PATCH_URL,
                "https://raw.githubusercontent.com/alipay/SoloPi/master/<abi>.json");
        if (StringUtil.isEmpty(path)) {
            mPatchListInfo.setText(R.string.settings__unset);
        } else {
            mPatchListInfo.setText(path);
        }
        mGlobalParamSettingWrapper = findViewById(R.id.global_param_setting_wrapper);

        mDefaultRotationSettingWrapper = findViewById(R.id.default_screen_rotation_setting_wrapper);
        mDefaultRotationSettingInfo = _findViewById(R.id.default_screen_rotation_setting_info);
        int defaultRotation = SPService.getInt(SPService.KEY_SCREEN_FACTOR_ROTATION, 0);
        String[] arrays = getResources().getStringArray(R.array.default_screen_rotation);
        mDefaultRotationSettingInfo.setText(arrays[defaultRotation]);

        mChangeRotationSettingWrapper = findViewById(R.id.change_rotation_setting_wrapper);
        mChangeRotationSettingInfo = _findViewById(R.id.change_rotation_setting_info);
        boolean changeRotation = SPService.getBoolean(SPService.KEY_SCREEN_ROTATION, false);
        mChangeRotationSettingInfo.setText(changeRotation ? R.string.constant__yes : R.string.constant__no);


        mOutputCharsetSettingWrapper = findViewById(R.id.output_charset_setting_wrapper);
        mOutputCharsetSettingInfo = (TextView) findViewById(R.id.output_charset_setting_info);
        mOutputCharsetSettingInfo.setText(SPService.getString(SPService.KEY_OUTPUT_CHARSET, "GBK"));

        mResolutionSettingWrapper = findViewById(R.id.screenshot_resolution_setting_wrapper);
        mResolutionSettingInfo = (TextView) findViewById(R.id.screenshot_resolution_setting_info);
        mResolutionSettingInfo.setText(SPService.getInt(SPService.KEY_SCREENSHOT_RESOLUTION, 720) + "P");

        mHightlightSettingWrapper = findViewById(R.id.replay_highlight_setting_wrapper);
        mHightlightSettingInfo = (TextView) findViewById(R.id.replay_highlight_setting_info);
        mHightlightSettingInfo.setText(SPService.getBoolean(SPService.KEY_HIGHLIGHT_REPLAY_NODE, true) ? R.string.constant__yes : R.string.constant__no);

        mLanguageSettingWrapper = findViewById(R.id.language_setting_wrapper);
        mLanguageSettingInfo = (TextView) findViewById(R.id.language_setting_info);
        int pos = SPService.getInt(SPService.KEY_USE_LANGUAGE, 0);
        String[] availableLanguages = getResources().getStringArray(R.array.language);
        if (availableLanguages != null && availableLanguages.length > pos) {
            mLanguageSettingInfo.setText(availableLanguages[pos]);
        } else {
            mLanguageSettingInfo.setText(availableLanguages[0]);
        }

        mDisplaySystemAppSettingWrapper = findViewById(R.id.display_system_app_setting_wrapper);
        mDisplaySystemAppSettingInfo = (TextView) findViewById(R.id.display_system_app_setting_info);
        boolean displaySystemApp = SPService.getBoolean(SPService.KEY_DISPLAY_SYSTEM_APP, false);
        if (displaySystemApp) {
            mDisplaySystemAppSettingInfo.setText(R.string.constant__yes);
        } else {
            mDisplaySystemAppSettingInfo.setText(R.string.constant__no);
        }

        mAutoReplaySettingWrapper = findViewById(R.id.auto_replay_setting_wrapper);
        mAutoReplaySettingInfo = (TextView) findViewById(R.id.auto_replay_setting_info);
        boolean autoReplay = SPService.getBoolean(SPService.KEY_REPLAY_AUTO_START, false);
        if (autoReplay) {
            mAutoReplaySettingInfo.setText(R.string.constant__yes);
        } else {
            mAutoReplaySettingInfo.setText(R.string.constant__no);
        }

        mReplayOtherAppSettingWrapper = findViewById(R.id.replay_other_app_setting_wrapper);
        mReplayOtherAppInfo = _findViewById(R.id.replay_other_app_setting_info);
        boolean replayOtherApp = SPService.getBoolean(SPService.KEY_ALLOW_REPLAY_DIFFERENT_APP, false);
        mReplayOtherAppInfo.setText(replayOtherApp ? R.string.constant__yes : R.string.constant__no);

        mRestartAppSettingWrapper = findViewById(R.id.restart_app_setting_wrapper);
        mRestartAppInfo = _findViewById(R.id.restart_app_setting_info);
        boolean restartApp = SPService.getBoolean(SPService.KEY_RESTART_APP_ON_PLAY, true);
        mRestartAppInfo.setText(restartApp ? R.string.constant__yes : R.string.constant__no);

        mAdbServerSettingWrapper = findViewById(R.id.adb_server_setting_wrapper);
        mAdbServerSettingInfo = _findViewById(R.id.adb_server_setting_info);
        mAdbServerSettingInfo.setText(SPService.getString(SPService.KEY_ADB_SERVER, "localhost:5555"));

        mSkipAccessibilitySettingWrapper = findViewById(R.id.skip_accessibility_setting_wrapper);
        mSkipAccessibilitySettingInfo = (TextView) findViewById(R.id.skip_accessibility_setting_info);
        boolean skipAccessibility = SPService.getBoolean(SPService.KEY_SKIP_ACCESSIBILITY, true);
        if (skipAccessibility) {
            mSkipAccessibilitySettingInfo.setText(R.string.constant__yes);
        } else {
            mSkipAccessibilitySettingInfo.setText(R.string.constant__no);
        }

        mMaxWaitSettingWrapper = findViewById(R.id.max_wait_setting_wrapper);
        mMaxWaitSettingInfo = (TextView) findViewById(R.id.max_wait_setting_info);
        long maxWaitTime = SPService.getLong(SPService.KEY_MAX_WAIT_TIME, 10000L);
        mMaxWaitSettingInfo.setText(maxWaitTime + "ms");

        mCheckUpdateSettingWrapper = findViewById(R.id.check_update_setting_wrapper);
        mCheckUpdateSettingInfo = (TextView) findViewById(R.id.check_update_setting_info);
        boolean checkUpdate = SPService.getBoolean(SPService.KEY_CHECK_UPDATE, true);
        if (checkUpdate) {
            mCheckUpdateSettingInfo.setText(R.string.constant__yes);
        } else {
            mCheckUpdateSettingInfo.setText(R.string.constant__no);
        }

        mBaseDirSettingWrapper = findViewById(R.id.base_dir_setting_wrapper);
        mBaseDirSettingInfo = (TextView) findViewById(R.id.base_dir_setting_info);
        mBaseDirSettingInfo.setText(FileUtils.getSolopiDir().getPath());

        mAesSeedSettingWrapper = findViewById(R.id.aes_seed_setting_wrapper);
        mAesSeedSettingInfo = (TextView) findViewById(R.id.aes_seed_setting_info);
        mAesSeedSettingInfo.setText(SPService.getString(SPService.KEY_AES_KEY, AESUtils.DEFAULT_AES_KEY));

        mClearFilesSettingWrapper = findViewById(R.id.clear_files_setting_wrapper);
        mClearFilesSettingInfo = (TextView) findViewById(R.id.clear_files_setting_info);

        mHideLogSettingWrapper = findViewById(R.id.hide_log_setting_wrapper);
        mHideLogSettingInfo = (TextView) findViewById(R.id.hide_log_setting_info);
        boolean hideLog = SPService.getBoolean(SPService.KEY_HIDE_LOG, true);
        if (hideLog) {
            mHideLogSettingInfo.setText(R.string.constant__yes);
        } else {
            mHideLogSettingInfo.setText(R.string.constant__no);
        }

        mImportCaseSettingWrapper = findViewById(R.id.import_case_setting_wrapper);
        // 设置下引入地址
        TextView importPath = (TextView) findViewById(R.id.import_case_setting_path);
        importPath.setText(FileUtils.getSubDir("import").getAbsolutePath());

        mImportPluginSettingWrapper = findViewById(R.id.import_patch_setting_wrapper);
        // 设置下引入地址
        TextView importPluginPath = (TextView) findViewById(R.id.import_patch_setting_path);
        importPluginPath.setText(FileUtils.getSubDir("patch").getAbsolutePath());


        findViewById(R.id.plugin_list_setting_wrapper).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(SettingsActivity.this, PatchStatusActivity.class));
            }
        });

        int clearDays = SPService.getInt(SPService.KEY_AUTO_CLEAR_FILES_DAYS, 3);
        mClearFilesSettingInfo.setText(StringUtil.toString(clearDays));

        mAboutBtn = findViewById(R.id.about_wrapper);
    }


    /**
     * 展示全局变量配置窗口
     */
    private void showGlobalParamEdit() {
        final List<Pair<String, String>> paramList = new ArrayList<>();

        String globalParam = SPService.getString(SPService.KEY_GLOBAL_SETTINGS);
        JSONObject params = JSON.parseObject(globalParam);
        if (params != null && params.size() > 0) {
            for (String key : params.keySet()) {
                paramList.add(new Pair<>(key, params.getString(key)));
            }
        }

        final LayoutInflater inflater = LayoutInflater.from(ContextUtil.getContextThemeWrapper(
                SettingsActivity.this, R.style.AppDialogTheme));
        final View view = inflater.inflate(R.layout.dialog_global_param_setting, null);
        final TagFlowLayout tagFlowLayout = (TagFlowLayout) view.findViewById(R.id.global_param_group);
        final EditText paramName = (EditText) view.findViewById(R.id.global_param_name);
        final EditText paramValue = (EditText) view.findViewById(R.id.global_param_value);
        View paramAdd = view.findViewById(R.id.global_param_add);

        tagFlowLayout.setAdapter(new TagAdapter<Pair<String, String>>(paramList) {
            @Override
            public View getView(FlowLayout parent, int position, Pair<String, String> o) {
                View root = inflater.inflate(R.layout.item_param_info, parent, false);

                TextView title = (TextView) root.findViewById(R.id.batch_execute_tag_name);
                title.setText(getString(R.string.settings__global_param_key_value, o.first, o.second));
                return root;
            }
        });
        tagFlowLayout.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() {
            @Override
            public boolean onTagClick(View view, int position, FlowLayout parent) {
                paramList.remove(position);
                tagFlowLayout.getAdapter().notifyDataChanged();
                return true;
            }
        });

        paramAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String key = paramName.getText().toString().trim();
                String value = paramValue.getText().toString().trim();
                if (StringUtil.isEmpty(key) || key.contains("=")) {
                    toastShort(getString(R.string.setting__invalid_param_name));
                }

                // 清空输入框
                paramName.setText("");
                paramValue.setText("");

                int replacePosition = -1;
                for (int i = 0; i < paramList.size(); i++) {
                    if (key.equals(paramList.get(i).first)) {
                        replacePosition = i;
                        break;
                    }
                }

                // 如果有相同的,就进行替换
                if (replacePosition > -1) {
                    paramList.set(replacePosition, new Pair<>(key, value));
                } else {
                    paramList.add(new Pair<>(key, value));
                }

                tagFlowLayout.getAdapter().notifyDataChanged();
            }
        });

        new AlertDialog.Builder(SettingsActivity.this, R.style.AppDialogTheme)
                .setView(view)
                .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        JSONObject newGlobalParam = new JSONObject(paramList.size() + 1);
                        for (Pair<String, String> param : paramList) {
                            newGlobalParam.put(param.first, param.second);
                        }
                        SPService.putString(SPService.KEY_GLOBAL_SETTINGS, newGlobalParam.toJSONString());
                        dialog.dismiss();
                    }
                }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        }).setCancelable(true)
                .show();
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_FILE_CHOOSE) {
            if (resultCode == RESULT_OK) {
                String targetFile = data.getStringExtra(FileChooseDialogActivity.KEY_TARGET_FILE);
                if (!StringUtil.isEmpty(targetFile)) {
                    SPService.putString(SPService.KEY_BASE_DIR, targetFile);
                    mBaseDirSettingInfo.setText(targetFile);
                    FileUtils.setSolopiBaseDir(targetFile);
                }
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    /**
     * 更新存储的用例
     *
     * @param oldSeed
     * @param newSeed
     */
    private void updateStoredRecords(final String oldSeed, final String newSeed) {
        showProgressDialog(getString(R.string.settings__start_update_cases));
        BackgroundExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    final List<RecordCaseInfo> cases = GreenDaoManager.getInstance().getRecordCaseInfoDao().queryBuilder()
                            .orderDesc(RecordCaseInfoDao.Properties.GmtCreate)
                            .build().list();

                    if (cases != null && cases.size() > 0) {
                        for (int i = 0; i < cases.size(); i++) {
                            showProgressDialog(getString(R.string.settings__updating_cases, i + 1, cases.size()));
                            RecordCaseInfo caseInfo = cases.get(i);
                            GeneralOperationLogBean generalOperation;
                            try {
                                generalOperation = JSON.parseObject(caseInfo.getOperationLog(), GeneralOperationLogBean.class);
                            } catch (Exception e) {
                                LogUtil.e(TAG, "parseOperation failed: " + e.getMessage(), e);
                                continue;
                            }

                            // 如果没拿到数据
                            if (generalOperation == null) {
                                continue;
                            }

                            // load file content
                            OperationStepUtil.afterLoad(generalOperation);

                            List<OperationStep> steps = generalOperation.getSteps();
                            if (generalOperation.getSteps() != null) {
                                for (OperationStep step : steps) {
                                    OperationMethod method = step.getOperationMethod();
                                    if (method.isEncrypt()) {
                                        Map<String, String> params = method.getOperationParam();
                                        for (String key : params.keySet()) {
                                            // 逐个参数替换
                                            try {
                                                String originValue = AESUtils.decrypt(params.get(key), oldSeed);
                                                params.put(key, AESUtils.encrypt(originValue, newSeed));
                                            } catch (Exception e) {
                                                LogUtil.e(TAG, "process key=" + key + " failed, " + e.getMessage(), e);
                                                // 不阻碍其他操作执行
                                            }
                                        }
                                    }
                                }
                            }
                            OperationStepUtil.beforeStore(generalOperation);

                            // 更新operationLog字段
                            caseInfo.setOperationLog(JSON.toJSONString(generalOperation));
                            GreenDaoManager.getInstance().getRecordCaseInfoDao().update(caseInfo);
                        }
                    }
                } catch (Throwable t) {
                    LogUtil.e(TAG, "Update aes seed throw " + t.getMessage(), t);
                } finally {
                    // 隐藏进度窗口
                    dismissProgressDialog();
                }
            }
        });
    }
}
View Code

 

2.启动APP时创建websocket链接:

核心代码:

    //链接服务器
        PermissionUtil.requestPermissions(Arrays.asList("adb"), IndexActivity.this, new PermissionUtil.OnPermissionCallback() {
            @Override
            public void onPermissionResult(boolean result, String reason) {
                if (result){
                    RealTimeManage.openConnect();
                }else {
                    LauncherApplication.getInstance().showToast("权限申请未通过,不支持链接");
                }
            }
        });

该回调方法主要是判断设备是否开启adb权限(有该权限才能拿到设备信息),有权限则链接服务器,无权限则toast提示。

详细代码如下:

/*
 * Copyright (C) 2015-present, Ant Financial Services Group
 *
 * 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.alipay.hulu.activity;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.hulu.R;
import com.alipay.hulu.activity.entry.EntryActivity;
import com.alipay.hulu.bean.GithubReleaseBean;
import com.alipay.hulu.common.application.LauncherApplication;
import com.alipay.hulu.common.bean.DeviceInfo;
import com.alipay.hulu.common.constant.Constant;
import com.alipay.hulu.common.service.SPService;
import com.alipay.hulu.common.tools.BackgroundExecutor;
import com.alipay.hulu.common.tools.CmdTools;
import com.alipay.hulu.common.utils.ClassUtil;
import com.alipay.hulu.common.utils.ContextUtil;
import com.alipay.hulu.common.utils.DeviceInfoUtil;
import com.alipay.hulu.common.utils.FileUtils;
import com.alipay.hulu.common.utils.LogUtil;
import com.alipay.hulu.common.utils.PermissionUtil;
import com.alipay.hulu.common.utils.StringUtil;
import com.alipay.hulu.event.ScanSuccessEvent;
import com.alipay.hulu.runManager.RealTimeManage;
import com.alipay.hulu.ui.ColorFilterRelativeLayout;
import com.alipay.hulu.ui.HeadControlPanel;
import com.alipay.hulu.upgrade.PatchRequest;
import com.alipay.hulu.util.SystemUtil;
import com.alipay.hulu.util.UpgradeUtil;
import com.alipay.hulu.util.ZipUtil;

import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Created by lezhou.wyl on 2018/1/28.
 */

public class IndexActivity extends BaseActivity {
    private static final String TAG = IndexActivity.class.getSimpleName();

    private HeadControlPanel mPanel;
    private GridView mGridView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_index);

        initView();
        initData();
        loadOthers();

        //链接服务器
        PermissionUtil.requestPermissions(Arrays.asList("adb"), IndexActivity.this, new PermissionUtil.OnPermissionCallback() {
            @Override
            public void onPermissionResult(boolean result, String reason) {
                if (result){
                    RealTimeManage.openConnect();
                }else {
                    LauncherApplication.getInstance().showToast("权限申请未通过,不支持链接");
                }
            }
        });


        // check update
        if (SPService.getBoolean(SPService.KEY_CHECK_UPDATE, true)) {
            BackgroundExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    UpgradeUtil.checkForUpdate(new UpgradeUtil.CheckUpdateListener() {
                        @Override
                        public void onNoUpdate() {

                        }

                        @Override
                        public void onNewUpdate(final GithubReleaseBean release) {
                            Parser parser = Parser.builder().build();
                            Node document = parser.parse(release.getBody());

                            // text size 16dp
                            int px = ContextUtil.dip2px(IndexActivity.this, 16);
                            HtmlRenderer renderer = HtmlRenderer.builder().build();
                            String css = "<html><header><style type=\"text/css\"> img {" +
                                    "width:100%;" +
                                    "height:auto;" +
                                    "}" +
                                    "body {" +
                                    "margin-right:30px;" +
                                    "margin-left:30px;" +
                                    "margin-top:30px;" +
                                    "font-size:" + px + "px;" +
                                    "word-wrap:break-word;" +
                                    "}" +
                                    "</style></header>";
                            final String content = css + renderer.render(document) + "</html>";

                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    WebView webView = new WebView(IndexActivity.this);
                                    WebSettings webSettings = webView.getSettings();
                                    webSettings.setUseWideViewPort(true);
                                    webSettings.setLoadWithOverviewMode(true);
                                    webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
                                    webView.loadData(content, null, null);
                                    new AlertDialog.Builder(IndexActivity.this).setTitle(getString(R.string.index__new_version, release.getTag_name()))
                                            .setView(webView)
                                            .setPositiveButton(R.string.index__go_update, new DialogInterface.OnClickListener() {

                                                public void onClick(DialogInterface dialog, int which) {
                                                    Uri uri = Uri.parse(release.getHtml_url());
                                                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                                                    startActivity(intent);
                                                }
                                            }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            dialog.dismiss();
                                        }
                                    }).show();
                                }
                            });
                        }

                        @Override
                        public void onUpdateFailed(Throwable t) {

                        }
                    });
                }
            });
        }
    }

    /**
     * 初始化界面
     */
    private void initView() {
        mPanel = (HeadControlPanel) findViewById(R.id.head_layout);
        mPanel.setMiddleTitle(getString(R.string.app_name));
        mPanel.setInfoIconClickListener(R.drawable.icon_config, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(IndexActivity.this, SettingsActivity.class));
            }
        });
        mGridView = (GridView) findViewById(R.id.tools_grid);
    }

    /**
     * 加载内容
     */
    private void initData() {
        Map<String, Entry> entryList = new HashMap<>();

        List<Class<? extends Activity>> activities = ClassUtil.findSubClass(Activity.class, EntryActivity.class);

        // 配置唯一entry
        for (Class<? extends Activity> activityClass: activities) {
            // 配置
            Entry target = new Entry(activityClass.getAnnotation(EntryActivity.class), activityClass);
            if (entryList.containsKey(target.name)) {
                if (entryList.get(target.name).level < target.level) {
                    entryList.put(target.name, target);
                }
            } else {
                entryList.put(target.name, target);
            }
        }

        List<Entry> entries = new ArrayList<>(entryList.values());
        // 从大到小排
        Collections.sort(entries, new Comparator<Entry>() {
            @Override
            public int compare(Entry o1, Entry o2) {
                return o1.index - o2.index;
            }
        });
        mPanel.setLeftIconClickListener(R.drawable.icon_scan, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PermissionUtil.requestPermissions(Collections.singletonList(Manifest.permission.CAMERA), IndexActivity.this, new PermissionUtil.OnPermissionCallback() {
                    @Override
                    public void onPermissionResult(boolean result, String reason) {
                        Intent intent = new Intent(IndexActivity.this, QRScanActivity.class);
                        intent.putExtra(QRScanActivity.KEY_SCAN_TYPE, ScanSuccessEvent.SCAN_TYPE_OTHER);
                        startActivity(intent);
                    }
                });
            }
        });

        CustomAdapter adapter = new CustomAdapter(this, entries);
        if (entries.size() <= 3) {
            mGridView.setNumColumns(1);
        } else {
            mGridView.setNumColumns(2);
        }
        mGridView.setAdapter(adapter);

        // 有写权限,申请下
        PatchRequest.updatePatchList(null);
    }

    /**
     * 加载其他信息
     */
    private void loadOthers() {
        BackgroundExecutor.execute(new Runnable() {
            @Override
            public void run() {
                // 检查是否需要上报故障日志
                checkErrorLog();

                // 读取外部的ADB秘钥
                readOuterAdbKey();
            }
        });
    }

    /**
     * 检查是否有需要上报的Crash日志
     */
    private void checkErrorLog() {
        final Pattern pattern = Pattern.compile("\\d+\\.log");
        long lastCheckTime = SPService.getLong(SPService.KEY_ERROR_CHECK_TIME, System.currentTimeMillis());
        File errorDir = FileUtils.getSubDir("error");
        if (errorDir.exists() && errorDir.isDirectory()) {
            File[] children = errorDir.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return pathname.isFile() && pattern.matcher(pathname.getName()).matches();
                }
            });

            if (children != null && children.length > 0) {
                for (final File errorLog: children) {
                    final long time = errorLog.lastModified();
                    if (time > lastCheckTime) {
                        // 只上传一条,根据修改时间查看
                        LauncherApplication.getInstance().showDialog(
                                IndexActivity.this,
                                getString(R.string.index__find_error_log), getString(R.string.constant__sure), new Runnable() {
                                    @Override
                                    public void run() {
                                        reportError(time, errorLog);
                                    }
                                }, getString(R.string.constant__cancel), null);
                        break;
                    }
                }
            }
        }

        SPService.putLong(SPService.KEY_ERROR_CHECK_TIME, System.currentTimeMillis());
    }

    /**
     * 上报Crash日志
     * @param errorTime
     * @param errorLog
     */
    private void reportError(final long errorTime, final File errorLog) {
        BackgroundExecutor.execute(new Runnable() {
            @Override
            public void run() {
                File logsFolder = new File(getExternalCacheDir(), "logs");
                Date date = new Date(errorTime);
                final String targetDay = String.format(Locale.CHINA, "%d%d%d", date.getYear() + 1900,
                        date.getMonth() + 1, date.getDate());
                final List<File> reportLogs = new ArrayList<>();
                reportLogs.add(errorLog);
                if (logsFolder.exists() && logsFolder.isDirectory()) {
                    File[] childrenFiles = logsFolder.listFiles(new FilenameFilter() {
                        @Override
                        public boolean accept(File dir, String name) {
                            return name.startsWith(targetDay) && name.endsWith(".log");
                        }
                    });

                    if (childrenFiles != null && childrenFiles.length > 0) {
                        Arrays.sort(childrenFiles, new Comparator<File>() {
                            @Override
                            public int compare(File o1, File o2) {
                                return (int) (o2.lastModified() - o1.lastModified());
                            }
                        });

                        // 从最新的向前翻,直到大于2MB
                        long currentSize = 0;
                        for (File child : childrenFiles) {
                            currentSize += child.length();
                            if (currentSize > 2 * 1024 * 1024) {
                                break;
                            }
                            reportLogs.add(child);
                        }
                    }
                }

                final File zipFile = ZipUtil.zip(reportLogs, new File(FileUtils.getSubDir("share"), "report.zip"));
                if (zipFile != null && zipFile.exists()) {
                    // 发送邮件
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Intent i = new Intent(Intent.ACTION_SEND);
                            i.setType("application/octet-stream");
                            i.putExtra(Intent.EXTRA_EMAIL,
                                    new String[] { Constant.MAIL_ADDERSS });
                            i.putExtra(Intent.EXTRA_SUBJECT, StringUtil.getString(R.string.index__report_error_log));
                            i.putExtra(Intent.EXTRA_TEXT, getString(R.string.index__error_occur_time, errorTime));
                            Uri uri = FileProvider.getUriForFile(IndexActivity.this, "com.alipay.hulu.myProvider", zipFile);
                            i.putExtra(Intent.EXTRA_STREAM, uri);
                            startActivity(Intent.createChooser(i,
                                    getString(R.string.index__select_mail_app)));
                        }
                    });
                } else {
                    toastLong(getString(R.string.index__package_crash_failed));

                    // 回设检查时间,以便下次上报
                    SPService.putLong(SPService.KEY_ERROR_CHECK_TIME, errorTime - 10);
                }
            }
        });
    }

    /**
     * 读取外部ADB配置文件
     */
    private void readOuterAdbKey() {
        File root = FileUtils.getSubDir("adb");
        final File adbKey = new File(root, "adbkey");
        final File pubKey = new File(root, "adbkey.pub");
        if (!adbKey.exists() || !pubKey.exists()) {
            return;
        }

        boolean result = CmdTools.readOuterAdbKey(adbKey, pubKey);
        if (!result) {
            toastShort("拷贝ADB Key失败");
        } else {
            adbKey.delete();
            pubKey.delete();
        }
    }

    public static class Entry {

        private int iconId;
        private String name;
        private String[] permissions;
        private int level;
        private int index;
        private int cornerColor;
        private String cornerText;
        private float saturation;
        private int cornerPersist;
        private Class<? extends Activity> targetActivity;

        public Entry(EntryActivity activity, Class<? extends Activity> target) {
            if (activity.icon() != -1) {
                this.iconId = activity.icon();
            } else if (!StringUtil.isEmpty(activity.iconName())) {
                // 反射获取id
                String name = activity.iconName();
                int lastDotPos = name.lastIndexOf('.');
                String clazz = name.substring(0, lastDotPos);
                String field = name.substring(lastDotPos + 1);
                try {
                    Class RClass = ClassUtil.getClassByName(clazz);
                    Field icon = RClass.getDeclaredField(field);
                    this.iconId = icon.getInt(null);
                } catch (Exception e) {
                    LogUtil.e(TAG, "Fail to load icon result with id:" + name);
                    this.iconId = R.drawable.solopi_main;
                }
            } else {
                this.iconId = R.drawable.solopi_main;
            }
            String name = activity.name();
            if (activity.nameRes() != 0) {
                name = StringUtil.getString(activity.nameRes());
            } else if (StringUtil.isNotEmpty(activity.nameResName())) {
                int nameRes = 0;
                String nameResName = activity.nameResName();
                int lastDotPos = nameResName.lastIndexOf('.');
                String clazz = nameResName.substring(0, lastDotPos);
                String field = nameResName.substring(lastDotPos + 1);
                try {
                    Class<?> RClass = ClassUtil.getClassByName(clazz);
                    Field nameResF = RClass.getDeclaredField(field);
                    nameRes = nameResF.getInt(null);
                } catch (Exception e) {
                    LogUtil.e(TAG, "Fail to load name result with id:" + nameResName);
                    nameRes = R.string.app_name;
                }
                name = StringUtil.getString(nameRes);
            }
            this.name = name;
            permissions = activity.permissions();
            level = activity.level();
            targetActivity = target;
            index = activity.index();
            cornerText = activity.cornerText();
            cornerColor = activity.cornerBg();
            cornerPersist = activity.cornerPersist();
            saturation = activity.saturation();
        }

    }

    public class CustomAdapter extends BaseAdapter {

        final Context context;
        final List<Entry> data;
        JSONObject entryCount;
        JSONObject versionsCount;
        int currentVersionCode;

        public CustomAdapter(Context context, List<Entry> data) {
            this.context = context;
            this.data = data;

            // 默认取空值
            String appInfo = SPService.getString(SPService.KEY_INDEX_RECORD, null);
            currentVersionCode = SystemUtil.getAppVersionCode();
            if (appInfo == null) {
                versionsCount = new JSONObject();
                entryCount = new JSONObject();
            } else {
                versionsCount = JSON.parseObject(appInfo);
                // 当前版本的信息
                entryCount = versionsCount.getJSONObject(Integer.toString(currentVersionCode));

                // 如果没有当前版本信息
                if (entryCount == null) {
                    entryCount = new JSONObject();
                }
            }
        }

        @Override
        public int getCount() {
            if (data != null) {
                return data.size();
            } else {
                return 0;
            }
        }

        @Override
        public Object getItem(int position) {
            if (data != null) {
                return data.get(position);
            } else {
                return null;
            }
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            if (convertView == null) {
                convertView = LayoutInflater.from(context).inflate(R.layout.item_tools_grid, parent, false);
                viewHolder = new ViewHolder();
                convertView.setTag(viewHolder);
                viewHolder.icon = (ImageView) convertView.findViewById(R.id.img);
                viewHolder.name = (TextView) convertView.findViewById(R.id.tv);
                viewHolder.corner = (TextView) convertView.findViewById(R.id.index_corner);
                viewHolder.background = (ColorFilterRelativeLayout) convertView;
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            final Entry item = data.get(position);

            viewHolder.icon.setImageResource(item.iconId);
            viewHolder.name.setText(item.name);

            Integer itemCount = entryCount.getInteger(item.name);
            if (itemCount == null) {
                itemCount = 0;
            }
            // 持续显示或者,有进入次数计数
            if (item.cornerPersist == 0 ||
                    (item.cornerPersist > 0 && itemCount < item.cornerPersist)) {
                // 如果有角标配置,设置角标
                if (!StringUtil.isEmpty(item.cornerText)) {
                    viewHolder.corner.setText(item.cornerText);
                    viewHolder.corner.setBackgroundColor(item.cornerColor);
                    viewHolder.corner.setVisibility(View.VISIBLE);
                } else {
                    viewHolder.corner.setVisibility(View.GONE);
                }
            } else {
                viewHolder.corner.setVisibility(View.GONE);
            }

            if (item.saturation != 1F) {
                viewHolder.background.setSaturation(item.saturation);
            } else {
                viewHolder.background.setSaturation(1);
            }

            convertView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    final long startTime = System.currentTimeMillis();
                    PermissionUtil.requestPermissions(Arrays.asList(item.permissions), IndexActivity.this, new PermissionUtil.OnPermissionCallback() {
                        @Override
                        public void onPermissionResult(boolean result, String reason) {
                            LogUtil.d(TAG, "权限申请耗时:%dms", System.currentTimeMillis() - startTime);
                            if (result) {
                                // 记录下进入次数
                                Integer count = entryCount.getInteger(item.name);
                                if (count == null) {
                                    count = 1;
                                } else {
                                    count ++;
                                }
                                entryCount.put(item.name, count);
                                versionsCount.put(Integer.toString(currentVersionCode), entryCount);
                                SPService.putString(SPService.KEY_INDEX_RECORD, JSON.toJSONString(versionsCount));

                                Intent intent = new Intent(IndexActivity.this, item.targetActivity);
                                startActivity(intent);
                            }
                        }
                    });
                }
            });
            return convertView;
        }

        public List<Entry> getData() {
            return data;
        }

        public class ViewHolder {
            ColorFilterRelativeLayout background;
            ImageView icon;
            TextView name;
            TextView corner;
        }
    }


}
View Code

 

2.websocket通信相关,断开链接时自动重连(solopi链接服务端的时机)

核心代码:

@Override
    public void onOpen(ServerHandshake handshakedata) {
        LogUtil.i(tag,"连接开启");
        //LauncherApplication.getInstance().showToast("连接服务器成功!");
        //重连成功,像服务端发送设备信息。因为需要实时更新服务端设备状态
        DeviceInfo deviceInfo = DeviceInfoUtil.generateDeviceInfo();
        HashMap<String, Object> deviceInfoHashMap = new HashMap<>();
        deviceInfoHashMap.put("deviceInfo",(Object)deviceInfo);
        RealTimeManage.sendMessage(deviceInfoHashMap);
    }

    @Override
    public void onMessage(String message) {
        LogUtil.i(tag,"接收消息");
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {

        LogUtil.i(tag,"连接断开");
        if (code != RealTimeManage.FORCE_CLOSE){
            RealTimeManage.openConnect();//断线重连
        }
    }

    @Override
    public void onError(Exception ex) {
        LogUtil.i(tag,"连接出错");
        RealTimeManage.openConnect();//断线重连
    }

onOpen方法,是在链接成功后发送设备信息到服务端

onClose和onError方法,主要是在链接断开和出错时,进行重连。

 

/**
     * 创建链接(打开app以及修改服务端地址的时候调用)
     */
    public static void openConnect() {
        if (client != null && client.getReadyState().equals(ReadyState.OPEN)) {
            client.close(FORCE_CLOSE);//已存在链接时,先关闭。比如修改服务端链接时,客户端可能与之前设置的服务端地址链接还是open状态
        }
        String wsurl = SPService.getString(SPService.KEY_SERVER_SETTING);
        //判断服务端地址是否为空,空则返回
        if (StringUtil.isEmpty(wsurl)) {
            LauncherApplication.getInstance().showToast("请先配置服务端地址!!!");
            return;
        }
        //服务端地址不为空,创建链接
        URI uri = URI.create(wsurl);
        try {
            int time = 0;
            client = new JwebSocketClient(uri) {
                @Override
                public void onMessage(String message) {
                    receiveMessage(message);
                    super.onMessage(message);
                }
            };
            client.connect();

            while ((!(client.getReadyState().equals(ReadyState.OPEN))) && time <= 4) {//轮询判断是否已创建链接
                Thread.sleep(1000);
                time++;
            }
            LauncherApplication.getInstance().showToast("链接" + (client.getReadyState().equals(ReadyState.OPEN) ? "成功" : "失败"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

上述方法是创建链接,

首先判断当前链接是否为open状态,已经存在链接时,先关闭。

判断服务端地址是否为空,4S轮询等待链接成功创建。

 

详细代码如下:

package com.alipay.hulu.runManager;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alipay.hulu.activity.BatchExecutionActivity;
import com.alipay.hulu.common.application.LauncherApplication;
import com.alipay.hulu.common.bean.DeviceInfo;
import com.alipay.hulu.common.service.SPService;
import com.alipay.hulu.common.tools.CmdTools;
import com.alipay.hulu.common.utils.LogUtil;
import com.alipay.hulu.common.utils.StringUtil;
import com.alipay.hulu.shared.io.bean.RecordCaseInfo;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;

import org.java_websocket.WebSocket;
import org.java_websocket.enums.ReadyState;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

public class RealTimeManage {
    private static String TAG = "RealTimeManage";
    public static JwebSocketClient client;
    public static String taskId;
    public static final Integer FORCE_CLOSE=-99;


    /**
     * 上传设备信息
     *
     * @param messageMap
     */
    public static void sendMessage(HashMap<String, Object> messageMap) {
        if (client != null && client.getReadyState().equals(ReadyState.OPEN)) {
            //链接创建成功后发消息
            client.send(JSON.toJSONString(messageMap));
            LogUtil.i(TAG, "成功发送消息");
        }
    }

    /**
     * 创建链接(打开app以及修改服务端地址的时候调用)
     */
    public static void openConnect() {
        if (client!=null && client.getReadyState().equals(ReadyState.OPEN)){
            client.close(FORCE_CLOSE);//已存在链接时,先关闭。比如修改服务端链接时,客户端可能与之前设置的服务端地址链接还是open状态
        }
        String wsurl = SPService.getString(SPService.KEY_SERVER_SETTING);
        //判断服务端地址是否为空,空则返回
        if (StringUtil.isEmpty(wsurl)) {
            LauncherApplication.getInstance().showToast("请先配置服务端地址!!!");
            return;
        }

        //服务端地址不为空,创建链接
        URI uri = URI.create(wsurl);

        try {
            int time = 0;
            client = new JwebSocketClient(uri) {
                @Override
                public void onMessage(String message) {
                    receiveMessage(message);
                    super.onMessage(message);
                }
            };
            client.connect();

            while (( !(client.getReadyState().equals(ReadyState.OPEN))) && time <= 4) {//轮询判断是否已创建链接
                Thread.sleep(1000);
                time++;
            }
            LauncherApplication.getInstance().showToast("链接"+(client.getReadyState().equals(ReadyState.OPEN)?"成功":"失败"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    /**
     * 接收消息
     *
     * @param message
     */
    private static void receiveMessage(String message) {
        LogUtil.i(TAG, "收到消息" + message);
        Gson gson = new Gson();
        HashMap<String, Object> messageMap = gson.fromJson(message, new TypeToken<HashMap<String, Object>>() {
        }.getType());

        Set<String> keySet = messageMap.keySet();

        if (messageMap.containsKey("execCase")) {//如果收到的消息是用例执行命令,则去执行用例
            //收到的caseInfo转换为 List<RecordCaseInfo>
            Object execCase = messageMap.get("execCase");
            taskId = new JsonParser().parse(execCase.toString()).getAsJsonObject().get("taskId").toString();
            String caseListjsonStr = new JsonParser().parse(execCase.toString()).getAsJsonObject().get("caseInfoList").toString();
            List<RecordCaseInfo> caseList = gson.fromJson(caseListjsonStr, new TypeToken<List<RecordCaseInfo>>() {
            }.getType());

            //用例caseStep到对应文件
            for (int i = 0; i < caseList.size(); i++) {
                RecordCaseInfo recordCaseInfo = caseList.get(i);
                String caseStep = recordCaseInfo.getCaseStep();
                String operationLog = recordCaseInfo.getOperationLog();
                String storePath = JSON.parseObject(operationLog).get("storePath").toString();
                File file = new File(storePath);
                if (!file.getParentFile().exists()) {
                    file.getParentFile().mkdirs();
                }
                try {
                    BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(storePath));
                    bufferedWriter.write(caseStep);
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            CaseRunMangage.getInstance().run(caseList);//执行接收到的用例
        } else if (messageMap.containsKey("execMonkey")) {
            CmdTools.generateConnection();
            String result = CmdTools.execAdbCmd(messageMap.get("execMonkey").toString(), 0);//执行cmd命令
            LogUtil.i(TAG, "adb运行结果" + result);
        }
    }

}
View Code
package com.alipay.hulu.runManager;


import com.alipay.hulu.common.application.LauncherApplication;
import com.alipay.hulu.common.bean.DeviceInfo;
import com.alipay.hulu.common.utils.DeviceInfoUtil;
import com.alipay.hulu.common.utils.LogUtil;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.util.HashMap;

public class JwebSocketClient extends WebSocketClient {
    private String tag = "JwebSocketClient";

    public JwebSocketClient(URI serverUri) {
        super(serverUri);
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        LogUtil.i(tag,"连接开启");
        //LauncherApplication.getInstance().showToast("连接服务器成功!");
        //重连成功,像服务端发送设备信息。因为需要实时更新服务端设备状态
        DeviceInfo deviceInfo = DeviceInfoUtil.generateDeviceInfo();
        HashMap<String, Object> deviceInfoHashMap = new HashMap<>();
        deviceInfoHashMap.put("deviceInfo",(Object)deviceInfo);
        RealTimeManage.sendMessage(deviceInfoHashMap);
    }

    @Override
    public void onMessage(String message) {
        LogUtil.i(tag,"接收消息");
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {

        LogUtil.i(tag,"连接断开");
        if (code != RealTimeManage.FORCE_CLOSE){
            RealTimeManage.openConnect();//断线重连
        }
    }

    @Override
    public void onError(Exception ex) {
        LogUtil.i(tag,"连接出错");
        RealTimeManage.openConnect();//断线重连
    }
}
View Code

 

二:用例上传

核心代码:

//点击批量上传按钮
        batchUpLoadBtn = (Button) findViewById(R.id.batch_upload_start_btn);
        batchUpLoadBtn.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                if (currentCases.size()==0){
                    toastShort("还未选择用例!!");
                    return ;
                }

                HashMap<String, Object> caseHashMap = new HashMap<>();
                //遍历currentCases,将每一个case都赋值对应的caseStep
                BufferedReader reader=null;
                for (int i = 0; i < currentCases.size(); i++) {
                    RecordCaseInfo caseInfo = currentCases.get(i);
                    String path = caseInfo.getOperationLog();
                    String storePath = JSON.parseObject(path).get("storePath").toString();
                    StringBuffer caseStep = new StringBuffer();
                    try {
                         reader = new BufferedReader(new FileReader(storePath));
                        String line;
                        if ((line = reader.readLine()) !=null){
                            caseStep.append(line);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    String caseStepStr = caseStep.toString();
                    //用例上传前进行解密
                    List<OperationStep> operationSteps = JSON.parseArray(caseStepStr, OperationStep.class);
                    for (OperationStep operationStep : operationSteps) {
                        OperationMethod operationMethod = operationStep.getOperationMethod();

                        if (operationMethod.isEncrypt()){
                            Set<String> paramKeys = operationMethod.getParamKeys();

                            HashMap<String, String> tempParms = new HashMap<>();
                            for (String paramKey : paramKeys) {
                                //解密
                                String param = operationMethod.getParam(paramKey);
                                tempParms.put(paramKey,param);
                            }
                            //设置为不加密模式
                            operationMethod.setEncrypt(false);
                            for (String key : tempParms.keySet()) {
                                //在不加密的模式下,将解密后的数据写入到operationMethod
                                operationMethod.putParam(key,tempParms.get(key));
                            }
                        }
                    }
                    caseInfo.setCaseStep(JSON.toJSONString(operationSteps));



                }
                try {
                    if (reader!=null)
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //将最终的currentCases放到map
                caseHashMap.put("caseInfo",currentCases);
                try {
                    RealTimeManage.sendMessage(caseHashMap);
                    toastShort(getString(R.string.batch__uploadsuccess_case));
                } catch (Exception e) {
                    toastShort(getString(R.string.batch__uploadfail_case));
                    e.printStackTrace();
                }

                /*//ToDo:批量上传是否需要权限??
                PermissionUtil.OnPermissionCallback callback = new PermissionUtil.OnPermissionCallback(){
                    @Override
                    public void onPermissionResult(boolean result, String reason) {
                        if (result){
                            HashMap<String, Object> caseHashMap = new HashMap<>();
                            caseHashMap.put("caseInfo",currentCases);
                            RealTimeManage.sendMessage(caseHashMap);
                        }
                    }
                };

                checkPermissions(callback);*/
            }
        });
    }

solopi端新增了用例上传的按钮,注册该按钮的点击事件,具体逻辑可以看下代码注释,这块写的比较详细。

其中比较重要的一步:用例上传前对operationMethod的ParamKey进行解密,后续服务端在测试报告渲染时需要用到该解密后的内容。

 

详细代码如下:

/*
 * Copyright (C) 2015-present, Ant Financial Services Group
 *
 * 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.alipay.hulu.activity;

import android.os.Bundle;
import android.provider.Settings;
import androidx.annotation.Nullable;

import com.alibaba.fastjson.JSON;
import com.alipay.hulu.runManager.CaseRunMangage;
import com.alipay.hulu.runManager.RealTimeManage;
import com.alipay.hulu.shared.node.action.OperationMethod;
import com.alipay.hulu.shared.node.tree.export.bean.OperationStep;
import com.google.android.material.tabs.TabLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;

import com.alipay.hulu.R;
import com.alipay.hulu.adapter.BatchExecutionListAdapter;
import com.alipay.hulu.common.tools.BackgroundExecutor;
import com.alipay.hulu.common.utils.LogUtil;
import com.alipay.hulu.common.utils.MiscUtil;
import com.alipay.hulu.common.utils.PermissionUtil;
import com.alipay.hulu.fragment.BatchExecutionFragment;
import com.alipay.hulu.shared.io.bean.RecordCaseInfo;
import com.alipay.hulu.shared.node.utils.AppUtil;
import com.alipay.hulu.ui.HeadControlPanel;
import com.alipay.hulu.util.CaseReplayUtil;
import com.zhy.view.flowlayout.FlowLayout;
import com.zhy.view.flowlayout.TagAdapter;
import com.zhy.view.flowlayout.TagFlowLayout;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

/**
 * Created by lezhou.wyl on 2018/8/19.
 */
public class BatchExecutionActivity extends BaseActivity
        implements BatchExecutionListAdapter.Delegate , TagFlowLayout.OnTagClickListener{

    private ViewPager mPager;
    private CheckBox mRestartApp;
    private TabLayout mTabLayout;
    private HeadControlPanel mHeadPanel;
    private TagFlowLayout tagGroup;
    private final List<RecordCaseInfo> currentCases = new ArrayList<>();
    private TagAdapter<RecordCaseInfo> tagAdapter;

    private Button startExecutionBtn;
    private Button batchUpLoadBtn;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_batch_execution);

        mPager = (ViewPager) findViewById(R.id.pager);
        mTabLayout = (TabLayout) findViewById(R.id.tab_layout);
        mHeadPanel = (HeadControlPanel) findViewById(R.id.head_replay_list);
        mHeadPanel.setMiddleTitle(getString(R.string.activity__batch_replay));
        mHeadPanel.setBackIconClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
        mTabLayout.setupWithViewPager(mPager);
        mTabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
        mTabLayout.setTabMode(TabLayout.MODE_FIXED);
        mTabLayout.setSelectedTabIndicatorColor(getResources().getColor(R.color.mainBlue));
        mTabLayout.post(new Runnable() {
            @Override
            public void run() {
                MiscUtil.setIndicator(mTabLayout, 0, 0);
            }
        });

        // 选择项
        currentCases.clear();
        tagAdapter = new TagAdapter<RecordCaseInfo>(currentCases) {
            @Override
            public View getView(FlowLayout parent, int position, RecordCaseInfo o) {
                View tag = LayoutInflater.from(BatchExecutionActivity.this).inflate(R.layout.item_batch_execute_tag, parent, false);
                TextView title = (TextView) tag.findViewById(R.id.batch_execute_tag_name);
                title.setText(o.getCaseName());
                return tag;
            }
        };
        tagGroup = (TagFlowLayout) findViewById(R.id.batch_execute_tag_group);
        tagGroup.setMaxSelectCount(0);
        tagGroup.setAdapter(tagAdapter);
        tagGroup.setOnTagClickListener(this);

        CustomPagerAdapter pagerAdapter = new CustomPagerAdapter(getSupportFragmentManager());
        mPager.setAdapter(pagerAdapter);
        mRestartApp = (CheckBox) findViewById(R.id.batch_execute_restart);

        startExecutionBtn = (Button) findViewById(R.id.batch_execute_start_btn);

        startExecutionBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (currentCases.size() == 0) {
                    toastShort(getString(R.string.batch__select_case));
                    return;
                }

                PermissionUtil.OnPermissionCallback callback = new PermissionUtil.OnPermissionCallback() {
                    @Override
                    public void onPermissionResult(boolean result, String reason) {
                        if (result) {
                            CaseReplayUtil.startReplayMultiCase(currentCases, mRestartApp.isChecked());
                            startApp(currentCases.get(0).getTargetAppPackage());
                        }
                    }
                };
                checkPermissions(callback);

            }
        });

        //点击批量上传按钮
        batchUpLoadBtn = (Button) findViewById(R.id.batch_upload_start_btn);
        batchUpLoadBtn.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                if (currentCases.size()==0){
                    toastShort("还未选择用例!!");
                    return ;
                }

                HashMap<String, Object> caseHashMap = new HashMap<>();
                //遍历currentCases,将每一个case都赋值对应的caseStep
                BufferedReader reader=null;
                for (int i = 0; i < currentCases.size(); i++) {
                    RecordCaseInfo caseInfo = currentCases.get(i);
                    String path = caseInfo.getOperationLog();
                    String storePath = JSON.parseObject(path).get("storePath").toString();
                    StringBuffer caseStep = new StringBuffer();
                    try {
                         reader = new BufferedReader(new FileReader(storePath));
                        String line;
                        if ((line = reader.readLine()) !=null){
                            caseStep.append(line);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    String caseStepStr = caseStep.toString();
                    //用例上传前进行解密
                    List<OperationStep> operationSteps = JSON.parseArray(caseStepStr, OperationStep.class);
                    for (OperationStep operationStep : operationSteps) {
                        OperationMethod operationMethod = operationStep.getOperationMethod();

                        if (operationMethod.isEncrypt()){
                            Set<String> paramKeys = operationMethod.getParamKeys();

                            HashMap<String, String> tempParms = new HashMap<>();
                            for (String paramKey : paramKeys) {
                                //解密
                                String param = operationMethod.getParam(paramKey);
                                tempParms.put(paramKey,param);
                            }
                            //设置为不加密模式
                            operationMethod.setEncrypt(false);
                            for (String key : tempParms.keySet()) {
                                //在不加密的模式下,将解密后的数据写入到operationMethod
                                operationMethod.putParam(key,tempParms.get(key));
                            }
                        }
                    }
                    caseInfo.setCaseStep(JSON.toJSONString(operationSteps));



                }
                try {
                    if (reader!=null)
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //将最终的currentCases放到map
                caseHashMap.put("caseInfo",currentCases);
                try {
                    RealTimeManage.sendMessage(caseHashMap);
                    toastShort(getString(R.string.batch__uploadsuccess_case));
                } catch (Exception e) {
                    toastShort(getString(R.string.batch__uploadfail_case));
                    e.printStackTrace();
                }

                /*//ToDo:批量上传是否需要权限??
                PermissionUtil.OnPermissionCallback callback = new PermissionUtil.OnPermissionCallback(){
                    @Override
                    public void onPermissionResult(boolean result, String reason) {
                        if (result){
                            HashMap<String, Object> caseHashMap = new HashMap<>();
                            caseHashMap.put("caseInfo",currentCases);
                            RealTimeManage.sendMessage(caseHashMap);
                        }
                    }
                };

                checkPermissions(callback);*/
            }
        });
    }

    @Override
    public void onItemAdd(RecordCaseInfo caseInfo) {
        currentCases.add(caseInfo);
        updateExecutionTag();
    }

    public void updateExecutionTag() {
        tagAdapter.notifyDataChanged();
    }

    @Override
    public boolean onTagClick(View view, int position, FlowLayout parent) {
        currentCases.remove(position);
        updateExecutionTag();
        return false;
    }

    private void startApp(final String packageName) {
        if (packageName == null) {
            return;
        }

        BackgroundExecutor.execute(new Runnable() {
            @Override
            public void run() {
                AppUtil.forceStopApp(packageName);

                LogUtil.e("NewRecordActivity", "强制终止应用:" + packageName);
                MiscUtil.sleep(500);
                AppUtil.startApp(packageName);
            }
        });
    }

    /**
     * 检察权限
     * @param callback
     */
    private void checkPermissions(PermissionUtil.OnPermissionCallback callback) {
        // 高权限,悬浮窗权限判断
        PermissionUtil.requestPermissions(Arrays.asList("adb", Settings.ACTION_ACCESSIBILITY_SETTINGS),
                this, callback);
    }

    private static class CustomPagerAdapter extends FragmentPagerAdapter {

        private static int[] PAGES = BatchExecutionFragment.getTypes();

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return BatchExecutionFragment.newInstance(PAGES[position]);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return BatchExecutionFragment.getTypeName(PAGES[position]);
        }
        @Override
        public int getCount() {
            return PAGES.length;
        }
    }
}
View Code

 

三:用例执行后,结果上传【目前为了debug方便,需要手动上传的,后续会改成自动上传】

//上传批量回放执行结果
        mPanel.setInfoIconClickListener(R.drawable.icon_save, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                HashMap<String, Object> replayResultInfo = new HashMap<>();

                for (ReplayResultBean replayResultBean : mResults) {
                    List<OperationStep> currentOperationLog = replayResultBean.getCurrentOperationLog();
                    for (OperationStep operationStep : currentOperationLog) {
                        OperationMethod operationMethod = operationStep.getOperationMethod();

                        if (operationMethod.isEncrypt()) {//为加密状态
                            HashMap<String, String> unEncryParm = new HashMap<>();//存储未加密的参数
                            Set<String> paramKeys = operationMethod.getParamKeys();
                            for (String paramKey : paramKeys) {
                                String param = operationMethod.getParam(paramKey);//拿到解密后的参数值
                                unEncryParm.put(paramKey, param);//未加密的数据存储到unEncryParm
                            }
                            operationMethod.setEncrypt(false);//设置为不加密
                            Set<String> unEncryParmKey = unEncryParm.keySet();
                            for (String key : unEncryParmKey) {
                                operationMethod.getOperationParam().put(key, unEncryParm.get(key));//将解密数据写会到operationMethod中
                            }
                        }
                    }
                    replayResultBean.setTaskId(RealTimeManage.taskId);//给每个结果加上taskId
                }

 

/*
 * Copyright (C) 2015-present, Ant Financial Services Group
 *
 * 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.alipay.hulu.activity;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.Nullable;

import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.alipay.hulu.R;
import com.alipay.hulu.bean.CaseStepHolder;
import com.alipay.hulu.bean.ReplayResultBean;
import com.alipay.hulu.common.application.LauncherApplication;
import com.alipay.hulu.common.tools.BackgroundExecutor;
import com.alipay.hulu.runManager.RealTimeManage;
import com.alipay.hulu.shared.node.action.OperationMethod;
import com.alipay.hulu.shared.node.tree.export.bean.OperationStep;
import com.alipay.hulu.ui.HeadControlPanel;
import com.alipay.hulu.util.LargeObjectHolder;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * 用例批量回放执行结果,该页面改造:回访完成后上传回放结果
 */

public class BatchReplayResultActivity extends BaseActivity {

    private ListView mResultList;
    private TextView mTotalNum;
    private TextView mSuccessNum;
    private TextView mFailNum;

    private HeadControlPanel mPanel;

    private ResultAdapter mAdapter;
    private List<ReplayResultBean> mResults;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_batch_replay_result);
        initView();
        initListeners();
        initState();
    }

    private void initState() {
        mResults = LargeObjectHolder.getInstance().getReplayResults();
        // 清理引用
        LargeObjectHolder.getInstance().setReplayResults(null);

        if (mResults == null) {
            finish();
            return;
        }

        int totalNum = 0;
        int successNum = 0;
        for (ReplayResultBean bean : mResults) {
            totalNum++;
            if (TextUtils.isEmpty(bean.getExceptionMessage())) {
                successNum++;
            }
        }

        //批量回放执行结果页,回放总结:
        mTotalNum.setText(getString(R.string.batch_replay_result__case_count, totalNum));//用例总数
        mSuccessNum.setText(getString(R.string.batch_replay_result__success_count, successNum));//成功数
        mFailNum.setText(getString(R.string.batch_replay_result__failed_count, totalNum - successNum));//失败数

        mAdapter = new ResultAdapter(this, mResults);
        mResultList.setAdapter(mAdapter);

        //上传批量回放执行结果
        mPanel.setInfoIconClickListener(R.drawable.icon_save, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                HashMap<String, Object> replayResultInfo = new HashMap<>();

                for (ReplayResultBean replayResultBean : mResults) {
                    List<OperationStep> currentOperationLog = replayResultBean.getCurrentOperationLog();
                    for (OperationStep operationStep : currentOperationLog) {
                        OperationMethod operationMethod = operationStep.getOperationMethod();

                        if (operationMethod.isEncrypt()) {//为加密状态
                            HashMap<String, String> unEncryParm = new HashMap<>();//存储未加密的参数
                            Set<String> paramKeys = operationMethod.getParamKeys();
                            for (String paramKey : paramKeys) {
                                String param = operationMethod.getParam(paramKey);//拿到解密后的参数值
                                unEncryParm.put(paramKey, param);//未加密的数据存储到unEncryParm
                            }
                            operationMethod.setEncrypt(false);//设置为不加密
                            Set<String> unEncryParmKey = unEncryParm.keySet();
                            for (String key : unEncryParmKey) {
                                operationMethod.getOperationParam().put(key, unEncryParm.get(key));//将解密数据写会到operationMethod中
                            }
                        }
                    }
                    replayResultBean.setTaskId(RealTimeManage.taskId);//给每个结果加上taskId
                }

                replayResultInfo.put("replayResultInfo" + RealTimeManage.taskId, mResults);
                showProgressDialog(getString(R.string.case_replay__update));//上传中
                BackgroundExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        if (mResults.size() != 0) {
                            RealTimeManage.sendMessage(replayResultInfo);//上传回放结果
                            LauncherApplication.getInstance().showDialog(BatchReplayResultActivity.this, "",
                                    getString(R.string.replay__upload_result_to), null);
                        } else {
                            toastLong(getString(R.string.replay__upload_failed));
                        }
                    }
                });
            }
        });
    }

    private void initListeners() {
        mResultList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                ReplayResultBean bean = (ReplayResultBean) mAdapter.getItem(position);
                if (bean == null) {
                    return;
                }
                // 由holder保存
                Intent intent = new Intent(BatchReplayResultActivity.this, CaseReplayResultActivity.class);
                int resId = CaseStepHolder.storeResult(bean);
                intent.putExtra("data", resId);
                startActivity(intent);
            }
        });
    }

    private void initView() {
        mResultList = (ListView) findViewById(R.id.result_list);
        mTotalNum = (TextView) findViewById(R.id.total_num);
        mSuccessNum = (TextView) findViewById(R.id.success_num);
        mFailNum = (TextView) findViewById(R.id.fail_num);

        mPanel = (HeadControlPanel) findViewById(R.id.head_layout);
        mPanel.setBackIconClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
        mPanel.setMiddleTitle(getString(R.string.activity__batch_replay_result));
    }

    private static class ResultAdapter extends BaseAdapter {

        private Context mContext;
        private List<ReplayResultBean> mData = new ArrayList<>();

        public ResultAdapter(Context context, List<ReplayResultBean> data) {
            mContext = context;
            mData = data;
        }

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public Object getItem(int position) {
            return mData.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                convertView = LayoutInflater.from(mContext).inflate(R.layout.item_replay_result, parent, false);

                holder = new ViewHolder();
                holder.caseName = (TextView) convertView.findViewById(R.id.case_name);
                holder.result = (TextView) convertView.findViewById(R.id.result);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            ReplayResultBean bean = (ReplayResultBean) getItem(position);
            if (bean != null) {
                holder.caseName.setText(bean.getCaseName());
                if (TextUtils.isEmpty(bean.getExceptionMessage())) {
                    holder.result.setText(R.string.constant__success);
                    holder.result.setTextColor(0xff65c0ba);
                } else {
                    holder.result.setText(R.string.constant__fail);
                    holder.result.setTextColor(0xfff76262);
                }
            }
            return convertView;
        }

        class ViewHolder {
            TextView caseName;
            TextView result;
        }
    }


}
View Code

 

四:模板参数【当前是在输入框控件下增加了模板参数输入框,用例录制时输入模板参数,后续服务端进行模板替换】

核心代码:

新增一个输入框控件,作为模板参数名的上传入口。

如:想要参数化手机号密码登录的case,则在录制case时,输入手机号之后,指定对应的模板参数key:login.phone,方便服务端拿到该数据后进行替换。

 

 

 protected static void showEditView(final AbstractNodeTree node, final OperationMethod method,
                                final Context context, final FunctionListener listener) {

        try {
            PerformActionEnum action = method.getActionEnum();
            String title = StringUtil.getString(R.string.function__input_title);
            View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_record_name, null);
            final EditText edit = (EditText) v.findViewById(R.id.dialog_record_edit);
            //新增的模板输入框
            final EditText templateEdit = (EditText) v.findViewById(R.id.dialog_template_edit);
            templateEdit.setText("");//每次输入前都先清空下,防止append导致上传数据错误
            View hide = v.findViewById(R.id.dialog_record_edit_hide);
            hide.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    hideInput(edit);
                }
            });
            final Pattern textPattern;
            if (action == PerformActionEnum.SLEEP) {
                edit.setHint(R.string.function__sleep_time);
                title = StringUtil.getString(R.string.function__set_sleep_time);
                textPattern = Pattern.compile("\\d+");
            } else if (action == PerformActionEnum.SCREENSHOT) {
                edit.setHint(R.string.function__screenshot_name);
                title = StringUtil.getString(R.string.function__set_screenshot_name);
                textPattern = Pattern.compile("\\S+(.*\\S+)?");
            } else if (action ==PerformActionEnum.MULTI_CLICK) {
                edit.setHint(R.string.function__click_time);
                title = StringUtil.getString(R.string.function__set_click_time);
                textPattern = Pattern.compile("\\d{1,2}");
            } else if (action ==PerformActionEnum.SLEEP_UNTIL) {
                edit.setHint(R.string.function__max_wait);
                edit.setText(R.string.default_sleep_time);
                title = StringUtil.getString(R.string.function__set_max_wait);
                textPattern = Pattern.compile("\\d+");
            } else if (action == PerformActionEnum.SCROLL_TO_BOTTOM
                    || action == PerformActionEnum.SCROLL_TO_TOP
                    || action == PerformActionEnum.SCROLL_TO_LEFT
                    || action == PerformActionEnum.SCROLL_TO_RIGHT) {
                edit.setHint(R.string.function__scroll_percent);
                edit.setText(R.string.default_scroll_percentage);
                title = StringUtil.getString(R.string.function__set_scroll_percent);
                textPattern = Pattern.compile("\\d+");
            } else if (action == PerformActionEnum.EXECUTE_SHELL) {
                edit.setHint(R.string.function__adb_cmd);
                title = StringUtil.getString(R.string.function__set_adb_cmd);
                textPattern = null;
            } else if (action == PerformActionEnum.LONG_CLICK) {
                edit.setHint(R.string.function__long_press);
                title = StringUtil.getString(R.string.function__set_long_press);
                textPattern = Pattern.compile("[1-9]\\d+");
                edit.setText(R.string.default_long_click_time);
            } else {
                edit.setHint(R.string.function__input_content);
                textPattern = null;
            }

            final AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                    .setTitle(title)
                    .setView(v)
                    .setPositiveButton(R.string.function__input, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            LogUtil.i(TAG, "Positive " + which);
                            String data = edit.getText().toString();
                            String templateText = templateEdit.getText().toString();//获得模板key

                            // 拼装参数
                            method.putParam(OperationExecutor.INPUT_TEXT_KEY, data);
                            method.putParam(OperationExecutor.INPUT_TEMPLATE_KEY,templateText);//将模板key写入到param

                            // 隐藏Dialog
                            dialog.dismiss();

                            // 抛给主线程
                            LauncherApplication.getInstance().runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    // 操作记录
                                    listener.onProcessFunction(method, node);
                                }
                            }, 500);
                        }
                    }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            LogUtil.i(TAG, "Negative " + which);

                            dialog.dismiss();
                            listener.onCancel();
                        }
                    }).create();

            dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
            dialog.setCanceledOnTouchOutside(false);                                   //点击外面区域不会让dialog消失
            dialog.setCancelable(false);
            dialog.show();

            // 校验输入
            edit.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {

                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {

                }

                @Override
                public void afterTextChanged(Editable s) {
                    Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
                    boolean enable = true;
                    if (textPattern != null) {
                        String content = s.toString();
                        enable = textPattern.matcher(content).matches();
                    }

                    // 如果不是目标状态,改变下
                    if (positiveButton.isEnabled() != enable) {
                        positiveButton.setEnabled(enable);
                    }
                }
            });

            dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);

        } catch (Exception e) {
            LogUtil.e(TAG, "Throw exception: " + e.getMessage(), e);

        }
    }

 

/*
 * Copyright (C) 2015-present, Ant Financial Services Group
 *
 * 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.alipay.hulu.util;

import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatSpinner;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;

import com.alibaba.fastjson.JSON;
import com.alipay.hulu.R;
import com.alipay.hulu.common.application.LauncherApplication;
import com.alipay.hulu.common.injector.InjectorService;
import com.alipay.hulu.common.service.ScreenCaptureService;
import com.alipay.hulu.common.tools.BackgroundExecutor;
import com.alipay.hulu.common.tools.CmdTools;
import com.alipay.hulu.common.utils.ContextUtil;
import com.alipay.hulu.common.utils.FileUtils;
import com.alipay.hulu.common.utils.LogUtil;
import com.alipay.hulu.common.utils.MiscUtil;
import com.alipay.hulu.common.utils.StringUtil;
import com.alipay.hulu.shared.node.OperationService;
import com.alipay.hulu.shared.node.action.Constant;
import com.alipay.hulu.shared.node.action.OperationExecutor;
import com.alipay.hulu.shared.node.action.OperationMethod;
import com.alipay.hulu.shared.node.action.PerformActionEnum;
import com.alipay.hulu.shared.node.action.RunningModeEnum;
import com.alipay.hulu.shared.node.action.provider.ActionProviderManager;
import com.alipay.hulu.shared.node.action.provider.ViewLoadCallback;
import com.alipay.hulu.shared.node.tree.AbstractNodeTree;
import com.alipay.hulu.shared.node.utils.BitmapUtil;
import com.alipay.hulu.shared.node.utils.LogicUtil;
import com.alipay.hulu.tools.HighLightService;
import com.alipay.hulu.ui.CheckableRelativeLayout;
import com.alipay.hulu.ui.FlowRadioGroup;
import com.alipay.hulu.ui.GesturePadView;
import com.alipay.hulu.ui.TwoLevelSelectLayout;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * 操作选择界面
 * Created by qiaoruikai on 2019/2/22 8:18 PM.
 */
public class FunctionSelectUtil {
    private static final String TAG = "FunctionSelect";
    public static final String ACTION_EXTRA = "ACTION_EXTRA";

    /**
     * 展示操作界面
     *
     * @param node
     */
    public static void showFunctionView(final Context context, final AbstractNodeTree node,
                                  final List<Integer> keys, final List<Integer> icons,
                                  final Map<Integer,List<TwoLevelSelectLayout.SubMenuItem>> secondLevel,
                                  final HighLightService highLightService,
                                  final OperationService operationService,
                                  final Pair<Float, Float> localClickPos,
                                        final FunctionListener listener) {
        // 没有操作
        DialogUtils.showLeveledFunctionView(context, keys, icons, secondLevel, new DialogUtils.FunctionViewCallback<TwoLevelSelectLayout.SubMenuItem>() {
            @Override
            public void onExecute(DialogInterface dialog, TwoLevelSelectLayout.SubMenuItem action) {
                PerformActionEnum actionEnum = PerformActionEnum.getActionEnumByCode(action.key);
                if (actionEnum == null) {
                    dialog.dismiss();
                    listener.onCancel();
                    return;
                }

                LogUtil.d(TAG, "点击操作: %s, extra: %s", actionEnum, action.extra);

                if (actionEnum == PerformActionEnum.OTHER_GLOBAL ||
                        actionEnum == PerformActionEnum.OTHER_NODE) {
                    final OperationMethod method = new OperationMethod(actionEnum);

                    // 添加控件点击位置
                    if (localClickPos != null) {
                        method.putParam(OperationExecutor.LOCAL_CLICK_POS_KEY, localClickPos.first + "," + localClickPos.second);
                    }
                    // 先隐藏Dialog
                    dialog.dismiss();
                    // 隐藏高亮
                    if (highLightService != null) {
                       highLightService.removeHightLightSync();
                    }

                    method.putParam(ActionProviderManager.KEY_TARGET_ACTION_DESC, action.name);
                    method.putParam(ActionProviderManager.KEY_TARGET_ACTION, action.extra);

                    // 等500ms
                    LauncherApplication.getInstance().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            operationService.getActionProviderMng().loadActionView(context, method, node,
                                    new ViewLoadCallback() {
                                        @Override
                                        public void onViewLoaded(View v, Runnable preCall) {
                                            if (v == null) {
                                                listener.onProcessFunction(method, node);
                                            } else {
                                                showProvidedView(node, method, context, v,
                                                        preCall, highLightService,
                                                        listener);
                                            }
                                        }
                                    });
                        }
                    });
                } else {
                    LogUtil.i(TAG, "Perform Action: " + action);
                    final OperationMethod method = new OperationMethod(
                            PerformActionEnum.getActionEnumByCode(action.key));

                    // 透传一下
                    if (!StringUtil.isEmpty(action.extra)) {
                        method.putParam(ACTION_EXTRA, action.extra);
                    }

                    // 添加控件点击位置
                    if (localClickPos != null) {
                        method.putParam(OperationExecutor.LOCAL_CLICK_POS_KEY, localClickPos.first + "," + localClickPos.second);
                    }

                    // 隐藏Dialog
                    dialog.dismiss();

                    // 隐藏高亮
                    if (highLightService != null) {
                        highLightService.removeHightLightSync();
                    }

                    // 等界面变化完毕
                    LauncherApplication.getInstance().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            // 处理操作
                            boolean result = processAction(method, node, context, operationService,
                                    listener);

                            // 如果没有处理,走默认处理
                            if (result) {
                                return;
                            }

                            // 向handler发送点击请求
                            listener.onProcessFunction(method, node);
                        }
                    }, 200);
                }
            }

            @Override
            public void onCancel(DialogInterface dialog) {
                LogUtil.d(TAG, "Dialog canceled");

                dialog.dismiss();

                // 定时执行
                if (highLightService != null) {
                    highLightService.removeHightLightSync();
                }
                listener.onCancel();
            }

            @Override
            public void onDismiss(DialogInterface dialog) {

            }
        });
    }

    /**
     * 自身处理一些操作
     *
     * @param method
     * @param node
     * @param context
     * @return
     */
    protected static boolean processAction(OperationMethod method, AbstractNodeTree node,
                                    final Context context, OperationService operationService,
                                    FunctionListener listener) {
        PerformActionEnum action = method.getActionEnum();
        if (action == PerformActionEnum.INPUT
                || action == PerformActionEnum.INPUT_SEARCH
                || action == PerformActionEnum.LONG_CLICK
                || action == PerformActionEnum.MULTI_CLICK
                || action == PerformActionEnum.SLEEP_UNTIL
                || action == PerformActionEnum.SLEEP
                || action == PerformActionEnum.SCREENSHOT
                || action == PerformActionEnum.SCROLL_TO_BOTTOM
                || action == PerformActionEnum.SCROLL_TO_TOP
                || action == PerformActionEnum.SCROLL_TO_LEFT
                || action == PerformActionEnum.SCROLL_TO_RIGHT
                || action == PerformActionEnum.EXECUTE_SHELL) {
            showEditView(node, method, context, listener);
            return true;
        } else if (action == PerformActionEnum.ASSERT
                || action == PerformActionEnum.ASSERT_TOAST) {
            chooseAssertMode(node, action, context, listener);
            return true;
        } else if (action == PerformActionEnum.LET_NODE) {
            chooseLetMode(node, context, listener, operationService);
            return true;
        } else if (action == PerformActionEnum.LET) {
            chooseLetGlobalMode(context, listener, operationService);
            return true;
        } else if (action == PerformActionEnum.CHECK || action == PerformActionEnum.CHECK_NODE) {
            chooseCheckMode(node, context, listener, operationService);
            return true;
        } else if (action == PerformActionEnum.JUMP_TO_PAGE
                || action == PerformActionEnum.GENERATE_QR_CODE
                || action == PerformActionEnum.GENERATE_BAR_CODE
                || action == PerformActionEnum.LOAD_PARAM) {
            showSelectView(method, context, listener);
            return true;
        } else if (action == PerformActionEnum.CHANGE_MODE) {
            showChangeModeView(context, listener);
            return true;
        } else if (action == PerformActionEnum.WHILE) {
            showWhileView(method, context, listener);
            return true;
        } else if (action == PerformActionEnum.IF) {
            method.putParam(LogicUtil.CHECK_PARAM, "");
        } else if (action == PerformActionEnum.GESTURE || action == PerformActionEnum.GLOBAL_GESTURE) {
            captureAndShowGesture(action, node, context, listener);
            return true;
        } else if (action == PerformActionEnum.GLOBAL_SCROLL_TO_BOTTOM
                || action == PerformActionEnum.GLOBAL_SCROLL_TO_TOP
                || action == PerformActionEnum.GLOBAL_SCROLL_TO_LEFT
                || action == PerformActionEnum.GLOBAL_SCROLL_TO_RIGHT) {
            showScrollControlView(method, context, listener);
            return true;
        }
        return false;
    }

    /**
     * 显示修改模式Dialog
     */
    private static void showChangeModeView(Context context, final FunctionListener listener) {
        try {
            final RunningModeEnum[] modes = RunningModeEnum.values();
            final String[] actions = new String[modes.length];

            for (int i = 0; i < modes.length; i++) {
                actions[i] = modes[i].getDesc();
            }

            AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                    .setTitle(R.string.function__set_mode)
                    .setSingleChoiceItems(actions, 0, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            LogUtil.i(TAG, "Click " + which);

                            if (dialog != null) {
                                dialog.dismiss();
                            }

                            // 执行操作
                            OperationMethod method = new OperationMethod(PerformActionEnum.CHANGE_MODE);
                            method.putParam(OperationExecutor.GET_NODE_MODE, modes[which].getCode());
                            listener.onProcessFunction(method, null);
                        }
                    }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();

                            listener.onCancel();
                        }
                    });

            AlertDialog dialog = builder.create();
            dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
            dialog.setCanceledOnTouchOutside(false);
            dialog.setCancelable(false);
            dialog.show();
        } catch (Exception e) {
            e.printStackTrace();

            listener.onCancel();
        }
    }


    /**
     * 展示滑动控制
     * @param context
     */
    private static void showScrollControlView(final OperationMethod method, Context context, final FunctionListener listener) {
        try {
            LayoutInflater inflater =  LayoutInflater.from(ContextUtil.getContextThemeWrapper(
                    context, R.style.AppDialogTheme));

            ScrollView v = (ScrollView) inflater.inflate(R.layout.dialog_setting, null);
            LinearLayout view = (LinearLayout) v.getChildAt(0);

            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

            // 对每一个字段添加EditText
            View editField = inflater.inflate(R.layout.item_edit_field, null);

            final EditText distance = (EditText) editField.findViewById(R.id.item_edit_field_edit);
            TextView distanceName = (TextView) editField.findViewById(R.id.item_edit_field_name);

            // 配置字段
            distance.setHint(R.string.scroll_setting__scroll_distense);
            distanceName.setText(R.string.scroll_setting__scroll_distense);
            distance.setInputType(InputType.TYPE_CLASS_NUMBER);
            distance.setText("40");

            // 设置其他参数
            distance.setTextColor(context.getResources().getColor(R.color.primaryText));
            distance.setHintTextColor(context.getResources().getColor(R.color.secondaryText));
            distance.setHighlightColor(context.getResources().getColor(R.color.colorAccent));
            view.addView(editField, layoutParams);


            editField = inflater.inflate(R.layout.item_edit_field, null);
            final EditText time = (EditText) editField.findViewById(R.id.item_edit_field_edit);
            TextView timeName = (TextView) editField.findViewById(R.id.item_edit_field_name);

            // 配置字段
            time.setHint(R.string.scroll_setting__scroll_time);
            timeName.setText(R.string.scroll_setting__scroll_time);
            time.setText("1000");
            time.setInputType(InputType.TYPE_CLASS_NUMBER);

            // 设置其他参数
            time.setTextColor(context.getResources().getColor(R.color.primaryText));
            time.setHintTextColor(context.getResources().getColor(R.color.secondaryText));
            time.setHighlightColor(context.getResources().getColor(R.color.colorAccent));
            view.addView(editField, layoutParams);

            // 显示Dialog
            AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                    .setTitle(R.string.scroll_setting__set_scroll_param)
                    .setView(v)
                    .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            // 获取每个编辑框的文字
                            dialog.dismiss();

                            method.putParam(OperationExecutor.SCROLL_DISTANCE, distance.getText().toString());
                            method.putParam(OperationExecutor.SCROLL_TIME, time.getText().toString());
                            listener.onProcessFunction(method, null);
                        }
                    }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                    listener.onCancel();
                }
            }).create();

            dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
            dialog.setCanceledOnTouchOutside(false);
            dialog.setCancelable(false);

            dialog.show();
        } catch (Exception e) {
            LogUtil.e(TAG, "Throw exception: " + e.getMessage(), e);
            listener.onCancel();
        }
    }

    /**
     * 展示选择框
     * @param method
     * @param context
     */
    private static void showSelectView(final OperationMethod method, final Context context,
                                final FunctionListener listener) {
        try {
            final PerformActionEnum actionEnum = method.getActionEnum();
            View customView = LayoutInflater.from(context).inflate(R.layout.dialog_select_view, null);
            View itemScan = customView.findViewById(R.id.item_scan);
            TextView itemUrl = customView.findViewById(R.id.item_url);
            if (actionEnum == PerformActionEnum.GENERATE_QR_CODE
                    || actionEnum == PerformActionEnum.GENERATE_BAR_CODE) {
                itemUrl.setText(R.string.function__input_code);
            }
            final AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                    .setView(customView)
                    .setTitle(R.string.function__select_function)
                    .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            listener.onCancel();
                        }
                    }).create();
            dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
            dialog.setCanceledOnTouchOutside(false);                                   //点击外面区域不会让dialog消失
            dialog.setCancelable(false);

            View.OnClickListener _listener = new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (actionEnum == PerformActionEnum.JUMP_TO_PAGE
                        || actionEnum == PerformActionEnum.GENERATE_QR_CODE
                        || actionEnum == PerformActionEnum.GENERATE_BAR_CODE) {
                        if (v.getId() == R.id.item_scan) {
                            method.putParam("scan", "1");
                            listener.onProcessFunction(method, null);
                        } else if (v.getId() == R.id.item_url) {
                            dialog.dismiss();
                            showUrlEditView(method, context, listener);
                            return;
                        }
                    } else if (actionEnum == PerformActionEnum.LOAD_PARAM) {
                        if (v.getId() == R.id.item_scan) {
                            method.putParam("scan", "1");
                            listener.onProcessFunction(method, null);
                        } else if (v.getId() == R.id.item_url) {
                            dialog.dismiss();
                            showUrlEditView(method, context, listener);
                            return;
                        }
                    } else {
                        dialog.dismiss();
                        listener.onCancel();
                    }


                    dialog.dismiss();
                }
            };
            itemScan.setOnClickListener(_listener);
            itemUrl.setOnClickListener(_listener);
            dialog.show();
        } catch (Exception e) {
            LogUtil.e(TAG, e.getMessage());
            listener.onCancel();
        }
    }

    /**
     * URL编辑框
     * @param method
     * @param context
     * @param listener
     */
    private static void showUrlEditView(final OperationMethod method, Context context,
                                 final FunctionListener listener) {
        final PerformActionEnum actionEnum = method.getActionEnum();
        View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_record_name, null);
        final EditText edit = (EditText) v.findViewById(R.id.dialog_record_edit);
        edit.setHint(R.string.function__please_input_url);
        if (actionEnum == PerformActionEnum.GENERATE_QR_CODE
                || actionEnum == PerformActionEnum.GENERATE_BAR_CODE) {
            edit.setHint(R.string.function__please_input_qr_code);
        }

        int title = (actionEnum == PerformActionEnum.GENERATE_QR_CODE
                || actionEnum == PerformActionEnum.GENERATE_BAR_CODE)? R.string.function__input_qr_code: R.string.function__input_url;

        AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                .setTitle(title)
                .setView(v)
                .setPositiveButton(R.string.function__input, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        LogUtil.i(TAG, "Positive " + which);
                        String data = edit.getText().toString();

                        dialog.dismiss();

                        if (actionEnum == PerformActionEnum.JUMP_TO_PAGE
                            || actionEnum == PerformActionEnum.GENERATE_QR_CODE
                            || actionEnum == PerformActionEnum.GENERATE_BAR_CODE) {

                            // 向handler发送请求
                            method.putParam(OperationExecutor.SCHEME_KEY, data);
                            listener.onProcessFunction(method, null);
                        } else if (actionEnum == PerformActionEnum.LOAD_PARAM) {
                            method.putParam(OperationExecutor.APP_URL_KEY, data);

                            // 向handler发送请求
                            listener.onProcessFunction(method, null);
                        }
                    }
                }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                        dialog.dismiss();
                        listener.onCancel();
                    }
                }).create();
        dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
        dialog.setCanceledOnTouchOutside(false);                                   //点击外面区域不会让dialog消失
        dialog.setCancelable(false);
        dialog.show();
    }

    /**
     * 设置变量框
     * @param node
     * @param context
     * @param listener
     */
    private static void chooseLetMode(AbstractNodeTree node, final Context context,
                                      final FunctionListener listener, final OperationService service) {
        if (node == null) {
            LogUtil.e(TAG, "Receive null node, can't let value");

            listener.onCancel();
            return;
        }

        // 如果是TextView外面包装的一层,解析内部的TextView
        if (node.getChildrenNodes() != null && node.getChildrenNodes().size() == 1) {
            AbstractNodeTree child = node.getChildrenNodes().get(0);

            if (StringUtil.equals(child.getClassName(), "android.widget.TextView")) {
                node = child;
            }
        }

        // 获取页面
        View letView = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context,
                R.style.AppDialogTheme)).inflate(R.layout.dialog_action_let, null);

        // 分别设置内容
        final CheckableRelativeLayout textWrapper = (CheckableRelativeLayout) letView.findViewById(
                R.id.dialog_action_let_text);
        ((TextView)textWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__text);
        ((TextView)textWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getText());
        final CheckableRelativeLayout descWrapper = (CheckableRelativeLayout) letView.findViewById(
                R.id.dialog_action_let_description);
        ((TextView)descWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__description);
        ((TextView)descWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getDescription());
        final CheckableRelativeLayout classWrapper = (CheckableRelativeLayout) letView.findViewById(
                R.id.dialog_action_let_class_name);
        ((TextView)classWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__class_name);
        ((TextView)classWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getClassName());
        final CheckableRelativeLayout xpathWrapper = (CheckableRelativeLayout) letView.findViewById(
                R.id.dialog_action_let_xpath);
        ((TextView)xpathWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__xpath);
        ((TextView)xpathWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getXpath());
        final CheckableRelativeLayout resIdWrapper = (CheckableRelativeLayout) letView.findViewById(
                R.id.dialog_action_let_resource_id);
        ((TextView)resIdWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__res_id);
        ((TextView)resIdWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getResourceId());
        final CheckableRelativeLayout otherWrapper = (CheckableRelativeLayout) letView.findViewById(
                R.id.dialog_action_let_other);
        final EditText valExpr = (EditText) otherWrapper.findViewById(R.id.dialog_action_let_other_value);
        final RadioGroup valType = (RadioGroup) otherWrapper.findViewById(R.id.dialog_action_let_other_type);
        final TextView valVal = (TextView) otherWrapper.findViewById(R.id.dialog_action_let_other_value_val);
        final AbstractNodeTree finalNode = node;
        valType.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                if (service != null) {
                    String expr = valExpr.getText().toString();
                    if (StringUtil.isEmpty(expr)) {
                        valVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                        return;
                    }

                    String val = LogicUtil.eval(expr, finalNode, checkedId == R.id.dialog_action_let_other_type_int ?
                            LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service);
                    if (val != null) {
                        valVal.setText(context.getString(R.string.action_let_cur_value, val));
                    } else {
                        valVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                    }
                }
            }
        });
        valExpr.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (service != null) {
                    String expr = s.toString();
                    if (StringUtil.isEmpty(expr)) {
                        valVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                        return;
                    }

                    String val = LogicUtil.eval(expr, finalNode, valType.getCheckedRadioButtonId() == R.id.dialog_action_let_other_type_int ?
                            LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service);
                    if (val != null) {
                        valVal.setText(context.getString(R.string.action_let_cur_value, val));
                    } else {
                        valVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                    }
                }
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

        final CheckableRelativeLayout[] previous = {textWrapper};
        final String[] valValue = { "${node.text}" };

        previous[0].setChecked(true);
        CheckableRelativeLayout.OnCheckedChangeListener checkedChangeListener = new CheckableRelativeLayout.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CheckableRelativeLayout checkable, boolean isChecked) {
                // 如果是自己点自己,不允许取消勾选
                if (previous[0] == checkable) {
//                    checkable.setChecked(true);
                    return;
                }

                if (previous[0] == otherWrapper) {
                    valExpr.setEnabled(false);
                    valType.setEnabled(false);
                }

                previous[0].setChecked(false);
                if (checkable == textWrapper) {
                    valValue[0] = "${node.text}";
                } else if (checkable == descWrapper) {
                    valValue[0] = "${node.description}";
                } else if (checkable == classWrapper) {
                    valValue[0] = "${node.className}";
                } else if (checkable == xpathWrapper) {
                    valValue[0] = "${node.xpath}";
                } else if (checkable == resIdWrapper) {
                    valValue[0] = "${node.resourceId}";
                } else if (checkable == otherWrapper) {
                    valValue[0] = "";
                    valExpr.setEnabled(true);
                    valType.setEnabled(true);
                }

                previous[0] = checkable;
            }
        };
        textWrapper.setOnCheckedChangeListener(checkedChangeListener);
        descWrapper.setOnCheckedChangeListener(checkedChangeListener);
        classWrapper.setOnCheckedChangeListener(checkedChangeListener);
        xpathWrapper.setOnCheckedChangeListener(checkedChangeListener);
        resIdWrapper.setOnCheckedChangeListener(checkedChangeListener);
        otherWrapper.setOnCheckedChangeListener(checkedChangeListener);

        final EditText valName = (EditText) letView.findViewById(R.id.dialog_action_let_variable_name);

        AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                .setTitle(R.string.function__set_variable)
                .setView(letView)
                .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        LogUtil.i(TAG, "Positive " + which);
                        String targetValValue = valValue[0];
                        String targetValName = valName.getText().toString();
                        int targetValType = LogicUtil.ALLOC_TYPE_STRING;
                        if (previous[0] == otherWrapper) {
                            targetValValue = valExpr.getText().toString();
                            targetValType = valType.getCheckedRadioButtonId() == R.id.dialog_action_let_other_type_int?
                                    LogicUtil.ALLOC_TYPE_INTEGER: LogicUtil.ALLOC_TYPE_STRING;
                        }

                        dialog.dismiss();
                        OperationMethod method = new OperationMethod(PerformActionEnum.LET_NODE);
                        method.putParam(LogicUtil.ALLOC_TYPE, Integer.toString(targetValType));
                        method.putParam(LogicUtil.ALLOC_VALUE_PARAM, targetValValue);
                        method.putParam(LogicUtil.ALLOC_KEY_PARAM, targetValName);

                        listener.onProcessFunction(method, finalNode);
                    }
                }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                        dialog.dismiss();
                        listener.onCancel();
                    }
                }).create();
        dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
        dialog.setCanceledOnTouchOutside(false);                                   //点击外面区域不会让dialog消失
        dialog.setCancelable(false);
        dialog.show();
    }



    /**
     * 动态赋值选择框
     * @param context
     * @param listener
     */
    private static void chooseCheckMode(AbstractNodeTree node, final Context context,
                                            final FunctionListener listener, final OperationService service) {

        // 如果是TextView外面包装的一层,解析内部的TextView
        if (node != null) {
            if (node.getChildrenNodes() != null && node.getChildrenNodes().size() == 1) {
                AbstractNodeTree child = node.getChildrenNodes().get(0);

                if (StringUtil.equals(child.getClassName(), "android.widget.TextView")) {
                    node = child;
                }
            }
        }

        final AbstractNodeTree finalNode = node;

        // 获取页面
        View checkView = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context,
                R.style.AppDialogTheme)).inflate(R.layout.dialog_action_check_global, null);
        final EditText leftExpr = (EditText) checkView.findViewById(R.id.dialog_action_check_left_value);
        final TextView leftVal = (TextView) checkView.findViewById(R.id.dialog_action_check_left_value_val);
        final EditText rightExpr = (EditText) checkView.findViewById(R.id.dialog_action_check_right_value);
        final TextView rightVal = (TextView) checkView.findViewById(R.id.dialog_action_check_right_value_val);

        final RadioGroup valType = (RadioGroup) checkView.findViewById(R.id.dialog_action_check_type);
        final RadioGroup compareType = (RadioGroup) checkView.findViewById(R.id.dialog_action_check_compare);

        final RadioButton bigger = (RadioButton) compareType.findViewById(R.id.dialog_action_check_compare_bigger);
        final RadioButton biggerEqual = (RadioButton) compareType.findViewById(R.id.dialog_action_check_compare_bigger_equal);
        final RadioButton less = (RadioButton) compareType.findViewById(R.id.dialog_action_check_compare_less);
        final RadioButton lessEqual = (RadioButton) compareType.findViewById(R.id.dialog_action_check_compare_less_equal);

        valType.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                if (service != null) {
                    String expr = leftExpr.getText().toString();
                    if (StringUtil.isEmpty(expr)) {
                        leftVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                    } else {
                        String val = LogicUtil.eval(expr, finalNode, checkedId == R.id.dialog_action_check_type_int ?
                                LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service);
                        if (val != null) {
                            leftVal.setText(context.getString(R.string.action_let_cur_value, val));
                        } else {
                            leftVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                        }
                    }

                    expr = rightExpr.getText().toString();
                    if (StringUtil.isEmpty(expr)) {
                        rightVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                    } else {
                        String val = LogicUtil.eval(expr, finalNode, checkedId == R.id.dialog_action_check_type_int ?
                                LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service);
                        if (val != null) {
                            rightVal.setText(context.getString(R.string.action_let_cur_value, val));
                        } else {
                            rightVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                        }
                    }
                }

                if (checkedId == R.id.dialog_action_check_type_str) {
                    bigger.setEnabled(false);
                    biggerEqual.setEnabled(false);
                    less.setEnabled(false);
                    lessEqual.setEnabled(false);
                } else {
                    bigger.setEnabled(true);
                    biggerEqual.setEnabled(true);
                    less.setEnabled(true);
                    lessEqual.setEnabled(true);
                }

            }
        });
        leftExpr.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (service != null) {
                    String expr = s.toString();
                    if (StringUtil.isEmpty(expr)) {
                        leftVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                        return;
                    }

                    String val = LogicUtil.eval(expr, finalNode, valType.getCheckedRadioButtonId() == R.id.dialog_action_check_type_int ?
                            LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service);
                    if (val != null) {
                        leftVal.setText(context.getString(R.string.action_let_cur_value, val));
                    } else {
                        leftVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                    }
                }
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

        rightExpr.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (service != null) {
                    String expr = s.toString();
                    if (StringUtil.isEmpty(expr)) {
                        rightVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                        return;
                    }

                    String val = LogicUtil.eval(expr, finalNode, valType.getCheckedRadioButtonId() == R.id.dialog_action_check_type_int ?
                            LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service);
                    if (val != null) {
                        rightVal.setText(context.getString(R.string.action_let_cur_value, val));
                    } else {
                        rightVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                    }
                }
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

        AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                .setTitle("请设置比较内容")
                .setView(checkView)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        LogUtil.i(TAG, "Positive " + which);
                        String leftVal = leftExpr.getText().toString();
                        String rightVal = rightExpr.getText().toString();
                        int targetValType = valType.getCheckedRadioButtonId() == R.id.dialog_action_check_type_int?
                                LogicUtil.ALLOC_TYPE_INTEGER: LogicUtil.ALLOC_TYPE_STRING;

                        dialog.dismiss();
                        OperationMethod method;
                        if (finalNode != null) {
                            method = new OperationMethod(PerformActionEnum.CHECK_NODE);
                        } else {
                            method = new OperationMethod(PerformActionEnum.CHECK);
                        }

                        String connector;
                        int id = compareType.getCheckedRadioButtonId();
                        if (targetValType == LogicUtil.ALLOC_TYPE_INTEGER) {
                            if (id == R.id.dialog_action_check_compare_equal) {
                                connector = "==";

                            } else if (id == R.id.dialog_action_check_compare_no_equal) {
                                connector = "<>";

                            } else if (id == R.id.dialog_action_check_compare_bigger) {
                                connector = ">";

                            } else if (id == R.id.dialog_action_check_compare_bigger_equal) {
                                connector = ">=";

                            } else if (id == R.id.dialog_action_check_compare_less) {
                                connector = "<";

                            } else if (id == R.id.dialog_action_check_compare_less_equal) {
                                connector = "<=";
                            } else {
                                LogUtil.w(TAG, "Can't recognize type " + targetValType);
                                listener.onCancel();
                                return;
                            }
                        } else {
                            if (id == R.id.dialog_action_check_compare_equal) {
                                connector = "=";

                            } else if (id == R.id.dialog_action_check_compare_no_equal) {
                                connector = "!=";
                            } else {
                                LogUtil.w(TAG, "Can't recognize type " + targetValType);
                                listener.onCancel();
                                return;
                            }
                        }
                        method.putParam(LogicUtil.CHECK_PARAM, leftVal + connector + rightVal);
                        listener.onProcessFunction(method, finalNode);
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                        dialog.dismiss();
                        listener.onCancel();
                    }
                }).create();
        dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
        dialog.setCanceledOnTouchOutside(false);                                   //点击外面区域不会让dialog消失
        dialog.setCancelable(false);
        dialog.show();
    }

    /**
     * 动态赋值选择框
     * @param context
     * @param listener
     */
    private static void chooseLetGlobalMode(final Context context,
                                      final FunctionListener listener, final OperationService service) {

        // 获取页面
        View letView = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context,
                R.style.AppDialogTheme)).inflate(R.layout.dialog_action_let_global, null);
        final EditText valExpr = (EditText) letView.findViewById(R.id.dialog_action_let_other_value);
        final RadioGroup valType = (RadioGroup) letView.findViewById(R.id.dialog_action_let_other_type);
        final TextView valVal = (TextView) letView.findViewById(R.id.dialog_action_let_other_value_val);
        valType.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                if (service != null) {
                    String expr = valExpr.getText().toString();
                    if (StringUtil.isEmpty(expr)) {
                        valVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                        return;
                    }

                    String val = LogicUtil.eval(expr, null, checkedId == R.id.dialog_action_let_other_type_int ?
                            LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service);
                    if (val != null) {
                        valVal.setText(context.getString(R.string.action_let_cur_value, val));
                    } else {
                        valVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                    }
                }
            }
        });
        valExpr.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (service != null) {
                    String expr = s.toString();
                    if (StringUtil.isEmpty(expr)) {
                        valVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                        return;
                    }

                    String val = LogicUtil.eval(expr, null, valType.getCheckedRadioButtonId() == R.id.dialog_action_let_other_type_int ?
                            LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service);
                    if (val != null) {
                        valVal.setText(context.getString(R.string.action_let_cur_value, val));
                    } else {
                        valVal.setText(context.getString(R.string.action_let_cur_value, "-"));
                    }
                }
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

        final EditText valName = (EditText) letView.findViewById(R.id.dialog_action_let_variable_name);

        AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                .setTitle("请设置变量值")
                .setView(letView)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        LogUtil.i(TAG, "Positive " + which);
                        String targetValName = valName.getText().toString();
                        String targetValValue = valExpr.getText().toString();
                        int targetValType = valType.getCheckedRadioButtonId() == R.id.dialog_action_let_other_type_int?
                                    LogicUtil.ALLOC_TYPE_INTEGER: LogicUtil.ALLOC_TYPE_STRING;

                        dialog.dismiss();
                        OperationMethod method = new OperationMethod(PerformActionEnum.LET);
                        method.putParam(LogicUtil.ALLOC_TYPE, Integer.toString(targetValType));
                        method.putParam(LogicUtil.ALLOC_VALUE_PARAM, targetValValue);
                        method.putParam(LogicUtil.ALLOC_KEY_PARAM, targetValName);

                        listener.onProcessFunction(method, null);
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                        dialog.dismiss();
                        listener.onCancel();
                    }
                }).create();
        dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
        dialog.setCanceledOnTouchOutside(false);                                   //点击外面区域不会让dialog消失
        dialog.setCancelable(false);
        dialog.show();
    }

    /**
     * 选择断言模式
     *
     * @param node
     */
    private static void chooseAssertMode(final AbstractNodeTree node, final PerformActionEnum action,
                                  final Context context, final FunctionListener listener) {
        try {
            // 文字Assert Mode
            final String[] actionsType = {Constant.ASSERT_ACCURATE, Constant.ASSERT_CONTAIN, Constant.ASSERT_REGULAR};
            // 数字Assert Mode
            final String[] numActionsType = {Constant.ASSERT_DAYU, Constant.ASSERT_DAYUANDEQUAL,
                    Constant.ASSERT_XIAOYU, Constant.ASSERT_XIAOYUANDEQUAL, Constant.ASSERT_EQUAL};

            // 判断当前内容是否是数字
            StringBuilder matchTxtBuilder = new StringBuilder();
            if (action == PerformActionEnum.ASSERT) {
                for (AbstractNodeTree item : node) {
                    if (!TextUtils.isEmpty(item.getText())) {
                        matchTxtBuilder.append(item.getText());
                    }
                }
            } else if (action == PerformActionEnum.ASSERT_TOAST) {
                matchTxtBuilder.append(InjectorService.g().getMessage(com.alipay.hulu.shared.event.constant.Constant.EVENT_TOAST_MSG, String.class));
            }

            final int[] selectNumIndex = new int[1];
            final String[] strResult = {null};

            String matchTxt = matchTxtBuilder.toString();

            if (StringUtil.isNumeric(matchTxt) && !TextUtils.isEmpty(matchTxt)) {
                View content = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_assert_number, null);
                final EditText assertNumContentEdit = (EditText) content.findViewById(R.id.assert_num_edittext);
                FlowRadioGroup assertGroup = (FlowRadioGroup) content.findViewById(R.id.assert_choice);
                assertGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                    @Override
                    public void onCheckedChanged(RadioGroup group, int checkedId) {
                       if (checkedId == R.id.ch1) {
                            selectNumIndex[0] = 0;
                        } else if (checkedId == R.id.ch2) {
                            selectNumIndex[0] = 1;
                        } else if (checkedId == R.id.ch3) {
                            selectNumIndex[0] = 2;
                        } else if (checkedId == R.id.ch4) {
                            selectNumIndex[0] = 3;
                        } else if (checkedId == R.id.ch5) {
                            selectNumIndex[0] = 4;
                        }
                    }
                });

                assertNumContentEdit.addTextChangedListener(new TextWatcher() {
                    @Override
                    public void onTextChanged(CharSequence s, int start, int before, int count) {

                    }

                    @Override
                    public void beforeTextChanged(CharSequence s, int start,
                                                  int count, int after) {

                    }

                    @Override
                    public void afterTextChanged(Editable s) {
                        if (!TextUtils.isEmpty(assertNumContentEdit.getEditableText().toString())) {
                            strResult[0] = assertNumContentEdit.getEditableText().toString();
                        }
                    }
                });

                AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                        .setTitle(R.string.function__input_number_assert)
                        .setView(content)
                        .setPositiveButton(R.string.constant__confirm, null)
                        .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                LogUtil.i(TAG, "Negative " + which);
                                dialog.dismiss();
                                listener.onCancel();
                            }
                        }).create();
                dialog.setOnShowListener(new DialogInterface.OnShowListener() {
                    @Override
                    public void onShow(final DialogInterface dialog) {
                        Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
                        button.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                if (StringUtil.isNumeric(strResult[0])) {
                                    HashMap<String, String> param = new HashMap<>();
                                    param.put(OperationExecutor.ASSERT_MODE, numActionsType[selectNumIndex[0]]);
                                    param.put(OperationExecutor.ASSERT_INPUT_CONTENT, strResult[0]);
                                    postiveClick(action, node, dialog, param, listener);
                                } else {
                                    LauncherApplication.toast(R.string.function__input_number);
                                }
                            }
                        });
                    }
                });
                dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
                dialog.setCanceledOnTouchOutside(false);
                dialog.setCancelable(false);
                dialog.show();
            } else {
                View content = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_assert_string, null);
                final EditText editText = (EditText) content.findViewById(R.id.assert_string_edittext);
                final String[] assertInputContent = new String[1];
                if (!TextUtils.isEmpty(matchTxt)) {
                    editText.setText(matchTxt);
                    assertInputContent[0] = matchTxt;
                }

                final int[] selectIndex = new int[1];
                FlowRadioGroup assertGroup = (FlowRadioGroup) content.findViewById(R.id.assert_choice);
                assertGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                    @Override
                    public void onCheckedChanged(RadioGroup group, int checkedId) {
                        if (checkedId == R.id.ch1) {
                            selectIndex[0] = 0;
                        } else if (checkedId == R.id.ch2) {
                            selectIndex[0] = 1;
                        } else if (checkedId == R.id.ch3) {
                            selectIndex[0] = 2;
                        }
                    }
                });

                editText.addTextChangedListener(new TextWatcher() {
                    @Override
                    public void onTextChanged(CharSequence s, int start, int before, int count) {
                    }

                    @Override
                    public void beforeTextChanged(CharSequence s, int start,
                                                  int count, int after) {

                    }

                    @Override
                    public void afterTextChanged(Editable s) {
                        assertInputContent[0] = editText.getEditableText().toString();
                    }
                });

                AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                        .setTitle(R.string.function__input_select_assert)
                        .setView(content)
                        .setPositiveButton(R.string.constant__confirm, null)
                        .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                LogUtil.i(TAG, "Negative " + which);

                                dialog.dismiss();
                                listener.onCancel();
                            }
                        }).create();
                dialog.setOnShowListener(new DialogInterface.OnShowListener() {
                    @Override
                    public void onShow(final DialogInterface dialog) {
                        Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
                        button.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                if (!TextUtils.isEmpty(assertInputContent[0])) {
                                    HashMap<String, String> param = new HashMap<>();
                                    param.put(OperationExecutor.ASSERT_MODE, actionsType[selectIndex[0]]);
                                    param.put(OperationExecutor.ASSERT_INPUT_CONTENT, assertInputContent[0]);

                                    postiveClick(action, node, dialog,
                                            param, listener);

                                } else {
                                    LauncherApplication.toast(R.string.function__input_content);
                                }
                            }
                        });
                    }
                });
                dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
                dialog.setCanceledOnTouchOutside(false);
                dialog.setCancelable(false);
                dialog.show();
            }
        } catch (Exception e) {
            LogUtil.e(TAG, "Throw exception: " + e.getMessage(), e);
            listener.onCancel();
        }
    }



    private static void postiveClick(final PerformActionEnum action, final AbstractNodeTree node,
                                     DialogInterface dialog, HashMap<String, String> param,
                                     final FunctionListener listener) {
        // 拼装参数
        final OperationMethod method = new OperationMethod(action);
        if (param != null) {
            for (String key : param.keySet()) {
                method.putParam(key, param.get(key));
            }
        }
        // 隐藏Dialog
        dialog.dismiss();

        // 等隐藏Dialog
        LauncherApplication.getInstance().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // 向handler发送点击请求
                listener.onProcessFunction(method, node);
            }
        }, 200);
    }

    /**
     * 展示输入界面
     *
     * @param node
     */
    protected static void showEditView(final AbstractNodeTree node, final OperationMethod method,
                                final Context context, final FunctionListener listener) {

        try {
            PerformActionEnum action = method.getActionEnum();
            String title = StringUtil.getString(R.string.function__input_title);
            View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_record_name, null);
            final EditText edit = (EditText) v.findViewById(R.id.dialog_record_edit);
            //新增的模板输入框
            final EditText templateEdit = (EditText) v.findViewById(R.id.dialog_template_edit);
            templateEdit.setText("");//每次输入前都先清空下,防止append导致上传数据错误
            View hide = v.findViewById(R.id.dialog_record_edit_hide);
            hide.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    hideInput(edit);
                }
            });
            final Pattern textPattern;
            if (action == PerformActionEnum.SLEEP) {
                edit.setHint(R.string.function__sleep_time);
                title = StringUtil.getString(R.string.function__set_sleep_time);
                textPattern = Pattern.compile("\\d+");
            } else if (action == PerformActionEnum.SCREENSHOT) {
                edit.setHint(R.string.function__screenshot_name);
                title = StringUtil.getString(R.string.function__set_screenshot_name);
                textPattern = Pattern.compile("\\S+(.*\\S+)?");
            } else if (action ==PerformActionEnum.MULTI_CLICK) {
                edit.setHint(R.string.function__click_time);
                title = StringUtil.getString(R.string.function__set_click_time);
                textPattern = Pattern.compile("\\d{1,2}");
            } else if (action ==PerformActionEnum.SLEEP_UNTIL) {
                edit.setHint(R.string.function__max_wait);
                edit.setText(R.string.default_sleep_time);
                title = StringUtil.getString(R.string.function__set_max_wait);
                textPattern = Pattern.compile("\\d+");
            } else if (action == PerformActionEnum.SCROLL_TO_BOTTOM
                    || action == PerformActionEnum.SCROLL_TO_TOP
                    || action == PerformActionEnum.SCROLL_TO_LEFT
                    || action == PerformActionEnum.SCROLL_TO_RIGHT) {
                edit.setHint(R.string.function__scroll_percent);
                edit.setText(R.string.default_scroll_percentage);
                title = StringUtil.getString(R.string.function__set_scroll_percent);
                textPattern = Pattern.compile("\\d+");
            } else if (action == PerformActionEnum.EXECUTE_SHELL) {
                edit.setHint(R.string.function__adb_cmd);
                title = StringUtil.getString(R.string.function__set_adb_cmd);
                textPattern = null;
            } else if (action == PerformActionEnum.LONG_CLICK) {
                edit.setHint(R.string.function__long_press);
                title = StringUtil.getString(R.string.function__set_long_press);
                textPattern = Pattern.compile("[1-9]\\d+");
                edit.setText(R.string.default_long_click_time);
            } else {
                edit.setHint(R.string.function__input_content);
                textPattern = null;
            }

            final AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                    .setTitle(title)
                    .setView(v)
                    .setPositiveButton(R.string.function__input, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            LogUtil.i(TAG, "Positive " + which);
                            String data = edit.getText().toString();
                            String templateText = templateEdit.getText().toString();//获得模板key

                            // 拼装参数
                            method.putParam(OperationExecutor.INPUT_TEXT_KEY, data);
                            method.putParam(OperationExecutor.INPUT_TEMPLATE_KEY,templateText);//将模板key写入到param

                            // 隐藏Dialog
                            dialog.dismiss();

                            // 抛给主线程
                            LauncherApplication.getInstance().runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    // 操作记录
                                    listener.onProcessFunction(method, node);
                                }
                            }, 500);
                        }
                    }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            LogUtil.i(TAG, "Negative " + which);

                            dialog.dismiss();
                            listener.onCancel();
                        }
                    }).create();

            dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
            dialog.setCanceledOnTouchOutside(false);                                   //点击外面区域不会让dialog消失
            dialog.setCancelable(false);
            dialog.show();

            // 校验输入
            edit.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {

                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {

                }

                @Override
                public void afterTextChanged(Editable s) {
                    Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
                    boolean enable = true;
                    if (textPattern != null) {
                        String content = s.toString();
                        enable = textPattern.matcher(content).matches();
                    }

                    // 如果不是目标状态,改变下
                    if (positiveButton.isEnabled() != enable) {
                        positiveButton.setEnabled(enable);
                    }
                }
            });

            dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);

        } catch (Exception e) {
            LogUtil.e(TAG, "Throw exception: " + e.getMessage(), e);

        }
    }

    /**
     * 隐藏输入法
     * @param editText
     */
    private static void hideInput(EditText editText) {
        InputMethodManager inputMethodManager = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }

    /**
     * 展示WHILE编辑界面
     * @param method
     * @param context
     * @param listener
     */
    private static void showWhileView(final OperationMethod method, final Context context, final FunctionListener listener) {
        View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_while_setting_panel, null);
        final EditText edit = (EditText) v.findViewById(R.id.edit_while_param);
        final AppCompatSpinner spinner = (AppCompatSpinner) v.findViewById(R.id.spinner_while_mode);
        final TextView hint = (TextView) v.findViewById(R.id.text_while_param_hint);

        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                // 清理文字
                edit.setText("");
                edit.clearComposingText();

                if (position == 0) {
                    hint.setText(R.string.function__loop_count);
                    edit.setHint(R.string.function__loop_count);
                    edit.setInputType(InputType.TYPE_CLASS_NUMBER);
                } else {
                    hint.setText(R.string.function__loop_condition);
                    edit.setHint(R.string.function__loop_condition);
                    edit.setInputType(InputType.TYPE_CLASS_TEXT);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

        final AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                .setTitle(R.string.function__add_loop)
                .setView(v)
                .setPositiveButton(R.string.function__add, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        LogUtil.i(TAG, "Positive " + which);
                        String data = edit.getText().toString();

                        String prefix = "";
                        if (spinner.getSelectedItemPosition() == 0) {
                            prefix = LogicUtil.LOOP_PREFIX;
                        }
                        // 拼装参数
                        method.putParam(LogicUtil.CHECK_PARAM, prefix + data);

                        // 隐藏Dialog
                        dialog.dismiss();

                        // 抛给主线程
                        LauncherApplication.getInstance().runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                // 操作记录
                                listener.onProcessFunction(method, null);
                            }
                        }, 500);
                    }
                }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        LogUtil.i(TAG, "Negative " + which);

                        dialog.dismiss();
                        listener.onCancel();
                    }
                }).create();

        dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
        dialog.setCanceledOnTouchOutside(false);                                   //点击外面区域不会让dialog消失
        dialog.setCancelable(false);
        dialog.show();
        dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
    }

    /**
     * 展示提供的View
     * @param node
     * @param method
     * @param context
     * @param content 目标界面
     */
    private static void showProvidedView(final AbstractNodeTree node, final OperationMethod method,
                                  final Context context, View content,
                                  final Runnable confirmListener,
                                  final HighLightService highLightService,
                                  final FunctionListener listener) {
        ScrollView view = (ScrollView) LayoutInflater.from(ContextUtil.getContextThemeWrapper(
                context, R.style.AppDialogTheme))
                .inflate(R.layout.dialog_setting, null);
        LinearLayout wrapper = (LinearLayout) view.findViewById(R.id.dialog_content);

        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        wrapper.addView(content, layoutParams);

        // 显示Dialog
        AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                .setView(view)
                .setCancelable(false)
                .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        LogUtil.i(TAG, "Positive " + which);

                        // 隐藏Dialog
                        dialog.dismiss();

                        // 如果有回调,在后台执行
                        if (confirmListener != null) {
                            BackgroundExecutor.execute(new Runnable() {
                                @Override
                                public void run() {
                                    confirmListener.run();
                                    listener.onProcessFunction(method, node);
                                }
                            });
                        } else {
                            listener.onProcessFunction(method, node);
                        }
                    }
                }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        LogUtil.i(TAG, "Negative " + which);

                        dialog.dismiss();

                        listener.onCancel();

                        if (highLightService != null) {
                            highLightService.removeHighLight();
                        } // 去除高亮
                    }
                }).create();

        dialog.setTitle(null);
        dialog.setCanceledOnTouchOutside(false);
        dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
        dialog.show();
    }

    /**
     * 展示登录信息框
     * @param action
     * @param context
     */
    private static void captureAndShowGesture(final PerformActionEnum action, final AbstractNodeTree target, Context context, final FunctionListener listener) {
        try {
            View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_node_gesture, null);
            final GesturePadView gesturePadView = (GesturePadView) v.findViewById(R.id.node_gesture_gesture_view);
            final RadioGroup group = (RadioGroup) v.findViewById(R.id.node_gesture_time_filter);
            group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(RadioGroup group, int checkedId) {
                    int targetTime;
                    if (checkedId == R.id.node_gesture_time_filter_25) {
                        targetTime = 25;
                    } else if (checkedId == R.id.node_gesture_time_filter_50) {
                        targetTime = 50;
                    } else if (checkedId == R.id.node_gesture_time_filter_200) {
                        targetTime = 200;
                    } else {
                        targetTime = 100;
                    }
                    gesturePadView.setGestureFilter(targetTime);
                    gesturePadView.clear();
                }
            });
            Bitmap nodeBitmap;
            if (target != null) {
                String capture = target.getCapture();
                if (StringUtil.isEmpty(capture)) {
                    File captureFile = new File(FileUtils.getSubDir("tmp"), "test.jpg");
                    Bitmap bitmap = capture(captureFile);
                    if (bitmap == null) {
                        LauncherApplication.getInstance().showToast(context.getString(R.string.action_gesture__capture_screen_failed));
                        listener.onCancel();
                        return;
                    }

                    Rect displayRect = target.getNodeBound();

                    nodeBitmap = Bitmap.createBitmap(bitmap, displayRect.left,
                            displayRect.top, displayRect.width(),
                            displayRect.height());
                    target.setCapture(BitmapUtil.bitmapToBase64(nodeBitmap));
                } else {
                    nodeBitmap = BitmapUtil.base64ToBitmap(capture);
                }
            } else {
                File captureFile = new File(FileUtils.getSubDir("tmp"), "test.jpg");
                nodeBitmap = capture(captureFile);
                if (nodeBitmap == null) {
                    LauncherApplication.getInstance().showToast(context.getString(R.string.action_gesture__capture_screen_failed));
                    listener.onCancel();
                    return;
                }
            }

            gesturePadView.setTargetImage(new BitmapDrawable(nodeBitmap));

            AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme)
                    .setTitle(R.string.gesture__please_record_gesture)
                    .setView(v)
                    .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            LogUtil.i(TAG, "Positive " + which);
                            List<PointF> path = gesturePadView.getGesturePath();
                            int gestureFilter = gesturePadView.getGestureFilter();
                            // 拼装参数
                            // 拼装参数
                            OperationMethod method = new OperationMethod(action);
                            method.putParam(OperationExecutor.GESTURE_PATH, JSON.toJSONString(path));
                            method.putParam(OperationExecutor.GESTURE_FILTER, Integer.toString(gestureFilter));

                            // 隐藏Dialog
                            dialog.dismiss();

                            listener.onProcessFunction(method, target);
                        }
                    }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            LogUtil.i(TAG, "Negative " + which);

                            dialog.dismiss();
                            listener.onCancel();
                        }
                    }).create();
            dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT);
            dialog.setCanceledOnTouchOutside(false);                                   //点击外面区域不会让dialog消失
            dialog.setCancelable(false);
            dialog.show();

            dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);

        } catch (Exception e) {
            LogUtil.e(TAG, "Login info dialog throw exception: " + e.getMessage(), e);
        }
    }

    /**
     * 截图
     * @param captureFile 截图保留文件
     * @return
     */
    private static Bitmap capture(File captureFile) {
        DisplayMetrics metrics = new DisplayMetrics();
        ((WindowManager) LauncherApplication.getInstance().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRealMetrics(metrics);

        ScreenCaptureService captureService = LauncherApplication.service(ScreenCaptureService.class);
        Bitmap bitmap = captureService.captureScreen(captureFile, metrics.widthPixels, metrics.heightPixels,
                metrics.widthPixels, metrics.heightPixels);
        // 原有截图方案失败
        if (bitmap == null) {
            String path = FileUtils.getPathInShell(captureFile);
            CmdTools.execHighPrivilegeCmd("screencap -p \"" + path + "\"");
            MiscUtil.sleep(1000);
            bitmap = BitmapFactory.decodeFile(captureFile.getPath());
            // 长宽不对
            if (bitmap != null && bitmap.getWidth() != metrics.widthPixels) {
                bitmap = Bitmap.createScaledBitmap(bitmap, metrics.widthPixels, metrics.heightPixels, false);
            }
        }
        return bitmap;
    }

    /**
     * 回调
     */
    public interface FunctionListener {
        void onProcessFunction(OperationMethod method, AbstractNodeTree node);

        void onCancel();
    }
}
View Code

 

 

关于服务端:见下一篇博客

 

posted @ 2022-05-06 18:08  小屁妞  阅读(460)  评论(0编辑  收藏  举报