安卓端-APPUI自動化實戰【solopi】
當前UI自動化測試存在以下問題:
1.投入產出比低:在目前版本快速迭代的大背景下,app更新較快,維護指令碼成本高,導致投入產出比低
2.對測試人員要求較高:必須有一定的程式設計能力
3.執行穩定性較差,斷言的可靠性不高。
如何解決以上問題,並且儘可能的減少重複造輪子的時間成本?
選擇了支付寶開源的SoloPi自動化測試工具,在移動端上一個無線化、非侵入式、免Root的Android自動化專項測試工具,目前開放的有錄製回放、效能測試、一機多控三項主要功能,能為測試開發人員節省寶貴時間。
github地址:GitHub - alipay/SoloPi: SoloPi 自動化測試工具
詳細介紹:
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 * *View Codehttp://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(); } } }); } }
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
關於服務端:見下一篇部落格