1. 程式人生 > 其它 >安卓端-APPUI自動化實戰【solopi】

安卓端-APPUI自動化實戰【solopi】

當前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連結服務端的時機)。

/*
 * 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.websocket通訊相關,斷開連結時自動重連(solopi連結服務端的時機)

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

 

啟動APP時連結伺服器【由於裝置資訊是在獲取adb許可權之後才可以獲得,在啟動頁加了許可權判斷】

/*
 * 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

 

二:用例上傳

/*
 * 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方便,需要手動上傳的,後續會改成自動上傳】

/*
 * 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

 

四:模板引數【當前是在輸入框控制元件下增加了模板引數輸入框,用例錄製時輸入模板引數,後續服務端進行模板替換】

/*
 * 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

 

 

關於服務端:見下一篇部落格