安卓端-APPUI自动化实战【上】
当前UI自动化测试存在以下问题:
1.投入产出比低:在目前版本快速迭代的大背景下,app更新较快,维护脚本成本高,导致投入产出比低
2.对测试人员要求较高:必须有一定的编程能力
3.运行稳定性较差,断言的可靠性不高。
如何解决以上问题,并且尽可能的减少重复造轮子的时间成本?
选择了支付宝开源的SoloPi自动化测试工具,在移动端上一个无线化、非侵入式、免Root的Android自动化专项测试工具,目前开放的有录制回放、性能测试、一机多控三项主要功能,能为测试开发人员节省宝贵时间。
github地址:GitHub - alipay/SoloPi: SoloPi 自动化测试工具
详细介绍:SoloPi:支付宝开源的Android专项测试工具_测试_温元良_InfoQ精选文章
solopi的整体架构:这套方案中,底层依赖主要是“无线 ADB、系统辅助功能、Chrome 调试以及图像识别技术”。
accessibility service主要是获取视图,获取空间信息适用于native场景
ChromeDevTools主要适用于 h5和小程序场景,当前主流APP基本都是混合型APP,因此需要这种能力
图像识别 只要适用于游戏类APP
我们对solopiAPP进行了2次开发,主要实现了以下功能:
1.solopi可以与服务端(测试管理平台,以下简称服务端)通过websocket通信,无需数据线链接,在同一网段即可通信。
2.solopi录制用例,将用例上传到服务端,服务端集中管理用例,
3.服务端选择用例集下发到solopi,solopi接收后自动执行,执行完成后将结果上传到服务端,服务端可以查看测试报告。
服务端下发用例支持顺序下发,
服务端下发用例时可以实现模板替换,支持不同手机设备上动态化的替换指定的参数
关于solopi端:
一:通信
1.服务端链接地址配置:修改源码,手动输入服务端链接地址,并且点击确认按钮时,链接服务器(solopi链接服务端的时机)。
核心代码:
/** * 注册点击事件 */ mServerSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) {//输入框弹窗输入数据长度判断为1,取第一个数据 String path = data.get(0); //判断输入的服务端链接是否以ws开头,是则设置到textview里,不是则弹出弹窗 if (StringUtil.startWith(path, "ws")) { SPService.putString(SPService.KEY_SERVER_SETTING, path); RealTimeManage.openConnect();//每次修改服务端链接地址时,自动重连 } else { DialogUtils.DialogInfo.Builder builder = new DialogUtils.DialogInfo.Builder(); builder.setTitle("地址有误"); builder.setShowContent("请设置webscoket地址,ws开头!!!"); builder.setPositiveButtonText("确认"); DialogUtils.createDialog(SettingsActivity.this,builder.build(), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }).show(); return; } if (StringUtil.isEmpty(path)) { mServerSettingInfo.setText(R.string.constant__not_config); } else { mServerSettingInfo.setText(path); } } } }, getString(R.string.server_setting_url), Collections.singletonList(new Pair<>(getString(R.string.server_setting_url), SPService.getString(SPService.KEY_SERVER_SETTING)))); } });
注册点击事件。判断输入的地址是否以ws开头,则成功设置并链接服务器,如果不是则弹窗提示。
详细代码如下:
/* * Copyright (C) 2015-present, Ant Financial Services Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.hulu.activity; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AlertDialog; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import android.widget.TextView; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONObject; import com.alipay.hulu.R; import com.alipay.hulu.common.application.LauncherApplication; import com.alipay.hulu.common.bean.DeviceInfo; import com.alipay.hulu.common.service.SPService; import com.alipay.hulu.common.tools.BackgroundExecutor; import com.alipay.hulu.common.utils.AESUtils; import com.alipay.hulu.common.utils.ClassUtil; import com.alipay.hulu.common.utils.ContextUtil; import com.alipay.hulu.common.utils.DeviceInfoUtil; import com.alipay.hulu.common.utils.FileUtils; import com.alipay.hulu.common.utils.LogUtil; import com.alipay.hulu.common.utils.PatchProcessUtil; import com.alipay.hulu.common.utils.StringUtil; import com.alipay.hulu.common.utils.activity.FileChooseDialogActivity; import com.alipay.hulu.common.utils.patch.PatchLoadResult; import com.alipay.hulu.runManager.CaseRunMangage; import com.alipay.hulu.runManager.JwebSocketClient; import com.alipay.hulu.runManager.RealTimeManage; import com.alipay.hulu.shared.io.bean.GeneralOperationLogBean; import com.alipay.hulu.shared.io.bean.RecordCaseInfo; import com.alipay.hulu.shared.io.db.GreenDaoManager; import com.alipay.hulu.shared.io.db.RecordCaseInfoDao; import com.alipay.hulu.shared.io.util.OperationStepUtil; import com.alipay.hulu.shared.node.action.OperationMethod; import com.alipay.hulu.shared.node.tree.export.bean.OperationStep; import com.alipay.hulu.ui.HeadControlPanel; import com.alipay.hulu.upgrade.PatchRequest; import com.alipay.hulu.util.DialogUtils; import com.alipay.hulu.util.DialogUtils.OnDialogResultListener; import com.zhy.view.flowlayout.FlowLayout; import com.zhy.view.flowlayout.TagAdapter; import com.zhy.view.flowlayout.TagFlowLayout; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import static com.alipay.hulu.util.DialogUtils.showMultipleEditDialog; /** * Created by lezhou.wyl on 01/01/2018. */ public class SettingsActivity extends BaseActivity { private static final String TAG = "SettingsActivity"; private static final int REQUEST_FILE_CHOOSE = 1101; private HeadControlPanel mPanel; private View mRecordUploadWrapper; private TextView mRecordUploadInfo; private View mServerSettingWrapper; private TextView mServerSettingInfo; private View mRecordScreenUploadWrapper; private TextView mRecordScreenUploadInfo; private View mPatchListWrapper; private TextView mPatchListInfo; private View mReplayOtherAppSettingWrapper; private TextView mReplayOtherAppInfo; private View mRestartAppSettingWrapper; private TextView mRestartAppInfo; private View mGlobalParamSettingWrapper; private View mResolutionSettingWrapper; private TextView mResolutionSettingInfo; private View mHightlightSettingWrapper; private TextView mHightlightSettingInfo; private View mLanguageSettingWrapper; private TextView mLanguageSettingInfo; private View mDisplaySystemAppSettingWrapper; private TextView mDisplaySystemAppSettingInfo; private View mAutoReplaySettingWrapper; private TextView mAutoReplaySettingInfo; private View mSkipAccessibilitySettingWrapper; private TextView mSkipAccessibilitySettingInfo; private View mMaxWaitSettingWrapper; private TextView mMaxWaitSettingInfo; private View mDefaultRotationSettingWrapper; private TextView mDefaultRotationSettingInfo; private View mChangeRotationSettingWrapper; private TextView mChangeRotationSettingInfo; private View mCheckUpdateSettingWrapper; private TextView mCheckUpdateSettingInfo; private View mBaseDirSettingWrapper; private TextView mBaseDirSettingInfo; private View mOutputCharsetSettingWrapper; private TextView mOutputCharsetSettingInfo; private View mAesSeedSettingWrapper; private TextView mAesSeedSettingInfo; private View mClearFilesSettingWrapper; private TextView mClearFilesSettingInfo; private View mHideLogSettingWrapper; private TextView mHideLogSettingInfo; private View mAdbServerSettingWrapper; private TextView mAdbServerSettingInfo; private View mImportCaseSettingWrapper; private View mImportPluginSettingWrapper; private View mAboutBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); initView(); initListeners(); } private void initListeners() { mPanel.setBackIconClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SettingsActivity.this.finish(); } }); mAboutBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //startActivity(new Intent(SettingsActivity.this, InfoActivity.class)); DeviceInfo deviceInfo = DeviceInfoUtil.generateDeviceInfo(); CaseRunMangage.getInstance().run(); } }); mGlobalParamSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showGlobalParamEdit(); } }); mDefaultRotationSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setItems(R.array.default_screen_rotation, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String item = getResources().getStringArray(R.array.default_screen_rotation)[which]; SPService.putInt(SPService.KEY_SCREEN_FACTOR_ROTATION, which); if (which == 1 || which == 3) { SPService.putBoolean(SPService.KEY_SCREEN_ROTATION, true); mChangeRotationSettingInfo.setText(R.string.constant__yes); } else { SPService.putBoolean(SPService.KEY_SCREEN_ROTATION, false); mChangeRotationSettingInfo.setText(R.string.constant__no); } mDefaultRotationSettingInfo.setText(item); } }) .setTitle(R.string.setting__set_screen_orientation) .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .show(); } }); mChangeRotationSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setMessage(R.string.setting__change_screen_axis) .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_SCREEN_ROTATION, true); mChangeRotationSettingInfo.setText(R.string.constant__yes); } }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_SCREEN_ROTATION, false); mChangeRotationSettingInfo.setText(R.string.constant__no); } }).show(); } }); mRecordUploadWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) { String path = data.get(0); SPService.putString(SPService.KEY_PERFORMANCE_UPLOAD, path); if (StringUtil.isEmpty(path)) { mRecordUploadInfo.setText(R.string.constant__not_config); } else { mRecordUploadInfo.setText(path); } } } }, getString(R.string.settings__performance_upload_url), Collections.singletonList(new Pair<>(getString(R.string.settings__performance_upload_url), SPService.getString(SPService.KEY_PERFORMANCE_UPLOAD)))); } }); /** * 注册点击事件 */ mServerSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) { String path = data.get(0); //判断输入的服务端链接是否以ws开头,是则设置到textview里,不是则弹出弹窗 if (StringUtil.startWith(path, "ws")) { SPService.putString(SPService.KEY_SERVER_SETTING, path); RealTimeManage.openConnect();//每次修改服务端链接地址时,自动重连 } else { DialogUtils.DialogInfo.Builder builder = new DialogUtils.DialogInfo.Builder(); builder.setTitle("地址有误"); builder.setShowContent("请设置webscoket地址,ws开头!!!"); builder.setPositiveButtonText("确认"); DialogUtils.createDialog(SettingsActivity.this,builder.build(), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }).show(); return; } if (StringUtil.isEmpty(path)) { mServerSettingInfo.setText(R.string.constant__not_config); } else { mServerSettingInfo.setText(path); } } } }, getString(R.string.server_setting_url), Collections.singletonList(new Pair<>(getString(R.string.server_setting_url), SPService.getString(SPService.KEY_SERVER_SETTING)))); } }); mRecordScreenUploadWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) { String path = data.get(0); SPService.putString(SPService.KEY_RECORD_SCREEN_UPLOAD, path); if (StringUtil.isEmpty(path)) { mRecordScreenUploadInfo.setText(R.string.constant__not_config); } else { mRecordScreenUploadInfo.setText(path); } } } }, getString(R.string.settings__record_upload_url), Collections.singletonList(new Pair<>(getString(R.string.settings__record_upload_url), SPService.getString(SPService.KEY_RECORD_SCREEN_UPLOAD)))); } }); mPatchListWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) { String path = data.get(0); SPService.putString(SPService.KEY_PATCH_URL, path); if (StringUtil.isEmpty(path)) { mPatchListInfo.setText(R.string.constant__not_config); } else { mPatchListInfo.setText(path); // 更新patch列表 PatchRequest.updatePatchList(null); } } } }, getString(R.string.settings__plugin_url), Collections.singletonList(new Pair<>(getString(R.string.settings__plugin_url), SPService.getString(SPService.KEY_PATCH_URL, "https://raw.githubusercontent.com/alipay/SoloPi/master/<abi>.json")))); } }); mOutputCharsetSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new DialogUtils.OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) { String charset = data.get(0); SPService.putString(SPService.KEY_OUTPUT_CHARSET, charset); mOutputCharsetSettingInfo.setText(charset); } } }, getString(R.string.settings__output_charset), Collections.singletonList(new Pair<>(getString(R.string.settings__output_charset), SPService.getString(SPService.KEY_OUTPUT_CHARSET)))); } }); mLanguageSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setTitle(R.string.settings__language) .setItems(R.array.language, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putInt(SPService.KEY_USE_LANGUAGE, which); LauncherApplication.getInstance().setApplicationLanguage(); mLanguageSettingInfo.setText(getResources().getStringArray(R.array.language)[which]); // 重启服务 LauncherApplication.getInstance().restartAllServices(); Intent intent = new Intent(SettingsActivity.this, SplashActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); finish(); } }) .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).show(); } }); mReplayOtherAppSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setMessage(R.string.settings__should_replay_in_other_app) .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_ALLOW_REPLAY_DIFFERENT_APP, true); mReplayOtherAppInfo.setText(R.string.constant__yes); dialog.dismiss(); } }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_ALLOW_REPLAY_DIFFERENT_APP, false); mReplayOtherAppInfo.setText(R.string.constant__no); dialog.dismiss(); } }).show(); } }); mRestartAppSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setMessage(R.string.settings__should_restart_before_replay) .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_RESTART_APP_ON_PLAY, true); mRestartAppInfo.setText(R.string.constant__yes); dialog.dismiss(); } }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_RESTART_APP_ON_PLAY, false); mRestartAppInfo.setText(R.string.constant__no); dialog.dismiss(); } }).show(); } }); mDisplaySystemAppSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setMessage(R.string.setting__display_system_app) .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_DISPLAY_SYSTEM_APP, true); mDisplaySystemAppSettingInfo.setText(R.string.constant__yes); MyApplication.getInstance().reloadAppList(); dialog.dismiss(); } }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_DISPLAY_SYSTEM_APP, false); mDisplaySystemAppSettingInfo.setText(R.string.constant__no); MyApplication.getInstance().reloadAppList(); dialog.dismiss(); } }).show(); } }); mAutoReplaySettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setMessage(R.string.setting__auto_replay) .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_REPLAY_AUTO_START, true); mAutoReplaySettingInfo.setText(R.string.constant__yes); dialog.dismiss(); } }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_REPLAY_AUTO_START, false); mAutoReplaySettingInfo.setText(R.string.constant__no); dialog.dismiss(); } }).show(); } }); mSkipAccessibilitySettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setMessage(R.string.setting__skip_accessibility) .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_SKIP_ACCESSIBILITY, true); mSkipAccessibilitySettingInfo.setText(R.string.constant__yes); dialog.dismiss(); } }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_SKIP_ACCESSIBILITY, false); mSkipAccessibilitySettingInfo.setText(R.string.constant__no); dialog.dismiss(); } }).show(); } }); mMaxWaitSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) { String time = data.get(0); SPService.putLong(SPService.KEY_MAX_WAIT_TIME, Long.parseLong(time)); mMaxWaitSettingInfo.setText(time + "ms"); } } }, getString(R.string.settings__max_wait_time), Collections.singletonList(new Pair<>(getString(R.string.setting__max_wait_time), Long.toString(SPService.getLong(SPService.KEY_MAX_WAIT_TIME, 10000))))); } }); mBaseDirSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FileChooseDialogActivity.startFileChooser(SettingsActivity.this, REQUEST_FILE_CHOOSE, getString(R.string.settings__base_dir), "solopi", FileUtils.getSolopiDir()); } }); mAesSeedSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) { String seed = data.get(0); String originSeed = SPService.getString(SPService.KEY_AES_KEY, getApplication().getPackageName()); SPService.putString(SPService.KEY_AES_KEY, seed); // 发生了更新 if (!StringUtil.equals(originSeed, seed)) { updateStoredRecords(originSeed, seed); } mAesSeedSettingInfo.setText(seed); } } }, getString(R.string.settings__encrept_key), Collections.singletonList(new Pair<>(getString(R.string.settings__encrept_key), SPService.getString(SPService.KEY_AES_KEY, "com.alipay.hulu")))); } }); mClearFilesSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) { String days = data.get(0); if (StringUtil.isInteger(days)) { int daysNum = Integer.parseInt(days); if (daysNum < 0) { daysNum = -1; } SPService.putInt(SPService.KEY_AUTO_CLEAR_FILES_DAYS, daysNum); mClearFilesSettingInfo.setText(days); } else { toastShort(R.string.settings__config_failed); } } } }, getString(R.string.settings__auto_clean_time), Collections.singletonList(new Pair<>(getString(R.string.settings_auto_clean_hint), StringUtil.toString(SPService.getInt(SPService.KEY_AUTO_CLEAR_FILES_DAYS, 3))))); } }); mResolutionSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { List<Pair<String, String>> data = new ArrayList<>(2); data.add(new Pair<>(getString(R.string.settings__screenshot_resolution), "" + SPService.getInt(SPService.KEY_SCREENSHOT_RESOLUTION, 720))); showMultipleEditDialog(SettingsActivity.this, new OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() != 2) { LogUtil.e("SettingActivity", "获取编辑项少于两项"); return; } // 更新截图分辨率信息 SPService.putInt(SPService.KEY_SCREENSHOT_RESOLUTION, Integer.parseInt(data.get(0))); mResolutionSettingInfo.setText(data.get(0) + "P"); } }, getString(R.string.settings__screenshot_setting), data); } }); mHightlightSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setMessage(R.string.settings__highlight_node) .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_HIGHLIGHT_REPLAY_NODE, true); mHightlightSettingInfo.setText(R.string.constant__yes); dialog.dismiss(); } }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_HIGHLIGHT_REPLAY_NODE, false); mHightlightSettingInfo.setText(R.string.constant__no); dialog.dismiss(); } }).show(); } }); // check update mCheckUpdateSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme) .setMessage(R.string.settings__check_update) .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_CHECK_UPDATE, true); mCheckUpdateSettingInfo.setText(R.string.constant__yes); dialog.dismiss(); } }).setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SPService.putBoolean(SPService.KEY_CHECK_UPDATE, false); mCheckUpdateSettingInfo.setText(R.string.constant__no); dialog.dismiss(); } }).show(); } }); // adb调试地址 mAdbServerSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showMultipleEditDialog(SettingsActivity.this, new DialogUtils.OnDialogResultListener() { @Override public void onDialogPositive(List<String> data) { if (data.size() == 1) { String server = data.get(0); SPService.putString(SPService.KEY_ADB_SERVER, server); mAdbServerSettingInfo.setText(server); } } }, getString(R.string.settings__adb_server), Collections.singletonList(new Pair<>(getString(R.string.settings__adb_server), SPService.getString(SPService.KEY_ADB_SERVER, "localhost:5555")))); } }); mHideLogSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this, R.style.SimpleDialogTheme).setMessage(R.string.settings__whether_hide_node_info) .setPositiveButton(R.string.constant__yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mHideLogSettingInfo.setText(R.string.constant__yes); SPService.putBoolean(SPService.KEY_HIDE_LOG, true); } }) .setNegativeButton(R.string.constant__no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); mHideLogSettingInfo.setText(R.string.constant__no); SPService.putBoolean(SPService.KEY_HIDE_LOG, false); } }) .setCancelable(true).show(); } }); mImportCaseSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showProgressDialog(getString(R.string.settings__load_extrenal_case)); BackgroundExecutor.execute(new Runnable() { @Override public void run() { File importDir = FileUtils.getSubDir("import"); File[] subFiles = importDir.listFiles(); int count = 0; if (subFiles != null) { for (File sub : subFiles) { // 格式校验 if (sub.isFile() && StringUtil.contains(sub.getName(), ".json")) { try { BufferedReader reader = new BufferedReader(new FileReader(sub)); StringBuilder sb = new StringBuilder(); char[] chars = new char[1024]; int readCount; while ((readCount = reader.read(chars, 0, 1024)) > 0) { sb.append(chars, 0, readCount); } reader.close(); // 加载实例 RecordCaseInfo caseInfo = JSON.parseObject(sb.toString(), RecordCaseInfo.class); String operationLog = caseInfo.getOperationLog(); GeneralOperationLogBean log = JSON.parseObject(operationLog, GeneralOperationLogBean.class); OperationStepUtil.beforeStore(log); caseInfo.setOperationLog(JSON.toJSONString(log)); GreenDaoManager.getInstance().getRecordCaseInfoDao().insert(caseInfo); // 导入完毕后删除 sub.delete(); count++; } catch (FileNotFoundException e) { LogUtil.e(TAG, "Catch java.io.FileNotFoundException: " + e.getMessage(), e); } catch (IOException e) { LogUtil.e(TAG, "Catch java.io.IOException: " + e.getMessage(), e); } catch (JSONException e) { LogUtil.e(TAG, e, "无法解析文件【%s】", StringUtil.hide(sub.getAbsoluteFile())); } catch (Exception e) { LogUtil.e(TAG, "Catch Exception: " + e.getMessage(), e); } } } } dismissProgressDialog(); toastLong(getString(R.string.settings__load_count_case, count)); } }); } }); mImportPluginSettingWrapper.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showProgressDialog(getString(R.string.settings__load_plugin)); BackgroundExecutor.execute(new Runnable() { @Override public void run() { File f = FileUtils.getSubDir("patch"); if (f.exists() && f.isDirectory()) { File[] subFiles = f.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".zip"); } }); if (subFiles != null && subFiles.length > 0) { for (File patch : subFiles) { try { PatchLoadResult result = PatchProcessUtil.dynamicLoadPatch(patch); if (result != null) { ClassUtil.installPatch(result); toastShort(getString(R.string.settings__load_success, result.name)); patch.delete(); } else { LogUtil.e("Settings", "插件安装失败"); toastShort(getString(R.string.settings__load_failed)); } } catch (Throwable e) { LogUtil.e("Settings", "加载插件异常", e); toastShort(getString(R.string.settings__load_failed)); } } } } // 隐藏进度 dismissProgressDialog(); } }); } }); } private void initView() { mPanel = (HeadControlPanel) findViewById(R.id.head_layout); mPanel.setMiddleTitle(getString(R.string.activity__setting)); mRecordScreenUploadWrapper = findViewById(R.id.recordscreen_upload_setting_wrapper); mRecordScreenUploadInfo = (TextView) findViewById(R.id.recordscreen_upload_setting_info); String path = SPService.getString(SPService.KEY_RECORD_SCREEN_UPLOAD); if (StringUtil.isEmpty(path)) { mRecordScreenUploadInfo.setText(R.string.settings__unset); } else { mRecordScreenUploadInfo.setText(path); } mRecordUploadWrapper = findViewById(R.id.performance_upload_setting_wrapper); mRecordUploadInfo = (TextView) findViewById(R.id.performance_upload_setting_info); path = SPService.getString(SPService.KEY_PERFORMANCE_UPLOAD); if (StringUtil.isEmpty(path)) { mRecordUploadInfo.setText(R.string.settings__unset); } else { mRecordUploadInfo.setText(path); } mServerSettingWrapper = findViewById(R.id.server_setting_wrapper); mServerSettingInfo = (TextView) findViewById(R.id.server_setting_info); path = SPService.getString(SPService.KEY_SERVER_SETTING); if (StringUtil.isEmpty(path)) { mServerSettingInfo.setText(R.string.settings__unset); } else { mServerSettingInfo.setText(path); } mPatchListWrapper = findViewById(R.id.patch_list_setting_wrapper); mPatchListInfo = (TextView) findViewById(R.id.patch_list_setting_info); path = SPService.getString(SPService.KEY_PATCH_URL, "https://raw.githubusercontent.com/alipay/SoloPi/master/<abi>.json"); if (StringUtil.isEmpty(path)) { mPatchListInfo.setText(R.string.settings__unset); } else { mPatchListInfo.setText(path); } mGlobalParamSettingWrapper = findViewById(R.id.global_param_setting_wrapper); mDefaultRotationSettingWrapper = findViewById(R.id.default_screen_rotation_setting_wrapper); mDefaultRotationSettingInfo = _findViewById(R.id.default_screen_rotation_setting_info); int defaultRotation = SPService.getInt(SPService.KEY_SCREEN_FACTOR_ROTATION, 0); String[] arrays = getResources().getStringArray(R.array.default_screen_rotation); mDefaultRotationSettingInfo.setText(arrays[defaultRotation]); mChangeRotationSettingWrapper = findViewById(R.id.change_rotation_setting_wrapper); mChangeRotationSettingInfo = _findViewById(R.id.change_rotation_setting_info); boolean changeRotation = SPService.getBoolean(SPService.KEY_SCREEN_ROTATION, false); mChangeRotationSettingInfo.setText(changeRotation ? R.string.constant__yes : R.string.constant__no); mOutputCharsetSettingWrapper = findViewById(R.id.output_charset_setting_wrapper); mOutputCharsetSettingInfo = (TextView) findViewById(R.id.output_charset_setting_info); mOutputCharsetSettingInfo.setText(SPService.getString(SPService.KEY_OUTPUT_CHARSET, "GBK")); mResolutionSettingWrapper = findViewById(R.id.screenshot_resolution_setting_wrapper); mResolutionSettingInfo = (TextView) findViewById(R.id.screenshot_resolution_setting_info); mResolutionSettingInfo.setText(SPService.getInt(SPService.KEY_SCREENSHOT_RESOLUTION, 720) + "P"); mHightlightSettingWrapper = findViewById(R.id.replay_highlight_setting_wrapper); mHightlightSettingInfo = (TextView) findViewById(R.id.replay_highlight_setting_info); mHightlightSettingInfo.setText(SPService.getBoolean(SPService.KEY_HIGHLIGHT_REPLAY_NODE, true) ? R.string.constant__yes : R.string.constant__no); mLanguageSettingWrapper = findViewById(R.id.language_setting_wrapper); mLanguageSettingInfo = (TextView) findViewById(R.id.language_setting_info); int pos = SPService.getInt(SPService.KEY_USE_LANGUAGE, 0); String[] availableLanguages = getResources().getStringArray(R.array.language); if (availableLanguages != null && availableLanguages.length > pos) { mLanguageSettingInfo.setText(availableLanguages[pos]); } else { mLanguageSettingInfo.setText(availableLanguages[0]); } mDisplaySystemAppSettingWrapper = findViewById(R.id.display_system_app_setting_wrapper); mDisplaySystemAppSettingInfo = (TextView) findViewById(R.id.display_system_app_setting_info); boolean displaySystemApp = SPService.getBoolean(SPService.KEY_DISPLAY_SYSTEM_APP, false); if (displaySystemApp) { mDisplaySystemAppSettingInfo.setText(R.string.constant__yes); } else { mDisplaySystemAppSettingInfo.setText(R.string.constant__no); } mAutoReplaySettingWrapper = findViewById(R.id.auto_replay_setting_wrapper); mAutoReplaySettingInfo = (TextView) findViewById(R.id.auto_replay_setting_info); boolean autoReplay = SPService.getBoolean(SPService.KEY_REPLAY_AUTO_START, false); if (autoReplay) { mAutoReplaySettingInfo.setText(R.string.constant__yes); } else { mAutoReplaySettingInfo.setText(R.string.constant__no); } mReplayOtherAppSettingWrapper = findViewById(R.id.replay_other_app_setting_wrapper); mReplayOtherAppInfo = _findViewById(R.id.replay_other_app_setting_info); boolean replayOtherApp = SPService.getBoolean(SPService.KEY_ALLOW_REPLAY_DIFFERENT_APP, false); mReplayOtherAppInfo.setText(replayOtherApp ? R.string.constant__yes : R.string.constant__no); mRestartAppSettingWrapper = findViewById(R.id.restart_app_setting_wrapper); mRestartAppInfo = _findViewById(R.id.restart_app_setting_info); boolean restartApp = SPService.getBoolean(SPService.KEY_RESTART_APP_ON_PLAY, true); mRestartAppInfo.setText(restartApp ? R.string.constant__yes : R.string.constant__no); mAdbServerSettingWrapper = findViewById(R.id.adb_server_setting_wrapper); mAdbServerSettingInfo = _findViewById(R.id.adb_server_setting_info); mAdbServerSettingInfo.setText(SPService.getString(SPService.KEY_ADB_SERVER, "localhost:5555")); mSkipAccessibilitySettingWrapper = findViewById(R.id.skip_accessibility_setting_wrapper); mSkipAccessibilitySettingInfo = (TextView) findViewById(R.id.skip_accessibility_setting_info); boolean skipAccessibility = SPService.getBoolean(SPService.KEY_SKIP_ACCESSIBILITY, true); if (skipAccessibility) { mSkipAccessibilitySettingInfo.setText(R.string.constant__yes); } else { mSkipAccessibilitySettingInfo.setText(R.string.constant__no); } mMaxWaitSettingWrapper = findViewById(R.id.max_wait_setting_wrapper); mMaxWaitSettingInfo = (TextView) findViewById(R.id.max_wait_setting_info); long maxWaitTime = SPService.getLong(SPService.KEY_MAX_WAIT_TIME, 10000L); mMaxWaitSettingInfo.setText(maxWaitTime + "ms"); mCheckUpdateSettingWrapper = findViewById(R.id.check_update_setting_wrapper); mCheckUpdateSettingInfo = (TextView) findViewById(R.id.check_update_setting_info); boolean checkUpdate = SPService.getBoolean(SPService.KEY_CHECK_UPDATE, true); if (checkUpdate) { mCheckUpdateSettingInfo.setText(R.string.constant__yes); } else { mCheckUpdateSettingInfo.setText(R.string.constant__no); } mBaseDirSettingWrapper = findViewById(R.id.base_dir_setting_wrapper); mBaseDirSettingInfo = (TextView) findViewById(R.id.base_dir_setting_info); mBaseDirSettingInfo.setText(FileUtils.getSolopiDir().getPath()); mAesSeedSettingWrapper = findViewById(R.id.aes_seed_setting_wrapper); mAesSeedSettingInfo = (TextView) findViewById(R.id.aes_seed_setting_info); mAesSeedSettingInfo.setText(SPService.getString(SPService.KEY_AES_KEY, AESUtils.DEFAULT_AES_KEY)); mClearFilesSettingWrapper = findViewById(R.id.clear_files_setting_wrapper); mClearFilesSettingInfo = (TextView) findViewById(R.id.clear_files_setting_info); mHideLogSettingWrapper = findViewById(R.id.hide_log_setting_wrapper); mHideLogSettingInfo = (TextView) findViewById(R.id.hide_log_setting_info); boolean hideLog = SPService.getBoolean(SPService.KEY_HIDE_LOG, true); if (hideLog) { mHideLogSettingInfo.setText(R.string.constant__yes); } else { mHideLogSettingInfo.setText(R.string.constant__no); } mImportCaseSettingWrapper = findViewById(R.id.import_case_setting_wrapper); // 设置下引入地址 TextView importPath = (TextView) findViewById(R.id.import_case_setting_path); importPath.setText(FileUtils.getSubDir("import").getAbsolutePath()); mImportPluginSettingWrapper = findViewById(R.id.import_patch_setting_wrapper); // 设置下引入地址 TextView importPluginPath = (TextView) findViewById(R.id.import_patch_setting_path); importPluginPath.setText(FileUtils.getSubDir("patch").getAbsolutePath()); findViewById(R.id.plugin_list_setting_wrapper).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(SettingsActivity.this, PatchStatusActivity.class)); } }); int clearDays = SPService.getInt(SPService.KEY_AUTO_CLEAR_FILES_DAYS, 3); mClearFilesSettingInfo.setText(StringUtil.toString(clearDays)); mAboutBtn = findViewById(R.id.about_wrapper); } /** * 展示全局变量配置窗口 */ private void showGlobalParamEdit() { final List<Pair<String, String>> paramList = new ArrayList<>(); String globalParam = SPService.getString(SPService.KEY_GLOBAL_SETTINGS); JSONObject params = JSON.parseObject(globalParam); if (params != null && params.size() > 0) { for (String key : params.keySet()) { paramList.add(new Pair<>(key, params.getString(key))); } } final LayoutInflater inflater = LayoutInflater.from(ContextUtil.getContextThemeWrapper( SettingsActivity.this, R.style.AppDialogTheme)); final View view = inflater.inflate(R.layout.dialog_global_param_setting, null); final TagFlowLayout tagFlowLayout = (TagFlowLayout) view.findViewById(R.id.global_param_group); final EditText paramName = (EditText) view.findViewById(R.id.global_param_name); final EditText paramValue = (EditText) view.findViewById(R.id.global_param_value); View paramAdd = view.findViewById(R.id.global_param_add); tagFlowLayout.setAdapter(new TagAdapter<Pair<String, String>>(paramList) { @Override public View getView(FlowLayout parent, int position, Pair<String, String> o) { View root = inflater.inflate(R.layout.item_param_info, parent, false); TextView title = (TextView) root.findViewById(R.id.batch_execute_tag_name); title.setText(getString(R.string.settings__global_param_key_value, o.first, o.second)); return root; } }); tagFlowLayout.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() { @Override public boolean onTagClick(View view, int position, FlowLayout parent) { paramList.remove(position); tagFlowLayout.getAdapter().notifyDataChanged(); return true; } }); paramAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String key = paramName.getText().toString().trim(); String value = paramValue.getText().toString().trim(); if (StringUtil.isEmpty(key) || key.contains("=")) { toastShort(getString(R.string.setting__invalid_param_name)); } // 清空输入框 paramName.setText(""); paramValue.setText(""); int replacePosition = -1; for (int i = 0; i < paramList.size(); i++) { if (key.equals(paramList.get(i).first)) { replacePosition = i; break; } } // 如果有相同的,就进行替换 if (replacePosition > -1) { paramList.set(replacePosition, new Pair<>(key, value)); } else { paramList.add(new Pair<>(key, value)); } tagFlowLayout.getAdapter().notifyDataChanged(); } }); new AlertDialog.Builder(SettingsActivity.this, R.style.AppDialogTheme) .setView(view) .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { JSONObject newGlobalParam = new JSONObject(paramList.size() + 1); for (Pair<String, String> param : paramList) { newGlobalParam.put(param.first, param.second); } SPService.putString(SPService.KEY_GLOBAL_SETTINGS, newGlobalParam.toJSONString()); dialog.dismiss(); } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).setCancelable(true) .show(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_FILE_CHOOSE) { if (resultCode == RESULT_OK) { String targetFile = data.getStringExtra(FileChooseDialogActivity.KEY_TARGET_FILE); if (!StringUtil.isEmpty(targetFile)) { SPService.putString(SPService.KEY_BASE_DIR, targetFile); mBaseDirSettingInfo.setText(targetFile); FileUtils.setSolopiBaseDir(targetFile); } } } else { super.onActivityResult(requestCode, resultCode, data); } } /** * 更新存储的用例 * * @param oldSeed * @param newSeed */ private void updateStoredRecords(final String oldSeed, final String newSeed) { showProgressDialog(getString(R.string.settings__start_update_cases)); BackgroundExecutor.execute(new Runnable() { @Override public void run() { try { final List<RecordCaseInfo> cases = GreenDaoManager.getInstance().getRecordCaseInfoDao().queryBuilder() .orderDesc(RecordCaseInfoDao.Properties.GmtCreate) .build().list(); if (cases != null && cases.size() > 0) { for (int i = 0; i < cases.size(); i++) { showProgressDialog(getString(R.string.settings__updating_cases, i + 1, cases.size())); RecordCaseInfo caseInfo = cases.get(i); GeneralOperationLogBean generalOperation; try { generalOperation = JSON.parseObject(caseInfo.getOperationLog(), GeneralOperationLogBean.class); } catch (Exception e) { LogUtil.e(TAG, "parseOperation failed: " + e.getMessage(), e); continue; } // 如果没拿到数据 if (generalOperation == null) { continue; } // load file content OperationStepUtil.afterLoad(generalOperation); List<OperationStep> steps = generalOperation.getSteps(); if (generalOperation.getSteps() != null) { for (OperationStep step : steps) { OperationMethod method = step.getOperationMethod(); if (method.isEncrypt()) { Map<String, String> params = method.getOperationParam(); for (String key : params.keySet()) { // 逐个参数替换 try { String originValue = AESUtils.decrypt(params.get(key), oldSeed); params.put(key, AESUtils.encrypt(originValue, newSeed)); } catch (Exception e) { LogUtil.e(TAG, "process key=" + key + " failed, " + e.getMessage(), e); // 不阻碍其他操作执行 } } } } } OperationStepUtil.beforeStore(generalOperation); // 更新operationLog字段 caseInfo.setOperationLog(JSON.toJSONString(generalOperation)); GreenDaoManager.getInstance().getRecordCaseInfoDao().update(caseInfo); } } } catch (Throwable t) { LogUtil.e(TAG, "Update aes seed throw " + t.getMessage(), t); } finally { // 隐藏进度窗口 dismissProgressDialog(); } } }); } }
2.启动APP时创建websocket链接:
核心代码:
//链接服务器 PermissionUtil.requestPermissions(Arrays.asList("adb"), IndexActivity.this, new PermissionUtil.OnPermissionCallback() { @Override public void onPermissionResult(boolean result, String reason) { if (result){ RealTimeManage.openConnect(); }else { LauncherApplication.getInstance().showToast("权限申请未通过,不支持链接"); } } });
该回调方法主要是判断设备是否开启adb权限(有该权限才能拿到设备信息),有权限则链接服务器,无权限则toast提示。
详细代码如下:
/* * Copyright (C) 2015-present, Ant Financial Services Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.hulu.activity; import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.core.content.FileProvider; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.WebSettings; import android.webkit.WebView; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alipay.hulu.R; import com.alipay.hulu.activity.entry.EntryActivity; import com.alipay.hulu.bean.GithubReleaseBean; import com.alipay.hulu.common.application.LauncherApplication; import com.alipay.hulu.common.bean.DeviceInfo; import com.alipay.hulu.common.constant.Constant; import com.alipay.hulu.common.service.SPService; import com.alipay.hulu.common.tools.BackgroundExecutor; import com.alipay.hulu.common.tools.CmdTools; import com.alipay.hulu.common.utils.ClassUtil; import com.alipay.hulu.common.utils.ContextUtil; import com.alipay.hulu.common.utils.DeviceInfoUtil; import com.alipay.hulu.common.utils.FileUtils; import com.alipay.hulu.common.utils.LogUtil; import com.alipay.hulu.common.utils.PermissionUtil; import com.alipay.hulu.common.utils.StringUtil; import com.alipay.hulu.event.ScanSuccessEvent; import com.alipay.hulu.runManager.RealTimeManage; import com.alipay.hulu.ui.ColorFilterRelativeLayout; import com.alipay.hulu.ui.HeadControlPanel; import com.alipay.hulu.upgrade.PatchRequest; import com.alipay.hulu.util.SystemUtil; import com.alipay.hulu.util.UpgradeUtil; import com.alipay.hulu.util.ZipUtil; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; /** * Created by lezhou.wyl on 2018/1/28. */ public class IndexActivity extends BaseActivity { private static final String TAG = IndexActivity.class.getSimpleName(); private HeadControlPanel mPanel; private GridView mGridView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_index); initView(); initData(); loadOthers(); //链接服务器 PermissionUtil.requestPermissions(Arrays.asList("adb"), IndexActivity.this, new PermissionUtil.OnPermissionCallback() { @Override public void onPermissionResult(boolean result, String reason) { if (result){ RealTimeManage.openConnect(); }else { LauncherApplication.getInstance().showToast("权限申请未通过,不支持链接"); } } }); // check update if (SPService.getBoolean(SPService.KEY_CHECK_UPDATE, true)) { BackgroundExecutor.execute(new Runnable() { @Override public void run() { UpgradeUtil.checkForUpdate(new UpgradeUtil.CheckUpdateListener() { @Override public void onNoUpdate() { } @Override public void onNewUpdate(final GithubReleaseBean release) { Parser parser = Parser.builder().build(); Node document = parser.parse(release.getBody()); // text size 16dp int px = ContextUtil.dip2px(IndexActivity.this, 16); HtmlRenderer renderer = HtmlRenderer.builder().build(); String css = "<html><header><style type=\"text/css\"> img {" + "width:100%;" + "height:auto;" + "}" + "body {" + "margin-right:30px;" + "margin-left:30px;" + "margin-top:30px;" + "font-size:" + px + "px;" + "word-wrap:break-word;" + "}" + "</style></header>"; final String content = css + renderer.render(document) + "</html>"; runOnUiThread(new Runnable() { @Override public void run() { WebView webView = new WebView(IndexActivity.this); WebSettings webSettings = webView.getSettings(); webSettings.setUseWideViewPort(true); webSettings.setLoadWithOverviewMode(true); webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS); webView.loadData(content, null, null); new AlertDialog.Builder(IndexActivity.this).setTitle(getString(R.string.index__new_version, release.getTag_name())) .setView(webView) .setPositiveButton(R.string.index__go_update, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Uri uri = Uri.parse(release.getHtml_url()); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).show(); } }); } @Override public void onUpdateFailed(Throwable t) { } }); } }); } } /** * 初始化界面 */ private void initView() { mPanel = (HeadControlPanel) findViewById(R.id.head_layout); mPanel.setMiddleTitle(getString(R.string.app_name)); mPanel.setInfoIconClickListener(R.drawable.icon_config, new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(IndexActivity.this, SettingsActivity.class)); } }); mGridView = (GridView) findViewById(R.id.tools_grid); } /** * 加载内容 */ private void initData() { Map<String, Entry> entryList = new HashMap<>(); List<Class<? extends Activity>> activities = ClassUtil.findSubClass(Activity.class, EntryActivity.class); // 配置唯一entry for (Class<? extends Activity> activityClass: activities) { // 配置 Entry target = new Entry(activityClass.getAnnotation(EntryActivity.class), activityClass); if (entryList.containsKey(target.name)) { if (entryList.get(target.name).level < target.level) { entryList.put(target.name, target); } } else { entryList.put(target.name, target); } } List<Entry> entries = new ArrayList<>(entryList.values()); // 从大到小排 Collections.sort(entries, new Comparator<Entry>() { @Override public int compare(Entry o1, Entry o2) { return o1.index - o2.index; } }); mPanel.setLeftIconClickListener(R.drawable.icon_scan, new View.OnClickListener() { @Override public void onClick(View v) { PermissionUtil.requestPermissions(Collections.singletonList(Manifest.permission.CAMERA), IndexActivity.this, new PermissionUtil.OnPermissionCallback() { @Override public void onPermissionResult(boolean result, String reason) { Intent intent = new Intent(IndexActivity.this, QRScanActivity.class); intent.putExtra(QRScanActivity.KEY_SCAN_TYPE, ScanSuccessEvent.SCAN_TYPE_OTHER); startActivity(intent); } }); } }); CustomAdapter adapter = new CustomAdapter(this, entries); if (entries.size() <= 3) { mGridView.setNumColumns(1); } else { mGridView.setNumColumns(2); } mGridView.setAdapter(adapter); // 有写权限,申请下 PatchRequest.updatePatchList(null); } /** * 加载其他信息 */ private void loadOthers() { BackgroundExecutor.execute(new Runnable() { @Override public void run() { // 检查是否需要上报故障日志 checkErrorLog(); // 读取外部的ADB秘钥 readOuterAdbKey(); } }); } /** * 检查是否有需要上报的Crash日志 */ private void checkErrorLog() { final Pattern pattern = Pattern.compile("\\d+\\.log"); long lastCheckTime = SPService.getLong(SPService.KEY_ERROR_CHECK_TIME, System.currentTimeMillis()); File errorDir = FileUtils.getSubDir("error"); if (errorDir.exists() && errorDir.isDirectory()) { File[] children = errorDir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.isFile() && pattern.matcher(pathname.getName()).matches(); } }); if (children != null && children.length > 0) { for (final File errorLog: children) { final long time = errorLog.lastModified(); if (time > lastCheckTime) { // 只上传一条,根据修改时间查看 LauncherApplication.getInstance().showDialog( IndexActivity.this, getString(R.string.index__find_error_log), getString(R.string.constant__sure), new Runnable() { @Override public void run() { reportError(time, errorLog); } }, getString(R.string.constant__cancel), null); break; } } } } SPService.putLong(SPService.KEY_ERROR_CHECK_TIME, System.currentTimeMillis()); } /** * 上报Crash日志 * @param errorTime * @param errorLog */ private void reportError(final long errorTime, final File errorLog) { BackgroundExecutor.execute(new Runnable() { @Override public void run() { File logsFolder = new File(getExternalCacheDir(), "logs"); Date date = new Date(errorTime); final String targetDay = String.format(Locale.CHINA, "%d%d%d", date.getYear() + 1900, date.getMonth() + 1, date.getDate()); final List<File> reportLogs = new ArrayList<>(); reportLogs.add(errorLog); if (logsFolder.exists() && logsFolder.isDirectory()) { File[] childrenFiles = logsFolder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith(targetDay) && name.endsWith(".log"); } }); if (childrenFiles != null && childrenFiles.length > 0) { Arrays.sort(childrenFiles, new Comparator<File>() { @Override public int compare(File o1, File o2) { return (int) (o2.lastModified() - o1.lastModified()); } }); // 从最新的向前翻,直到大于2MB long currentSize = 0; for (File child : childrenFiles) { currentSize += child.length(); if (currentSize > 2 * 1024 * 1024) { break; } reportLogs.add(child); } } } final File zipFile = ZipUtil.zip(reportLogs, new File(FileUtils.getSubDir("share"), "report.zip")); if (zipFile != null && zipFile.exists()) { // 发送邮件 runOnUiThread(new Runnable() { @Override public void run() { Intent i = new Intent(Intent.ACTION_SEND); i.setType("application/octet-stream"); i.putExtra(Intent.EXTRA_EMAIL, new String[] { Constant.MAIL_ADDERSS }); i.putExtra(Intent.EXTRA_SUBJECT, StringUtil.getString(R.string.index__report_error_log)); i.putExtra(Intent.EXTRA_TEXT, getString(R.string.index__error_occur_time, errorTime)); Uri uri = FileProvider.getUriForFile(IndexActivity.this, "com.alipay.hulu.myProvider", zipFile); i.putExtra(Intent.EXTRA_STREAM, uri); startActivity(Intent.createChooser(i, getString(R.string.index__select_mail_app))); } }); } else { toastLong(getString(R.string.index__package_crash_failed)); // 回设检查时间,以便下次上报 SPService.putLong(SPService.KEY_ERROR_CHECK_TIME, errorTime - 10); } } }); } /** * 读取外部ADB配置文件 */ private void readOuterAdbKey() { File root = FileUtils.getSubDir("adb"); final File adbKey = new File(root, "adbkey"); final File pubKey = new File(root, "adbkey.pub"); if (!adbKey.exists() || !pubKey.exists()) { return; } boolean result = CmdTools.readOuterAdbKey(adbKey, pubKey); if (!result) { toastShort("拷贝ADB Key失败"); } else { adbKey.delete(); pubKey.delete(); } } public static class Entry { private int iconId; private String name; private String[] permissions; private int level; private int index; private int cornerColor; private String cornerText; private float saturation; private int cornerPersist; private Class<? extends Activity> targetActivity; public Entry(EntryActivity activity, Class<? extends Activity> target) { if (activity.icon() != -1) { this.iconId = activity.icon(); } else if (!StringUtil.isEmpty(activity.iconName())) { // 反射获取id String name = activity.iconName(); int lastDotPos = name.lastIndexOf('.'); String clazz = name.substring(0, lastDotPos); String field = name.substring(lastDotPos + 1); try { Class RClass = ClassUtil.getClassByName(clazz); Field icon = RClass.getDeclaredField(field); this.iconId = icon.getInt(null); } catch (Exception e) { LogUtil.e(TAG, "Fail to load icon result with id:" + name); this.iconId = R.drawable.solopi_main; } } else { this.iconId = R.drawable.solopi_main; } String name = activity.name(); if (activity.nameRes() != 0) { name = StringUtil.getString(activity.nameRes()); } else if (StringUtil.isNotEmpty(activity.nameResName())) { int nameRes = 0; String nameResName = activity.nameResName(); int lastDotPos = nameResName.lastIndexOf('.'); String clazz = nameResName.substring(0, lastDotPos); String field = nameResName.substring(lastDotPos + 1); try { Class<?> RClass = ClassUtil.getClassByName(clazz); Field nameResF = RClass.getDeclaredField(field); nameRes = nameResF.getInt(null); } catch (Exception e) { LogUtil.e(TAG, "Fail to load name result with id:" + nameResName); nameRes = R.string.app_name; } name = StringUtil.getString(nameRes); } this.name = name; permissions = activity.permissions(); level = activity.level(); targetActivity = target; index = activity.index(); cornerText = activity.cornerText(); cornerColor = activity.cornerBg(); cornerPersist = activity.cornerPersist(); saturation = activity.saturation(); } } public class CustomAdapter extends BaseAdapter { final Context context; final List<Entry> data; JSONObject entryCount; JSONObject versionsCount; int currentVersionCode; public CustomAdapter(Context context, List<Entry> data) { this.context = context; this.data = data; // 默认取空值 String appInfo = SPService.getString(SPService.KEY_INDEX_RECORD, null); currentVersionCode = SystemUtil.getAppVersionCode(); if (appInfo == null) { versionsCount = new JSONObject(); entryCount = new JSONObject(); } else { versionsCount = JSON.parseObject(appInfo); // 当前版本的信息 entryCount = versionsCount.getJSONObject(Integer.toString(currentVersionCode)); // 如果没有当前版本信息 if (entryCount == null) { entryCount = new JSONObject(); } } } @Override public int getCount() { if (data != null) { return data.size(); } else { return 0; } } @Override public Object getItem(int position) { if (data != null) { return data.get(position); } else { return null; } } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.item_tools_grid, parent, false); viewHolder = new ViewHolder(); convertView.setTag(viewHolder); viewHolder.icon = (ImageView) convertView.findViewById(R.id.img); viewHolder.name = (TextView) convertView.findViewById(R.id.tv); viewHolder.corner = (TextView) convertView.findViewById(R.id.index_corner); viewHolder.background = (ColorFilterRelativeLayout) convertView; } else { viewHolder = (ViewHolder) convertView.getTag(); } final Entry item = data.get(position); viewHolder.icon.setImageResource(item.iconId); viewHolder.name.setText(item.name); Integer itemCount = entryCount.getInteger(item.name); if (itemCount == null) { itemCount = 0; } // 持续显示或者,有进入次数计数 if (item.cornerPersist == 0 || (item.cornerPersist > 0 && itemCount < item.cornerPersist)) { // 如果有角标配置,设置角标 if (!StringUtil.isEmpty(item.cornerText)) { viewHolder.corner.setText(item.cornerText); viewHolder.corner.setBackgroundColor(item.cornerColor); viewHolder.corner.setVisibility(View.VISIBLE); } else { viewHolder.corner.setVisibility(View.GONE); } } else { viewHolder.corner.setVisibility(View.GONE); } if (item.saturation != 1F) { viewHolder.background.setSaturation(item.saturation); } else { viewHolder.background.setSaturation(1); } convertView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final long startTime = System.currentTimeMillis(); PermissionUtil.requestPermissions(Arrays.asList(item.permissions), IndexActivity.this, new PermissionUtil.OnPermissionCallback() { @Override public void onPermissionResult(boolean result, String reason) { LogUtil.d(TAG, "权限申请耗时:%dms", System.currentTimeMillis() - startTime); if (result) { // 记录下进入次数 Integer count = entryCount.getInteger(item.name); if (count == null) { count = 1; } else { count ++; } entryCount.put(item.name, count); versionsCount.put(Integer.toString(currentVersionCode), entryCount); SPService.putString(SPService.KEY_INDEX_RECORD, JSON.toJSONString(versionsCount)); Intent intent = new Intent(IndexActivity.this, item.targetActivity); startActivity(intent); } } }); } }); return convertView; } public List<Entry> getData() { return data; } public class ViewHolder { ColorFilterRelativeLayout background; ImageView icon; TextView name; TextView corner; } } }
2.websocket通信相关,断开链接时自动重连(solopi链接服务端的时机)
核心代码:
@Override public void onOpen(ServerHandshake handshakedata) { LogUtil.i(tag,"连接开启"); //LauncherApplication.getInstance().showToast("连接服务器成功!"); //重连成功,像服务端发送设备信息。因为需要实时更新服务端设备状态 DeviceInfo deviceInfo = DeviceInfoUtil.generateDeviceInfo(); HashMap<String, Object> deviceInfoHashMap = new HashMap<>(); deviceInfoHashMap.put("deviceInfo",(Object)deviceInfo); RealTimeManage.sendMessage(deviceInfoHashMap); } @Override public void onMessage(String message) { LogUtil.i(tag,"接收消息"); } @Override public void onClose(int code, String reason, boolean remote) { LogUtil.i(tag,"连接断开"); if (code != RealTimeManage.FORCE_CLOSE){ RealTimeManage.openConnect();//断线重连 } } @Override public void onError(Exception ex) { LogUtil.i(tag,"连接出错"); RealTimeManage.openConnect();//断线重连 }
onOpen方法,是在链接成功后发送设备信息到服务端
onClose和onError方法,主要是在链接断开和出错时,进行重连。
/** * 创建链接(打开app以及修改服务端地址的时候调用) */ public static void openConnect() { if (client != null && client.getReadyState().equals(ReadyState.OPEN)) { client.close(FORCE_CLOSE);//已存在链接时,先关闭。比如修改服务端链接时,客户端可能与之前设置的服务端地址链接还是open状态 } String wsurl = SPService.getString(SPService.KEY_SERVER_SETTING); //判断服务端地址是否为空,空则返回 if (StringUtil.isEmpty(wsurl)) { LauncherApplication.getInstance().showToast("请先配置服务端地址!!!"); return; } //服务端地址不为空,创建链接 URI uri = URI.create(wsurl); try { int time = 0; client = new JwebSocketClient(uri) { @Override public void onMessage(String message) { receiveMessage(message); super.onMessage(message); } }; client.connect(); while ((!(client.getReadyState().equals(ReadyState.OPEN))) && time <= 4) {//轮询判断是否已创建链接 Thread.sleep(1000); time++; } LauncherApplication.getInstance().showToast("链接" + (client.getReadyState().equals(ReadyState.OPEN) ? "成功" : "失败")); } catch (Exception e) { e.printStackTrace(); } }
上述方法是创建链接,
首先判断当前链接是否为open状态,已经存在链接时,先关闭。
判断服务端地址是否为空,4S轮询等待链接成功创建。
详细代码如下:
package com.alipay.hulu.runManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alipay.hulu.activity.BatchExecutionActivity; import com.alipay.hulu.common.application.LauncherApplication; import com.alipay.hulu.common.bean.DeviceInfo; import com.alipay.hulu.common.service.SPService; import com.alipay.hulu.common.tools.CmdTools; import com.alipay.hulu.common.utils.LogUtil; import com.alipay.hulu.common.utils.StringUtil; import com.alipay.hulu.shared.io.bean.RecordCaseInfo; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; import org.java_websocket.WebSocket; import org.java_websocket.enums.ReadyState; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Set; public class RealTimeManage { private static String TAG = "RealTimeManage"; public static JwebSocketClient client; public static String taskId; public static final Integer FORCE_CLOSE=-99; /** * 上传设备信息 * * @param messageMap */ public static void sendMessage(HashMap<String, Object> messageMap) { if (client != null && client.getReadyState().equals(ReadyState.OPEN)) { //链接创建成功后发消息 client.send(JSON.toJSONString(messageMap)); LogUtil.i(TAG, "成功发送消息"); } } /** * 创建链接(打开app以及修改服务端地址的时候调用) */ public static void openConnect() { if (client!=null && client.getReadyState().equals(ReadyState.OPEN)){ client.close(FORCE_CLOSE);//已存在链接时,先关闭。比如修改服务端链接时,客户端可能与之前设置的服务端地址链接还是open状态 } String wsurl = SPService.getString(SPService.KEY_SERVER_SETTING); //判断服务端地址是否为空,空则返回 if (StringUtil.isEmpty(wsurl)) { LauncherApplication.getInstance().showToast("请先配置服务端地址!!!"); return; } //服务端地址不为空,创建链接 URI uri = URI.create(wsurl); try { int time = 0; client = new JwebSocketClient(uri) { @Override public void onMessage(String message) { receiveMessage(message); super.onMessage(message); } }; client.connect(); while (( !(client.getReadyState().equals(ReadyState.OPEN))) && time <= 4) {//轮询判断是否已创建链接 Thread.sleep(1000); time++; } LauncherApplication.getInstance().showToast("链接"+(client.getReadyState().equals(ReadyState.OPEN)?"成功":"失败")); } catch (Exception e) { e.printStackTrace(); } } /** * 接收消息 * * @param message */ private static void receiveMessage(String message) { LogUtil.i(TAG, "收到消息" + message); Gson gson = new Gson(); HashMap<String, Object> messageMap = gson.fromJson(message, new TypeToken<HashMap<String, Object>>() { }.getType()); Set<String> keySet = messageMap.keySet(); if (messageMap.containsKey("execCase")) {//如果收到的消息是用例执行命令,则去执行用例 //收到的caseInfo转换为 List<RecordCaseInfo> Object execCase = messageMap.get("execCase"); taskId = new JsonParser().parse(execCase.toString()).getAsJsonObject().get("taskId").toString(); String caseListjsonStr = new JsonParser().parse(execCase.toString()).getAsJsonObject().get("caseInfoList").toString(); List<RecordCaseInfo> caseList = gson.fromJson(caseListjsonStr, new TypeToken<List<RecordCaseInfo>>() { }.getType()); //用例caseStep到对应文件 for (int i = 0; i < caseList.size(); i++) { RecordCaseInfo recordCaseInfo = caseList.get(i); String caseStep = recordCaseInfo.getCaseStep(); String operationLog = recordCaseInfo.getOperationLog(); String storePath = JSON.parseObject(operationLog).get("storePath").toString(); File file = new File(storePath); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } try { BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(storePath)); bufferedWriter.write(caseStep); bufferedWriter.close(); } catch (IOException e) { e.printStackTrace(); } } CaseRunMangage.getInstance().run(caseList);//执行接收到的用例 } else if (messageMap.containsKey("execMonkey")) { CmdTools.generateConnection(); String result = CmdTools.execAdbCmd(messageMap.get("execMonkey").toString(), 0);//执行cmd命令 LogUtil.i(TAG, "adb运行结果" + result); } } }
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();//断线重连 } }
二:用例上传
核心代码:
//点击批量上传按钮 batchUpLoadBtn = (Button) findViewById(R.id.batch_upload_start_btn); batchUpLoadBtn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View view) { if (currentCases.size()==0){ toastShort("还未选择用例!!"); return ; } HashMap<String, Object> caseHashMap = new HashMap<>(); //遍历currentCases,将每一个case都赋值对应的caseStep BufferedReader reader=null; for (int i = 0; i < currentCases.size(); i++) { RecordCaseInfo caseInfo = currentCases.get(i); String path = caseInfo.getOperationLog(); String storePath = JSON.parseObject(path).get("storePath").toString(); StringBuffer caseStep = new StringBuffer(); try { reader = new BufferedReader(new FileReader(storePath)); String line; if ((line = reader.readLine()) !=null){ caseStep.append(line); } } catch (IOException e) { e.printStackTrace(); } String caseStepStr = caseStep.toString(); //用例上传前进行解密 List<OperationStep> operationSteps = JSON.parseArray(caseStepStr, OperationStep.class); for (OperationStep operationStep : operationSteps) { OperationMethod operationMethod = operationStep.getOperationMethod(); if (operationMethod.isEncrypt()){ Set<String> paramKeys = operationMethod.getParamKeys(); HashMap<String, String> tempParms = new HashMap<>(); for (String paramKey : paramKeys) { //解密 String param = operationMethod.getParam(paramKey); tempParms.put(paramKey,param); } //设置为不加密模式 operationMethod.setEncrypt(false); for (String key : tempParms.keySet()) { //在不加密的模式下,将解密后的数据写入到operationMethod operationMethod.putParam(key,tempParms.get(key)); } } } caseInfo.setCaseStep(JSON.toJSONString(operationSteps)); } try { if (reader!=null) reader.close(); } catch (IOException e) { e.printStackTrace(); } //将最终的currentCases放到map caseHashMap.put("caseInfo",currentCases); try { RealTimeManage.sendMessage(caseHashMap); toastShort(getString(R.string.batch__uploadsuccess_case)); } catch (Exception e) { toastShort(getString(R.string.batch__uploadfail_case)); e.printStackTrace(); } /*//ToDo:批量上传是否需要权限?? PermissionUtil.OnPermissionCallback callback = new PermissionUtil.OnPermissionCallback(){ @Override public void onPermissionResult(boolean result, String reason) { if (result){ HashMap<String, Object> caseHashMap = new HashMap<>(); caseHashMap.put("caseInfo",currentCases); RealTimeManage.sendMessage(caseHashMap); } } }; checkPermissions(callback);*/ } }); }
solopi端新增了用例上传的按钮,注册该按钮的点击事件,具体逻辑可以看下代码注释,这块写的比较详细。
其中比较重要的一步:用例上传前对operationMethod的ParamKey进行解密,后续服务端在测试报告渲染时需要用到该解密后的内容。
详细代码如下:
/* * Copyright (C) 2015-present, Ant Financial Services Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.hulu.activity; import android.os.Bundle; import android.provider.Settings; import androidx.annotation.Nullable; import com.alibaba.fastjson.JSON; import com.alipay.hulu.runManager.CaseRunMangage; import com.alipay.hulu.runManager.RealTimeManage; import com.alipay.hulu.shared.node.action.OperationMethod; import com.alipay.hulu.shared.node.tree.export.bean.OperationStep; import com.google.android.material.tabs.TabLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.TextView; import com.alipay.hulu.R; import com.alipay.hulu.adapter.BatchExecutionListAdapter; import com.alipay.hulu.common.tools.BackgroundExecutor; import com.alipay.hulu.common.utils.LogUtil; import com.alipay.hulu.common.utils.MiscUtil; import com.alipay.hulu.common.utils.PermissionUtil; import com.alipay.hulu.fragment.BatchExecutionFragment; import com.alipay.hulu.shared.io.bean.RecordCaseInfo; import com.alipay.hulu.shared.node.utils.AppUtil; import com.alipay.hulu.ui.HeadControlPanel; import com.alipay.hulu.util.CaseReplayUtil; import com.zhy.view.flowlayout.FlowLayout; import com.zhy.view.flowlayout.TagAdapter; import com.zhy.view.flowlayout.TagFlowLayout; import org.json.JSONObject; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.stream.Stream; /** * Created by lezhou.wyl on 2018/8/19. */ public class BatchExecutionActivity extends BaseActivity implements BatchExecutionListAdapter.Delegate , TagFlowLayout.OnTagClickListener{ private ViewPager mPager; private CheckBox mRestartApp; private TabLayout mTabLayout; private HeadControlPanel mHeadPanel; private TagFlowLayout tagGroup; private final List<RecordCaseInfo> currentCases = new ArrayList<>(); private TagAdapter<RecordCaseInfo> tagAdapter; private Button startExecutionBtn; private Button batchUpLoadBtn; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_batch_execution); mPager = (ViewPager) findViewById(R.id.pager); mTabLayout = (TabLayout) findViewById(R.id.tab_layout); mHeadPanel = (HeadControlPanel) findViewById(R.id.head_replay_list); mHeadPanel.setMiddleTitle(getString(R.string.activity__batch_replay)); mHeadPanel.setBackIconClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout)); mTabLayout.setupWithViewPager(mPager); mTabLayout.setTabGravity(TabLayout.GRAVITY_FILL); mTabLayout.setTabMode(TabLayout.MODE_FIXED); mTabLayout.setSelectedTabIndicatorColor(getResources().getColor(R.color.mainBlue)); mTabLayout.post(new Runnable() { @Override public void run() { MiscUtil.setIndicator(mTabLayout, 0, 0); } }); // 选择项 currentCases.clear(); tagAdapter = new TagAdapter<RecordCaseInfo>(currentCases) { @Override public View getView(FlowLayout parent, int position, RecordCaseInfo o) { View tag = LayoutInflater.from(BatchExecutionActivity.this).inflate(R.layout.item_batch_execute_tag, parent, false); TextView title = (TextView) tag.findViewById(R.id.batch_execute_tag_name); title.setText(o.getCaseName()); return tag; } }; tagGroup = (TagFlowLayout) findViewById(R.id.batch_execute_tag_group); tagGroup.setMaxSelectCount(0); tagGroup.setAdapter(tagAdapter); tagGroup.setOnTagClickListener(this); CustomPagerAdapter pagerAdapter = new CustomPagerAdapter(getSupportFragmentManager()); mPager.setAdapter(pagerAdapter); mRestartApp = (CheckBox) findViewById(R.id.batch_execute_restart); startExecutionBtn = (Button) findViewById(R.id.batch_execute_start_btn); startExecutionBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (currentCases.size() == 0) { toastShort(getString(R.string.batch__select_case)); return; } PermissionUtil.OnPermissionCallback callback = new PermissionUtil.OnPermissionCallback() { @Override public void onPermissionResult(boolean result, String reason) { if (result) { CaseReplayUtil.startReplayMultiCase(currentCases, mRestartApp.isChecked()); startApp(currentCases.get(0).getTargetAppPackage()); } } }; checkPermissions(callback); } }); //点击批量上传按钮 batchUpLoadBtn = (Button) findViewById(R.id.batch_upload_start_btn); batchUpLoadBtn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View view) { if (currentCases.size()==0){ toastShort("还未选择用例!!"); return ; } HashMap<String, Object> caseHashMap = new HashMap<>(); //遍历currentCases,将每一个case都赋值对应的caseStep BufferedReader reader=null; for (int i = 0; i < currentCases.size(); i++) { RecordCaseInfo caseInfo = currentCases.get(i); String path = caseInfo.getOperationLog(); String storePath = JSON.parseObject(path).get("storePath").toString(); StringBuffer caseStep = new StringBuffer(); try { reader = new BufferedReader(new FileReader(storePath)); String line; if ((line = reader.readLine()) !=null){ caseStep.append(line); } } catch (IOException e) { e.printStackTrace(); } String caseStepStr = caseStep.toString(); //用例上传前进行解密 List<OperationStep> operationSteps = JSON.parseArray(caseStepStr, OperationStep.class); for (OperationStep operationStep : operationSteps) { OperationMethod operationMethod = operationStep.getOperationMethod(); if (operationMethod.isEncrypt()){ Set<String> paramKeys = operationMethod.getParamKeys(); HashMap<String, String> tempParms = new HashMap<>(); for (String paramKey : paramKeys) { //解密 String param = operationMethod.getParam(paramKey); tempParms.put(paramKey,param); } //设置为不加密模式 operationMethod.setEncrypt(false); for (String key : tempParms.keySet()) { //在不加密的模式下,将解密后的数据写入到operationMethod operationMethod.putParam(key,tempParms.get(key)); } } } caseInfo.setCaseStep(JSON.toJSONString(operationSteps)); } try { if (reader!=null) reader.close(); } catch (IOException e) { e.printStackTrace(); } //将最终的currentCases放到map caseHashMap.put("caseInfo",currentCases); try { RealTimeManage.sendMessage(caseHashMap); toastShort(getString(R.string.batch__uploadsuccess_case)); } catch (Exception e) { toastShort(getString(R.string.batch__uploadfail_case)); e.printStackTrace(); } /*//ToDo:批量上传是否需要权限?? PermissionUtil.OnPermissionCallback callback = new PermissionUtil.OnPermissionCallback(){ @Override public void onPermissionResult(boolean result, String reason) { if (result){ HashMap<String, Object> caseHashMap = new HashMap<>(); caseHashMap.put("caseInfo",currentCases); RealTimeManage.sendMessage(caseHashMap); } } }; checkPermissions(callback);*/ } }); } @Override public void onItemAdd(RecordCaseInfo caseInfo) { currentCases.add(caseInfo); updateExecutionTag(); } public void updateExecutionTag() { tagAdapter.notifyDataChanged(); } @Override public boolean onTagClick(View view, int position, FlowLayout parent) { currentCases.remove(position); updateExecutionTag(); return false; } private void startApp(final String packageName) { if (packageName == null) { return; } BackgroundExecutor.execute(new Runnable() { @Override public void run() { AppUtil.forceStopApp(packageName); LogUtil.e("NewRecordActivity", "强制终止应用:" + packageName); MiscUtil.sleep(500); AppUtil.startApp(packageName); } }); } /** * 检察权限 * @param callback */ private void checkPermissions(PermissionUtil.OnPermissionCallback callback) { // 高权限,悬浮窗权限判断 PermissionUtil.requestPermissions(Arrays.asList("adb", Settings.ACTION_ACCESSIBILITY_SETTINGS), this, callback); } private static class CustomPagerAdapter extends FragmentPagerAdapter { private static int[] PAGES = BatchExecutionFragment.getTypes(); public CustomPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return BatchExecutionFragment.newInstance(PAGES[position]); } @Override public CharSequence getPageTitle(int position) { return BatchExecutionFragment.getTypeName(PAGES[position]); } @Override public int getCount() { return PAGES.length; } } }
三:用例执行后,结果上传【目前为了debug方便,需要手动上传的,后续会改成自动上传】
//上传批量回放执行结果 mPanel.setInfoIconClickListener(R.drawable.icon_save, new View.OnClickListener() { @Override public void onClick(View v) { HashMap<String, Object> replayResultInfo = new HashMap<>(); for (ReplayResultBean replayResultBean : mResults) { List<OperationStep> currentOperationLog = replayResultBean.getCurrentOperationLog(); for (OperationStep operationStep : currentOperationLog) { OperationMethod operationMethod = operationStep.getOperationMethod(); if (operationMethod.isEncrypt()) {//为加密状态 HashMap<String, String> unEncryParm = new HashMap<>();//存储未加密的参数 Set<String> paramKeys = operationMethod.getParamKeys(); for (String paramKey : paramKeys) { String param = operationMethod.getParam(paramKey);//拿到解密后的参数值 unEncryParm.put(paramKey, param);//未加密的数据存储到unEncryParm } operationMethod.setEncrypt(false);//设置为不加密 Set<String> unEncryParmKey = unEncryParm.keySet(); for (String key : unEncryParmKey) { operationMethod.getOperationParam().put(key, unEncryParm.get(key));//将解密数据写会到operationMethod中 } } } replayResultBean.setTaskId(RealTimeManage.taskId);//给每个结果加上taskId }
/* * Copyright (C) 2015-present, Ant Financial Services Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.hulu.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.annotation.Nullable; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import com.alipay.hulu.R; import com.alipay.hulu.bean.CaseStepHolder; import com.alipay.hulu.bean.ReplayResultBean; import com.alipay.hulu.common.application.LauncherApplication; import com.alipay.hulu.common.tools.BackgroundExecutor; import com.alipay.hulu.runManager.RealTimeManage; import com.alipay.hulu.shared.node.action.OperationMethod; import com.alipay.hulu.shared.node.tree.export.bean.OperationStep; import com.alipay.hulu.ui.HeadControlPanel; import com.alipay.hulu.util.LargeObjectHolder; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * 用例批量回放执行结果,该页面改造:回访完成后上传回放结果 */ public class BatchReplayResultActivity extends BaseActivity { private ListView mResultList; private TextView mTotalNum; private TextView mSuccessNum; private TextView mFailNum; private HeadControlPanel mPanel; private ResultAdapter mAdapter; private List<ReplayResultBean> mResults; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_batch_replay_result); initView(); initListeners(); initState(); } private void initState() { mResults = LargeObjectHolder.getInstance().getReplayResults(); // 清理引用 LargeObjectHolder.getInstance().setReplayResults(null); if (mResults == null) { finish(); return; } int totalNum = 0; int successNum = 0; for (ReplayResultBean bean : mResults) { totalNum++; if (TextUtils.isEmpty(bean.getExceptionMessage())) { successNum++; } } //批量回放执行结果页,回放总结: mTotalNum.setText(getString(R.string.batch_replay_result__case_count, totalNum));//用例总数 mSuccessNum.setText(getString(R.string.batch_replay_result__success_count, successNum));//成功数 mFailNum.setText(getString(R.string.batch_replay_result__failed_count, totalNum - successNum));//失败数 mAdapter = new ResultAdapter(this, mResults); mResultList.setAdapter(mAdapter); //上传批量回放执行结果 mPanel.setInfoIconClickListener(R.drawable.icon_save, new View.OnClickListener() { @Override public void onClick(View v) { HashMap<String, Object> replayResultInfo = new HashMap<>(); for (ReplayResultBean replayResultBean : mResults) { List<OperationStep> currentOperationLog = replayResultBean.getCurrentOperationLog(); for (OperationStep operationStep : currentOperationLog) { OperationMethod operationMethod = operationStep.getOperationMethod(); if (operationMethod.isEncrypt()) {//为加密状态 HashMap<String, String> unEncryParm = new HashMap<>();//存储未加密的参数 Set<String> paramKeys = operationMethod.getParamKeys(); for (String paramKey : paramKeys) { String param = operationMethod.getParam(paramKey);//拿到解密后的参数值 unEncryParm.put(paramKey, param);//未加密的数据存储到unEncryParm } operationMethod.setEncrypt(false);//设置为不加密 Set<String> unEncryParmKey = unEncryParm.keySet(); for (String key : unEncryParmKey) { operationMethod.getOperationParam().put(key, unEncryParm.get(key));//将解密数据写会到operationMethod中 } } } replayResultBean.setTaskId(RealTimeManage.taskId);//给每个结果加上taskId } replayResultInfo.put("replayResultInfo" + RealTimeManage.taskId, mResults); showProgressDialog(getString(R.string.case_replay__update));//上传中 BackgroundExecutor.execute(new Runnable() { @Override public void run() { if (mResults.size() != 0) { RealTimeManage.sendMessage(replayResultInfo);//上传回放结果 LauncherApplication.getInstance().showDialog(BatchReplayResultActivity.this, "", getString(R.string.replay__upload_result_to), null); } else { toastLong(getString(R.string.replay__upload_failed)); } } }); } }); } private void initListeners() { mResultList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ReplayResultBean bean = (ReplayResultBean) mAdapter.getItem(position); if (bean == null) { return; } // 由holder保存 Intent intent = new Intent(BatchReplayResultActivity.this, CaseReplayResultActivity.class); int resId = CaseStepHolder.storeResult(bean); intent.putExtra("data", resId); startActivity(intent); } }); } private void initView() { mResultList = (ListView) findViewById(R.id.result_list); mTotalNum = (TextView) findViewById(R.id.total_num); mSuccessNum = (TextView) findViewById(R.id.success_num); mFailNum = (TextView) findViewById(R.id.fail_num); mPanel = (HeadControlPanel) findViewById(R.id.head_layout); mPanel.setBackIconClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mPanel.setMiddleTitle(getString(R.string.activity__batch_replay_result)); } private static class ResultAdapter extends BaseAdapter { private Context mContext; private List<ReplayResultBean> mData = new ArrayList<>(); public ResultAdapter(Context context, List<ReplayResultBean> data) { mContext = context; mData = data; } @Override public int getCount() { return mData.size(); } @Override public Object getItem(int position) { return mData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.item_replay_result, parent, false); holder = new ViewHolder(); holder.caseName = (TextView) convertView.findViewById(R.id.case_name); holder.result = (TextView) convertView.findViewById(R.id.result); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } ReplayResultBean bean = (ReplayResultBean) getItem(position); if (bean != null) { holder.caseName.setText(bean.getCaseName()); if (TextUtils.isEmpty(bean.getExceptionMessage())) { holder.result.setText(R.string.constant__success); holder.result.setTextColor(0xff65c0ba); } else { holder.result.setText(R.string.constant__fail); holder.result.setTextColor(0xfff76262); } } return convertView; } class ViewHolder { TextView caseName; TextView result; } } }
四:模板参数【当前是在输入框控件下增加了模板参数输入框,用例录制时输入模板参数,后续服务端进行模板替换】
核心代码:
新增一个输入框控件,作为模板参数名的上传入口。
如:想要参数化手机号密码登录的case,则在录制case时,输入手机号之后,指定对应的模板参数key:login.phone,方便服务端拿到该数据后进行替换。
protected static void showEditView(final AbstractNodeTree node, final OperationMethod method, final Context context, final FunctionListener listener) { try { PerformActionEnum action = method.getActionEnum(); String title = StringUtil.getString(R.string.function__input_title); View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_record_name, null); final EditText edit = (EditText) v.findViewById(R.id.dialog_record_edit); //新增的模板输入框 final EditText templateEdit = (EditText) v.findViewById(R.id.dialog_template_edit); templateEdit.setText("");//每次输入前都先清空下,防止append导致上传数据错误 View hide = v.findViewById(R.id.dialog_record_edit_hide); hide.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { hideInput(edit); } }); final Pattern textPattern; if (action == PerformActionEnum.SLEEP) { edit.setHint(R.string.function__sleep_time); title = StringUtil.getString(R.string.function__set_sleep_time); textPattern = Pattern.compile("\\d+"); } else if (action == PerformActionEnum.SCREENSHOT) { edit.setHint(R.string.function__screenshot_name); title = StringUtil.getString(R.string.function__set_screenshot_name); textPattern = Pattern.compile("\\S+(.*\\S+)?"); } else if (action ==PerformActionEnum.MULTI_CLICK) { edit.setHint(R.string.function__click_time); title = StringUtil.getString(R.string.function__set_click_time); textPattern = Pattern.compile("\\d{1,2}"); } else if (action ==PerformActionEnum.SLEEP_UNTIL) { edit.setHint(R.string.function__max_wait); edit.setText(R.string.default_sleep_time); title = StringUtil.getString(R.string.function__set_max_wait); textPattern = Pattern.compile("\\d+"); } else if (action == PerformActionEnum.SCROLL_TO_BOTTOM || action == PerformActionEnum.SCROLL_TO_TOP || action == PerformActionEnum.SCROLL_TO_LEFT || action == PerformActionEnum.SCROLL_TO_RIGHT) { edit.setHint(R.string.function__scroll_percent); edit.setText(R.string.default_scroll_percentage); title = StringUtil.getString(R.string.function__set_scroll_percent); textPattern = Pattern.compile("\\d+"); } else if (action == PerformActionEnum.EXECUTE_SHELL) { edit.setHint(R.string.function__adb_cmd); title = StringUtil.getString(R.string.function__set_adb_cmd); textPattern = null; } else if (action == PerformActionEnum.LONG_CLICK) { edit.setHint(R.string.function__long_press); title = StringUtil.getString(R.string.function__set_long_press); textPattern = Pattern.compile("[1-9]\\d+"); edit.setText(R.string.default_long_click_time); } else { edit.setHint(R.string.function__input_content); textPattern = null; } final AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(title) .setView(v) .setPositiveButton(R.string.function__input, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Positive " + which); String data = edit.getText().toString(); String templateText = templateEdit.getText().toString();//获得模板key // 拼装参数 method.putParam(OperationExecutor.INPUT_TEXT_KEY, data); method.putParam(OperationExecutor.INPUT_TEMPLATE_KEY,templateText);//将模板key写入到param // 隐藏Dialog dialog.dismiss(); // 抛给主线程 LauncherApplication.getInstance().runOnUiThread(new Runnable() { @Override public void run() { // 操作记录 listener.onProcessFunction(method, node); } }, 500); } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Negative " + which); dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); //点击外面区域不会让dialog消失 dialog.setCancelable(false); dialog.show(); // 校验输入 edit.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); boolean enable = true; if (textPattern != null) { String content = s.toString(); enable = textPattern.matcher(content).matches(); } // 如果不是目标状态,改变下 if (positiveButton.isEnabled() != enable) { positiveButton.setEnabled(enable); } } }); dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); } catch (Exception e) { LogUtil.e(TAG, "Throw exception: " + e.getMessage(), e); } }
/* * Copyright (C) 2015-present, Ant Financial Services Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.hulu.util; import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.AppCompatSpinner; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.util.DisplayMetrics; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.ScrollView; import android.widget.TextView; import com.alibaba.fastjson.JSON; import com.alipay.hulu.R; import com.alipay.hulu.common.application.LauncherApplication; import com.alipay.hulu.common.injector.InjectorService; import com.alipay.hulu.common.service.ScreenCaptureService; import com.alipay.hulu.common.tools.BackgroundExecutor; import com.alipay.hulu.common.tools.CmdTools; import com.alipay.hulu.common.utils.ContextUtil; import com.alipay.hulu.common.utils.FileUtils; import com.alipay.hulu.common.utils.LogUtil; import com.alipay.hulu.common.utils.MiscUtil; import com.alipay.hulu.common.utils.StringUtil; import com.alipay.hulu.shared.node.OperationService; import com.alipay.hulu.shared.node.action.Constant; import com.alipay.hulu.shared.node.action.OperationExecutor; import com.alipay.hulu.shared.node.action.OperationMethod; import com.alipay.hulu.shared.node.action.PerformActionEnum; import com.alipay.hulu.shared.node.action.RunningModeEnum; import com.alipay.hulu.shared.node.action.provider.ActionProviderManager; import com.alipay.hulu.shared.node.action.provider.ViewLoadCallback; import com.alipay.hulu.shared.node.tree.AbstractNodeTree; import com.alipay.hulu.shared.node.utils.BitmapUtil; import com.alipay.hulu.shared.node.utils.LogicUtil; import com.alipay.hulu.tools.HighLightService; import com.alipay.hulu.ui.CheckableRelativeLayout; import com.alipay.hulu.ui.FlowRadioGroup; import com.alipay.hulu.ui.GesturePadView; import com.alipay.hulu.ui.TwoLevelSelectLayout; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** * 操作选择界面 * Created by qiaoruikai on 2019/2/22 8:18 PM. */ public class FunctionSelectUtil { private static final String TAG = "FunctionSelect"; public static final String ACTION_EXTRA = "ACTION_EXTRA"; /** * 展示操作界面 * * @param node */ public static void showFunctionView(final Context context, final AbstractNodeTree node, final List<Integer> keys, final List<Integer> icons, final Map<Integer,List<TwoLevelSelectLayout.SubMenuItem>> secondLevel, final HighLightService highLightService, final OperationService operationService, final Pair<Float, Float> localClickPos, final FunctionListener listener) { // 没有操作 DialogUtils.showLeveledFunctionView(context, keys, icons, secondLevel, new DialogUtils.FunctionViewCallback<TwoLevelSelectLayout.SubMenuItem>() { @Override public void onExecute(DialogInterface dialog, TwoLevelSelectLayout.SubMenuItem action) { PerformActionEnum actionEnum = PerformActionEnum.getActionEnumByCode(action.key); if (actionEnum == null) { dialog.dismiss(); listener.onCancel(); return; } LogUtil.d(TAG, "点击操作: %s, extra: %s", actionEnum, action.extra); if (actionEnum == PerformActionEnum.OTHER_GLOBAL || actionEnum == PerformActionEnum.OTHER_NODE) { final OperationMethod method = new OperationMethod(actionEnum); // 添加控件点击位置 if (localClickPos != null) { method.putParam(OperationExecutor.LOCAL_CLICK_POS_KEY, localClickPos.first + "," + localClickPos.second); } // 先隐藏Dialog dialog.dismiss(); // 隐藏高亮 if (highLightService != null) { highLightService.removeHightLightSync(); } method.putParam(ActionProviderManager.KEY_TARGET_ACTION_DESC, action.name); method.putParam(ActionProviderManager.KEY_TARGET_ACTION, action.extra); // 等500ms LauncherApplication.getInstance().runOnUiThread(new Runnable() { @Override public void run() { operationService.getActionProviderMng().loadActionView(context, method, node, new ViewLoadCallback() { @Override public void onViewLoaded(View v, Runnable preCall) { if (v == null) { listener.onProcessFunction(method, node); } else { showProvidedView(node, method, context, v, preCall, highLightService, listener); } } }); } }); } else { LogUtil.i(TAG, "Perform Action: " + action); final OperationMethod method = new OperationMethod( PerformActionEnum.getActionEnumByCode(action.key)); // 透传一下 if (!StringUtil.isEmpty(action.extra)) { method.putParam(ACTION_EXTRA, action.extra); } // 添加控件点击位置 if (localClickPos != null) { method.putParam(OperationExecutor.LOCAL_CLICK_POS_KEY, localClickPos.first + "," + localClickPos.second); } // 隐藏Dialog dialog.dismiss(); // 隐藏高亮 if (highLightService != null) { highLightService.removeHightLightSync(); } // 等界面变化完毕 LauncherApplication.getInstance().runOnUiThread(new Runnable() { @Override public void run() { // 处理操作 boolean result = processAction(method, node, context, operationService, listener); // 如果没有处理,走默认处理 if (result) { return; } // 向handler发送点击请求 listener.onProcessFunction(method, node); } }, 200); } } @Override public void onCancel(DialogInterface dialog) { LogUtil.d(TAG, "Dialog canceled"); dialog.dismiss(); // 定时执行 if (highLightService != null) { highLightService.removeHightLightSync(); } listener.onCancel(); } @Override public void onDismiss(DialogInterface dialog) { } }); } /** * 自身处理一些操作 * * @param method * @param node * @param context * @return */ protected static boolean processAction(OperationMethod method, AbstractNodeTree node, final Context context, OperationService operationService, FunctionListener listener) { PerformActionEnum action = method.getActionEnum(); if (action == PerformActionEnum.INPUT || action == PerformActionEnum.INPUT_SEARCH || action == PerformActionEnum.LONG_CLICK || action == PerformActionEnum.MULTI_CLICK || action == PerformActionEnum.SLEEP_UNTIL || action == PerformActionEnum.SLEEP || action == PerformActionEnum.SCREENSHOT || action == PerformActionEnum.SCROLL_TO_BOTTOM || action == PerformActionEnum.SCROLL_TO_TOP || action == PerformActionEnum.SCROLL_TO_LEFT || action == PerformActionEnum.SCROLL_TO_RIGHT || action == PerformActionEnum.EXECUTE_SHELL) { showEditView(node, method, context, listener); return true; } else if (action == PerformActionEnum.ASSERT || action == PerformActionEnum.ASSERT_TOAST) { chooseAssertMode(node, action, context, listener); return true; } else if (action == PerformActionEnum.LET_NODE) { chooseLetMode(node, context, listener, operationService); return true; } else if (action == PerformActionEnum.LET) { chooseLetGlobalMode(context, listener, operationService); return true; } else if (action == PerformActionEnum.CHECK || action == PerformActionEnum.CHECK_NODE) { chooseCheckMode(node, context, listener, operationService); return true; } else if (action == PerformActionEnum.JUMP_TO_PAGE || action == PerformActionEnum.GENERATE_QR_CODE || action == PerformActionEnum.GENERATE_BAR_CODE || action == PerformActionEnum.LOAD_PARAM) { showSelectView(method, context, listener); return true; } else if (action == PerformActionEnum.CHANGE_MODE) { showChangeModeView(context, listener); return true; } else if (action == PerformActionEnum.WHILE) { showWhileView(method, context, listener); return true; } else if (action == PerformActionEnum.IF) { method.putParam(LogicUtil.CHECK_PARAM, ""); } else if (action == PerformActionEnum.GESTURE || action == PerformActionEnum.GLOBAL_GESTURE) { captureAndShowGesture(action, node, context, listener); return true; } else if (action == PerformActionEnum.GLOBAL_SCROLL_TO_BOTTOM || action == PerformActionEnum.GLOBAL_SCROLL_TO_TOP || action == PerformActionEnum.GLOBAL_SCROLL_TO_LEFT || action == PerformActionEnum.GLOBAL_SCROLL_TO_RIGHT) { showScrollControlView(method, context, listener); return true; } return false; } /** * 显示修改模式Dialog */ private static void showChangeModeView(Context context, final FunctionListener listener) { try { final RunningModeEnum[] modes = RunningModeEnum.values(); final String[] actions = new String[modes.length]; for (int i = 0; i < modes.length; i++) { actions[i] = modes[i].getDesc(); } AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(R.string.function__set_mode) .setSingleChoiceItems(actions, 0, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Click " + which); if (dialog != null) { dialog.dismiss(); } // 执行操作 OperationMethod method = new OperationMethod(PerformActionEnum.CHANGE_MODE); method.putParam(OperationExecutor.GET_NODE_MODE, modes[which].getCode()); listener.onProcessFunction(method, null); } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); listener.onCancel(); } }); AlertDialog dialog = builder.create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); dialog.setCancelable(false); dialog.show(); } catch (Exception e) { e.printStackTrace(); listener.onCancel(); } } /** * 展示滑动控制 * @param context */ private static void showScrollControlView(final OperationMethod method, Context context, final FunctionListener listener) { try { LayoutInflater inflater = LayoutInflater.from(ContextUtil.getContextThemeWrapper( context, R.style.AppDialogTheme)); ScrollView v = (ScrollView) inflater.inflate(R.layout.dialog_setting, null); LinearLayout view = (LinearLayout) v.getChildAt(0); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); // 对每一个字段添加EditText View editField = inflater.inflate(R.layout.item_edit_field, null); final EditText distance = (EditText) editField.findViewById(R.id.item_edit_field_edit); TextView distanceName = (TextView) editField.findViewById(R.id.item_edit_field_name); // 配置字段 distance.setHint(R.string.scroll_setting__scroll_distense); distanceName.setText(R.string.scroll_setting__scroll_distense); distance.setInputType(InputType.TYPE_CLASS_NUMBER); distance.setText("40"); // 设置其他参数 distance.setTextColor(context.getResources().getColor(R.color.primaryText)); distance.setHintTextColor(context.getResources().getColor(R.color.secondaryText)); distance.setHighlightColor(context.getResources().getColor(R.color.colorAccent)); view.addView(editField, layoutParams); editField = inflater.inflate(R.layout.item_edit_field, null); final EditText time = (EditText) editField.findViewById(R.id.item_edit_field_edit); TextView timeName = (TextView) editField.findViewById(R.id.item_edit_field_name); // 配置字段 time.setHint(R.string.scroll_setting__scroll_time); timeName.setText(R.string.scroll_setting__scroll_time); time.setText("1000"); time.setInputType(InputType.TYPE_CLASS_NUMBER); // 设置其他参数 time.setTextColor(context.getResources().getColor(R.color.primaryText)); time.setHintTextColor(context.getResources().getColor(R.color.secondaryText)); time.setHighlightColor(context.getResources().getColor(R.color.colorAccent)); view.addView(editField, layoutParams); // 显示Dialog AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(R.string.scroll_setting__set_scroll_param) .setView(v) .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 获取每个编辑框的文字 dialog.dismiss(); method.putParam(OperationExecutor.SCROLL_DISTANCE, distance.getText().toString()); method.putParam(OperationExecutor.SCROLL_TIME, time.getText().toString()); listener.onProcessFunction(method, null); } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); dialog.setCancelable(false); dialog.show(); } catch (Exception e) { LogUtil.e(TAG, "Throw exception: " + e.getMessage(), e); listener.onCancel(); } } /** * 展示选择框 * @param method * @param context */ private static void showSelectView(final OperationMethod method, final Context context, final FunctionListener listener) { try { final PerformActionEnum actionEnum = method.getActionEnum(); View customView = LayoutInflater.from(context).inflate(R.layout.dialog_select_view, null); View itemScan = customView.findViewById(R.id.item_scan); TextView itemUrl = customView.findViewById(R.id.item_url); if (actionEnum == PerformActionEnum.GENERATE_QR_CODE || actionEnum == PerformActionEnum.GENERATE_BAR_CODE) { itemUrl.setText(R.string.function__input_code); } final AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setView(customView) .setTitle(R.string.function__select_function) .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); //点击外面区域不会让dialog消失 dialog.setCancelable(false); View.OnClickListener _listener = new View.OnClickListener() { @Override public void onClick(View v) { if (actionEnum == PerformActionEnum.JUMP_TO_PAGE || actionEnum == PerformActionEnum.GENERATE_QR_CODE || actionEnum == PerformActionEnum.GENERATE_BAR_CODE) { if (v.getId() == R.id.item_scan) { method.putParam("scan", "1"); listener.onProcessFunction(method, null); } else if (v.getId() == R.id.item_url) { dialog.dismiss(); showUrlEditView(method, context, listener); return; } } else if (actionEnum == PerformActionEnum.LOAD_PARAM) { if (v.getId() == R.id.item_scan) { method.putParam("scan", "1"); listener.onProcessFunction(method, null); } else if (v.getId() == R.id.item_url) { dialog.dismiss(); showUrlEditView(method, context, listener); return; } } else { dialog.dismiss(); listener.onCancel(); } dialog.dismiss(); } }; itemScan.setOnClickListener(_listener); itemUrl.setOnClickListener(_listener); dialog.show(); } catch (Exception e) { LogUtil.e(TAG, e.getMessage()); listener.onCancel(); } } /** * URL编辑框 * @param method * @param context * @param listener */ private static void showUrlEditView(final OperationMethod method, Context context, final FunctionListener listener) { final PerformActionEnum actionEnum = method.getActionEnum(); View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_record_name, null); final EditText edit = (EditText) v.findViewById(R.id.dialog_record_edit); edit.setHint(R.string.function__please_input_url); if (actionEnum == PerformActionEnum.GENERATE_QR_CODE || actionEnum == PerformActionEnum.GENERATE_BAR_CODE) { edit.setHint(R.string.function__please_input_qr_code); } int title = (actionEnum == PerformActionEnum.GENERATE_QR_CODE || actionEnum == PerformActionEnum.GENERATE_BAR_CODE)? R.string.function__input_qr_code: R.string.function__input_url; AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(title) .setView(v) .setPositiveButton(R.string.function__input, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Positive " + which); String data = edit.getText().toString(); dialog.dismiss(); if (actionEnum == PerformActionEnum.JUMP_TO_PAGE || actionEnum == PerformActionEnum.GENERATE_QR_CODE || actionEnum == PerformActionEnum.GENERATE_BAR_CODE) { // 向handler发送请求 method.putParam(OperationExecutor.SCHEME_KEY, data); listener.onProcessFunction(method, null); } else if (actionEnum == PerformActionEnum.LOAD_PARAM) { method.putParam(OperationExecutor.APP_URL_KEY, data); // 向handler发送请求 listener.onProcessFunction(method, null); } } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); //点击外面区域不会让dialog消失 dialog.setCancelable(false); dialog.show(); } /** * 设置变量框 * @param node * @param context * @param listener */ private static void chooseLetMode(AbstractNodeTree node, final Context context, final FunctionListener listener, final OperationService service) { if (node == null) { LogUtil.e(TAG, "Receive null node, can't let value"); listener.onCancel(); return; } // 如果是TextView外面包装的一层,解析内部的TextView if (node.getChildrenNodes() != null && node.getChildrenNodes().size() == 1) { AbstractNodeTree child = node.getChildrenNodes().get(0); if (StringUtil.equals(child.getClassName(), "android.widget.TextView")) { node = child; } } // 获取页面 View letView = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_action_let, null); // 分别设置内容 final CheckableRelativeLayout textWrapper = (CheckableRelativeLayout) letView.findViewById( R.id.dialog_action_let_text); ((TextView)textWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__text); ((TextView)textWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getText()); final CheckableRelativeLayout descWrapper = (CheckableRelativeLayout) letView.findViewById( R.id.dialog_action_let_description); ((TextView)descWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__description); ((TextView)descWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getDescription()); final CheckableRelativeLayout classWrapper = (CheckableRelativeLayout) letView.findViewById( R.id.dialog_action_let_class_name); ((TextView)classWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__class_name); ((TextView)classWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getClassName()); final CheckableRelativeLayout xpathWrapper = (CheckableRelativeLayout) letView.findViewById( R.id.dialog_action_let_xpath); ((TextView)xpathWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__xpath); ((TextView)xpathWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getXpath()); final CheckableRelativeLayout resIdWrapper = (CheckableRelativeLayout) letView.findViewById( R.id.dialog_action_let_resource_id); ((TextView)resIdWrapper.findViewById(R.id.dialog_action_let_item_title)).setText(R.string.function_select__res_id); ((TextView)resIdWrapper.findViewById(R.id.dialog_action_let_item_value)).setText(node.getResourceId()); final CheckableRelativeLayout otherWrapper = (CheckableRelativeLayout) letView.findViewById( R.id.dialog_action_let_other); final EditText valExpr = (EditText) otherWrapper.findViewById(R.id.dialog_action_let_other_value); final RadioGroup valType = (RadioGroup) otherWrapper.findViewById(R.id.dialog_action_let_other_type); final TextView valVal = (TextView) otherWrapper.findViewById(R.id.dialog_action_let_other_value_val); final AbstractNodeTree finalNode = node; valType.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (service != null) { String expr = valExpr.getText().toString(); if (StringUtil.isEmpty(expr)) { valVal.setText(context.getString(R.string.action_let_cur_value, "-")); return; } String val = LogicUtil.eval(expr, finalNode, checkedId == R.id.dialog_action_let_other_type_int ? LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service); if (val != null) { valVal.setText(context.getString(R.string.action_let_cur_value, val)); } else { valVal.setText(context.getString(R.string.action_let_cur_value, "-")); } } } }); valExpr.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (service != null) { String expr = s.toString(); if (StringUtil.isEmpty(expr)) { valVal.setText(context.getString(R.string.action_let_cur_value, "-")); return; } String val = LogicUtil.eval(expr, finalNode, valType.getCheckedRadioButtonId() == R.id.dialog_action_let_other_type_int ? LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service); if (val != null) { valVal.setText(context.getString(R.string.action_let_cur_value, val)); } else { valVal.setText(context.getString(R.string.action_let_cur_value, "-")); } } } @Override public void afterTextChanged(Editable s) { } }); final CheckableRelativeLayout[] previous = {textWrapper}; final String[] valValue = { "${node.text}" }; previous[0].setChecked(true); CheckableRelativeLayout.OnCheckedChangeListener checkedChangeListener = new CheckableRelativeLayout.OnCheckedChangeListener() { @Override public void onCheckedChanged(CheckableRelativeLayout checkable, boolean isChecked) { // 如果是自己点自己,不允许取消勾选 if (previous[0] == checkable) { // checkable.setChecked(true); return; } if (previous[0] == otherWrapper) { valExpr.setEnabled(false); valType.setEnabled(false); } previous[0].setChecked(false); if (checkable == textWrapper) { valValue[0] = "${node.text}"; } else if (checkable == descWrapper) { valValue[0] = "${node.description}"; } else if (checkable == classWrapper) { valValue[0] = "${node.className}"; } else if (checkable == xpathWrapper) { valValue[0] = "${node.xpath}"; } else if (checkable == resIdWrapper) { valValue[0] = "${node.resourceId}"; } else if (checkable == otherWrapper) { valValue[0] = ""; valExpr.setEnabled(true); valType.setEnabled(true); } previous[0] = checkable; } }; textWrapper.setOnCheckedChangeListener(checkedChangeListener); descWrapper.setOnCheckedChangeListener(checkedChangeListener); classWrapper.setOnCheckedChangeListener(checkedChangeListener); xpathWrapper.setOnCheckedChangeListener(checkedChangeListener); resIdWrapper.setOnCheckedChangeListener(checkedChangeListener); otherWrapper.setOnCheckedChangeListener(checkedChangeListener); final EditText valName = (EditText) letView.findViewById(R.id.dialog_action_let_variable_name); AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(R.string.function__set_variable) .setView(letView) .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Positive " + which); String targetValValue = valValue[0]; String targetValName = valName.getText().toString(); int targetValType = LogicUtil.ALLOC_TYPE_STRING; if (previous[0] == otherWrapper) { targetValValue = valExpr.getText().toString(); targetValType = valType.getCheckedRadioButtonId() == R.id.dialog_action_let_other_type_int? LogicUtil.ALLOC_TYPE_INTEGER: LogicUtil.ALLOC_TYPE_STRING; } dialog.dismiss(); OperationMethod method = new OperationMethod(PerformActionEnum.LET_NODE); method.putParam(LogicUtil.ALLOC_TYPE, Integer.toString(targetValType)); method.putParam(LogicUtil.ALLOC_VALUE_PARAM, targetValValue); method.putParam(LogicUtil.ALLOC_KEY_PARAM, targetValName); listener.onProcessFunction(method, finalNode); } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); //点击外面区域不会让dialog消失 dialog.setCancelable(false); dialog.show(); } /** * 动态赋值选择框 * @param context * @param listener */ private static void chooseCheckMode(AbstractNodeTree node, final Context context, final FunctionListener listener, final OperationService service) { // 如果是TextView外面包装的一层,解析内部的TextView if (node != null) { if (node.getChildrenNodes() != null && node.getChildrenNodes().size() == 1) { AbstractNodeTree child = node.getChildrenNodes().get(0); if (StringUtil.equals(child.getClassName(), "android.widget.TextView")) { node = child; } } } final AbstractNodeTree finalNode = node; // 获取页面 View checkView = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_action_check_global, null); final EditText leftExpr = (EditText) checkView.findViewById(R.id.dialog_action_check_left_value); final TextView leftVal = (TextView) checkView.findViewById(R.id.dialog_action_check_left_value_val); final EditText rightExpr = (EditText) checkView.findViewById(R.id.dialog_action_check_right_value); final TextView rightVal = (TextView) checkView.findViewById(R.id.dialog_action_check_right_value_val); final RadioGroup valType = (RadioGroup) checkView.findViewById(R.id.dialog_action_check_type); final RadioGroup compareType = (RadioGroup) checkView.findViewById(R.id.dialog_action_check_compare); final RadioButton bigger = (RadioButton) compareType.findViewById(R.id.dialog_action_check_compare_bigger); final RadioButton biggerEqual = (RadioButton) compareType.findViewById(R.id.dialog_action_check_compare_bigger_equal); final RadioButton less = (RadioButton) compareType.findViewById(R.id.dialog_action_check_compare_less); final RadioButton lessEqual = (RadioButton) compareType.findViewById(R.id.dialog_action_check_compare_less_equal); valType.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (service != null) { String expr = leftExpr.getText().toString(); if (StringUtil.isEmpty(expr)) { leftVal.setText(context.getString(R.string.action_let_cur_value, "-")); } else { String val = LogicUtil.eval(expr, finalNode, checkedId == R.id.dialog_action_check_type_int ? LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service); if (val != null) { leftVal.setText(context.getString(R.string.action_let_cur_value, val)); } else { leftVal.setText(context.getString(R.string.action_let_cur_value, "-")); } } expr = rightExpr.getText().toString(); if (StringUtil.isEmpty(expr)) { rightVal.setText(context.getString(R.string.action_let_cur_value, "-")); } else { String val = LogicUtil.eval(expr, finalNode, checkedId == R.id.dialog_action_check_type_int ? LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service); if (val != null) { rightVal.setText(context.getString(R.string.action_let_cur_value, val)); } else { rightVal.setText(context.getString(R.string.action_let_cur_value, "-")); } } } if (checkedId == R.id.dialog_action_check_type_str) { bigger.setEnabled(false); biggerEqual.setEnabled(false); less.setEnabled(false); lessEqual.setEnabled(false); } else { bigger.setEnabled(true); biggerEqual.setEnabled(true); less.setEnabled(true); lessEqual.setEnabled(true); } } }); leftExpr.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (service != null) { String expr = s.toString(); if (StringUtil.isEmpty(expr)) { leftVal.setText(context.getString(R.string.action_let_cur_value, "-")); return; } String val = LogicUtil.eval(expr, finalNode, valType.getCheckedRadioButtonId() == R.id.dialog_action_check_type_int ? LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service); if (val != null) { leftVal.setText(context.getString(R.string.action_let_cur_value, val)); } else { leftVal.setText(context.getString(R.string.action_let_cur_value, "-")); } } } @Override public void afterTextChanged(Editable s) { } }); rightExpr.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (service != null) { String expr = s.toString(); if (StringUtil.isEmpty(expr)) { rightVal.setText(context.getString(R.string.action_let_cur_value, "-")); return; } String val = LogicUtil.eval(expr, finalNode, valType.getCheckedRadioButtonId() == R.id.dialog_action_check_type_int ? LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service); if (val != null) { rightVal.setText(context.getString(R.string.action_let_cur_value, val)); } else { rightVal.setText(context.getString(R.string.action_let_cur_value, "-")); } } } @Override public void afterTextChanged(Editable s) { } }); AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle("请设置比较内容") .setView(checkView) .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Positive " + which); String leftVal = leftExpr.getText().toString(); String rightVal = rightExpr.getText().toString(); int targetValType = valType.getCheckedRadioButtonId() == R.id.dialog_action_check_type_int? LogicUtil.ALLOC_TYPE_INTEGER: LogicUtil.ALLOC_TYPE_STRING; dialog.dismiss(); OperationMethod method; if (finalNode != null) { method = new OperationMethod(PerformActionEnum.CHECK_NODE); } else { method = new OperationMethod(PerformActionEnum.CHECK); } String connector; int id = compareType.getCheckedRadioButtonId(); if (targetValType == LogicUtil.ALLOC_TYPE_INTEGER) { if (id == R.id.dialog_action_check_compare_equal) { connector = "=="; } else if (id == R.id.dialog_action_check_compare_no_equal) { connector = "<>"; } else if (id == R.id.dialog_action_check_compare_bigger) { connector = ">"; } else if (id == R.id.dialog_action_check_compare_bigger_equal) { connector = ">="; } else if (id == R.id.dialog_action_check_compare_less) { connector = "<"; } else if (id == R.id.dialog_action_check_compare_less_equal) { connector = "<="; } else { LogUtil.w(TAG, "Can't recognize type " + targetValType); listener.onCancel(); return; } } else { if (id == R.id.dialog_action_check_compare_equal) { connector = "="; } else if (id == R.id.dialog_action_check_compare_no_equal) { connector = "!="; } else { LogUtil.w(TAG, "Can't recognize type " + targetValType); listener.onCancel(); return; } } method.putParam(LogicUtil.CHECK_PARAM, leftVal + connector + rightVal); listener.onProcessFunction(method, finalNode); } }).setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); //点击外面区域不会让dialog消失 dialog.setCancelable(false); dialog.show(); } /** * 动态赋值选择框 * @param context * @param listener */ private static void chooseLetGlobalMode(final Context context, final FunctionListener listener, final OperationService service) { // 获取页面 View letView = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_action_let_global, null); final EditText valExpr = (EditText) letView.findViewById(R.id.dialog_action_let_other_value); final RadioGroup valType = (RadioGroup) letView.findViewById(R.id.dialog_action_let_other_type); final TextView valVal = (TextView) letView.findViewById(R.id.dialog_action_let_other_value_val); valType.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (service != null) { String expr = valExpr.getText().toString(); if (StringUtil.isEmpty(expr)) { valVal.setText(context.getString(R.string.action_let_cur_value, "-")); return; } String val = LogicUtil.eval(expr, null, checkedId == R.id.dialog_action_let_other_type_int ? LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service); if (val != null) { valVal.setText(context.getString(R.string.action_let_cur_value, val)); } else { valVal.setText(context.getString(R.string.action_let_cur_value, "-")); } } } }); valExpr.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (service != null) { String expr = s.toString(); if (StringUtil.isEmpty(expr)) { valVal.setText(context.getString(R.string.action_let_cur_value, "-")); return; } String val = LogicUtil.eval(expr, null, valType.getCheckedRadioButtonId() == R.id.dialog_action_let_other_type_int ? LogicUtil.ALLOC_TYPE_INTEGER : LogicUtil.ALLOC_TYPE_STRING, service); if (val != null) { valVal.setText(context.getString(R.string.action_let_cur_value, val)); } else { valVal.setText(context.getString(R.string.action_let_cur_value, "-")); } } } @Override public void afterTextChanged(Editable s) { } }); final EditText valName = (EditText) letView.findViewById(R.id.dialog_action_let_variable_name); AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle("请设置变量值") .setView(letView) .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Positive " + which); String targetValName = valName.getText().toString(); String targetValValue = valExpr.getText().toString(); int targetValType = valType.getCheckedRadioButtonId() == R.id.dialog_action_let_other_type_int? LogicUtil.ALLOC_TYPE_INTEGER: LogicUtil.ALLOC_TYPE_STRING; dialog.dismiss(); OperationMethod method = new OperationMethod(PerformActionEnum.LET); method.putParam(LogicUtil.ALLOC_TYPE, Integer.toString(targetValType)); method.putParam(LogicUtil.ALLOC_VALUE_PARAM, targetValValue); method.putParam(LogicUtil.ALLOC_KEY_PARAM, targetValName); listener.onProcessFunction(method, null); } }).setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); //点击外面区域不会让dialog消失 dialog.setCancelable(false); dialog.show(); } /** * 选择断言模式 * * @param node */ private static void chooseAssertMode(final AbstractNodeTree node, final PerformActionEnum action, final Context context, final FunctionListener listener) { try { // 文字Assert Mode final String[] actionsType = {Constant.ASSERT_ACCURATE, Constant.ASSERT_CONTAIN, Constant.ASSERT_REGULAR}; // 数字Assert Mode final String[] numActionsType = {Constant.ASSERT_DAYU, Constant.ASSERT_DAYUANDEQUAL, Constant.ASSERT_XIAOYU, Constant.ASSERT_XIAOYUANDEQUAL, Constant.ASSERT_EQUAL}; // 判断当前内容是否是数字 StringBuilder matchTxtBuilder = new StringBuilder(); if (action == PerformActionEnum.ASSERT) { for (AbstractNodeTree item : node) { if (!TextUtils.isEmpty(item.getText())) { matchTxtBuilder.append(item.getText()); } } } else if (action == PerformActionEnum.ASSERT_TOAST) { matchTxtBuilder.append(InjectorService.g().getMessage(com.alipay.hulu.shared.event.constant.Constant.EVENT_TOAST_MSG, String.class)); } final int[] selectNumIndex = new int[1]; final String[] strResult = {null}; String matchTxt = matchTxtBuilder.toString(); if (StringUtil.isNumeric(matchTxt) && !TextUtils.isEmpty(matchTxt)) { View content = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_assert_number, null); final EditText assertNumContentEdit = (EditText) content.findViewById(R.id.assert_num_edittext); FlowRadioGroup assertGroup = (FlowRadioGroup) content.findViewById(R.id.assert_choice); assertGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (checkedId == R.id.ch1) { selectNumIndex[0] = 0; } else if (checkedId == R.id.ch2) { selectNumIndex[0] = 1; } else if (checkedId == R.id.ch3) { selectNumIndex[0] = 2; } else if (checkedId == R.id.ch4) { selectNumIndex[0] = 3; } else if (checkedId == R.id.ch5) { selectNumIndex[0] = 4; } } }); assertNumContentEdit.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { if (!TextUtils.isEmpty(assertNumContentEdit.getEditableText().toString())) { strResult[0] = assertNumContentEdit.getEditableText().toString(); } } }); AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(R.string.function__input_number_assert) .setView(content) .setPositiveButton(R.string.constant__confirm, null) .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Negative " + which); dialog.dismiss(); listener.onCancel(); } }).create(); dialog.setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(final DialogInterface dialog) { Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (StringUtil.isNumeric(strResult[0])) { HashMap<String, String> param = new HashMap<>(); param.put(OperationExecutor.ASSERT_MODE, numActionsType[selectNumIndex[0]]); param.put(OperationExecutor.ASSERT_INPUT_CONTENT, strResult[0]); postiveClick(action, node, dialog, param, listener); } else { LauncherApplication.toast(R.string.function__input_number); } } }); } }); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); dialog.setCancelable(false); dialog.show(); } else { View content = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_assert_string, null); final EditText editText = (EditText) content.findViewById(R.id.assert_string_edittext); final String[] assertInputContent = new String[1]; if (!TextUtils.isEmpty(matchTxt)) { editText.setText(matchTxt); assertInputContent[0] = matchTxt; } final int[] selectIndex = new int[1]; FlowRadioGroup assertGroup = (FlowRadioGroup) content.findViewById(R.id.assert_choice); assertGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (checkedId == R.id.ch1) { selectIndex[0] = 0; } else if (checkedId == R.id.ch2) { selectIndex[0] = 1; } else if (checkedId == R.id.ch3) { selectIndex[0] = 2; } } }); editText.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { assertInputContent[0] = editText.getEditableText().toString(); } }); AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(R.string.function__input_select_assert) .setView(content) .setPositiveButton(R.string.constant__confirm, null) .setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Negative " + which); dialog.dismiss(); listener.onCancel(); } }).create(); dialog.setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(final DialogInterface dialog) { Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!TextUtils.isEmpty(assertInputContent[0])) { HashMap<String, String> param = new HashMap<>(); param.put(OperationExecutor.ASSERT_MODE, actionsType[selectIndex[0]]); param.put(OperationExecutor.ASSERT_INPUT_CONTENT, assertInputContent[0]); postiveClick(action, node, dialog, param, listener); } else { LauncherApplication.toast(R.string.function__input_content); } } }); } }); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); dialog.setCancelable(false); dialog.show(); } } catch (Exception e) { LogUtil.e(TAG, "Throw exception: " + e.getMessage(), e); listener.onCancel(); } } private static void postiveClick(final PerformActionEnum action, final AbstractNodeTree node, DialogInterface dialog, HashMap<String, String> param, final FunctionListener listener) { // 拼装参数 final OperationMethod method = new OperationMethod(action); if (param != null) { for (String key : param.keySet()) { method.putParam(key, param.get(key)); } } // 隐藏Dialog dialog.dismiss(); // 等隐藏Dialog LauncherApplication.getInstance().runOnUiThread(new Runnable() { @Override public void run() { // 向handler发送点击请求 listener.onProcessFunction(method, node); } }, 200); } /** * 展示输入界面 * * @param node */ protected static void showEditView(final AbstractNodeTree node, final OperationMethod method, final Context context, final FunctionListener listener) { try { PerformActionEnum action = method.getActionEnum(); String title = StringUtil.getString(R.string.function__input_title); View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_record_name, null); final EditText edit = (EditText) v.findViewById(R.id.dialog_record_edit); //新增的模板输入框 final EditText templateEdit = (EditText) v.findViewById(R.id.dialog_template_edit); templateEdit.setText("");//每次输入前都先清空下,防止append导致上传数据错误 View hide = v.findViewById(R.id.dialog_record_edit_hide); hide.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { hideInput(edit); } }); final Pattern textPattern; if (action == PerformActionEnum.SLEEP) { edit.setHint(R.string.function__sleep_time); title = StringUtil.getString(R.string.function__set_sleep_time); textPattern = Pattern.compile("\\d+"); } else if (action == PerformActionEnum.SCREENSHOT) { edit.setHint(R.string.function__screenshot_name); title = StringUtil.getString(R.string.function__set_screenshot_name); textPattern = Pattern.compile("\\S+(.*\\S+)?"); } else if (action ==PerformActionEnum.MULTI_CLICK) { edit.setHint(R.string.function__click_time); title = StringUtil.getString(R.string.function__set_click_time); textPattern = Pattern.compile("\\d{1,2}"); } else if (action ==PerformActionEnum.SLEEP_UNTIL) { edit.setHint(R.string.function__max_wait); edit.setText(R.string.default_sleep_time); title = StringUtil.getString(R.string.function__set_max_wait); textPattern = Pattern.compile("\\d+"); } else if (action == PerformActionEnum.SCROLL_TO_BOTTOM || action == PerformActionEnum.SCROLL_TO_TOP || action == PerformActionEnum.SCROLL_TO_LEFT || action == PerformActionEnum.SCROLL_TO_RIGHT) { edit.setHint(R.string.function__scroll_percent); edit.setText(R.string.default_scroll_percentage); title = StringUtil.getString(R.string.function__set_scroll_percent); textPattern = Pattern.compile("\\d+"); } else if (action == PerformActionEnum.EXECUTE_SHELL) { edit.setHint(R.string.function__adb_cmd); title = StringUtil.getString(R.string.function__set_adb_cmd); textPattern = null; } else if (action == PerformActionEnum.LONG_CLICK) { edit.setHint(R.string.function__long_press); title = StringUtil.getString(R.string.function__set_long_press); textPattern = Pattern.compile("[1-9]\\d+"); edit.setText(R.string.default_long_click_time); } else { edit.setHint(R.string.function__input_content); textPattern = null; } final AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(title) .setView(v) .setPositiveButton(R.string.function__input, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Positive " + which); String data = edit.getText().toString(); String templateText = templateEdit.getText().toString();//获得模板key // 拼装参数 method.putParam(OperationExecutor.INPUT_TEXT_KEY, data); method.putParam(OperationExecutor.INPUT_TEMPLATE_KEY,templateText);//将模板key写入到param // 隐藏Dialog dialog.dismiss(); // 抛给主线程 LauncherApplication.getInstance().runOnUiThread(new Runnable() { @Override public void run() { // 操作记录 listener.onProcessFunction(method, node); } }, 500); } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Negative " + which); dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); //点击外面区域不会让dialog消失 dialog.setCancelable(false); dialog.show(); // 校验输入 edit.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); boolean enable = true; if (textPattern != null) { String content = s.toString(); enable = textPattern.matcher(content).matches(); } // 如果不是目标状态,改变下 if (positiveButton.isEnabled() != enable) { positiveButton.setEnabled(enable); } } }); dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); } catch (Exception e) { LogUtil.e(TAG, "Throw exception: " + e.getMessage(), e); } } /** * 隐藏输入法 * @param editText */ private static void hideInput(EditText editText) { InputMethodManager inputMethodManager = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } /** * 展示WHILE编辑界面 * @param method * @param context * @param listener */ private static void showWhileView(final OperationMethod method, final Context context, final FunctionListener listener) { View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_while_setting_panel, null); final EditText edit = (EditText) v.findViewById(R.id.edit_while_param); final AppCompatSpinner spinner = (AppCompatSpinner) v.findViewById(R.id.spinner_while_mode); final TextView hint = (TextView) v.findViewById(R.id.text_while_param_hint); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { // 清理文字 edit.setText(""); edit.clearComposingText(); if (position == 0) { hint.setText(R.string.function__loop_count); edit.setHint(R.string.function__loop_count); edit.setInputType(InputType.TYPE_CLASS_NUMBER); } else { hint.setText(R.string.function__loop_condition); edit.setHint(R.string.function__loop_condition); edit.setInputType(InputType.TYPE_CLASS_TEXT); } } @Override public void onNothingSelected(AdapterView<?> parent) { } }); final AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(R.string.function__add_loop) .setView(v) .setPositiveButton(R.string.function__add, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Positive " + which); String data = edit.getText().toString(); String prefix = ""; if (spinner.getSelectedItemPosition() == 0) { prefix = LogicUtil.LOOP_PREFIX; } // 拼装参数 method.putParam(LogicUtil.CHECK_PARAM, prefix + data); // 隐藏Dialog dialog.dismiss(); // 抛给主线程 LauncherApplication.getInstance().runOnUiThread(new Runnable() { @Override public void run() { // 操作记录 listener.onProcessFunction(method, null); } }, 500); } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Negative " + which); dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); //点击外面区域不会让dialog消失 dialog.setCancelable(false); dialog.show(); dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); } /** * 展示提供的View * @param node * @param method * @param context * @param content 目标界面 */ private static void showProvidedView(final AbstractNodeTree node, final OperationMethod method, final Context context, View content, final Runnable confirmListener, final HighLightService highLightService, final FunctionListener listener) { ScrollView view = (ScrollView) LayoutInflater.from(ContextUtil.getContextThemeWrapper( context, R.style.AppDialogTheme)) .inflate(R.layout.dialog_setting, null); LinearLayout wrapper = (LinearLayout) view.findViewById(R.id.dialog_content); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); wrapper.addView(content, layoutParams); // 显示Dialog AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setView(view) .setCancelable(false) .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Positive " + which); // 隐藏Dialog dialog.dismiss(); // 如果有回调,在后台执行 if (confirmListener != null) { BackgroundExecutor.execute(new Runnable() { @Override public void run() { confirmListener.run(); listener.onProcessFunction(method, node); } }); } else { listener.onProcessFunction(method, node); } } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Negative " + which); dialog.dismiss(); listener.onCancel(); if (highLightService != null) { highLightService.removeHighLight(); } // 去除高亮 } }).create(); dialog.setTitle(null); dialog.setCanceledOnTouchOutside(false); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.show(); } /** * 展示登录信息框 * @param action * @param context */ private static void captureAndShowGesture(final PerformActionEnum action, final AbstractNodeTree target, Context context, final FunctionListener listener) { try { View v = LayoutInflater.from(ContextUtil.getContextThemeWrapper(context, R.style.AppDialogTheme)).inflate(R.layout.dialog_node_gesture, null); final GesturePadView gesturePadView = (GesturePadView) v.findViewById(R.id.node_gesture_gesture_view); final RadioGroup group = (RadioGroup) v.findViewById(R.id.node_gesture_time_filter); group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { int targetTime; if (checkedId == R.id.node_gesture_time_filter_25) { targetTime = 25; } else if (checkedId == R.id.node_gesture_time_filter_50) { targetTime = 50; } else if (checkedId == R.id.node_gesture_time_filter_200) { targetTime = 200; } else { targetTime = 100; } gesturePadView.setGestureFilter(targetTime); gesturePadView.clear(); } }); Bitmap nodeBitmap; if (target != null) { String capture = target.getCapture(); if (StringUtil.isEmpty(capture)) { File captureFile = new File(FileUtils.getSubDir("tmp"), "test.jpg"); Bitmap bitmap = capture(captureFile); if (bitmap == null) { LauncherApplication.getInstance().showToast(context.getString(R.string.action_gesture__capture_screen_failed)); listener.onCancel(); return; } Rect displayRect = target.getNodeBound(); nodeBitmap = Bitmap.createBitmap(bitmap, displayRect.left, displayRect.top, displayRect.width(), displayRect.height()); target.setCapture(BitmapUtil.bitmapToBase64(nodeBitmap)); } else { nodeBitmap = BitmapUtil.base64ToBitmap(capture); } } else { File captureFile = new File(FileUtils.getSubDir("tmp"), "test.jpg"); nodeBitmap = capture(captureFile); if (nodeBitmap == null) { LauncherApplication.getInstance().showToast(context.getString(R.string.action_gesture__capture_screen_failed)); listener.onCancel(); return; } } gesturePadView.setTargetImage(new BitmapDrawable(nodeBitmap)); AlertDialog dialog = new AlertDialog.Builder(context, R.style.AppDialogTheme) .setTitle(R.string.gesture__please_record_gesture) .setView(v) .setPositiveButton(R.string.constant__confirm, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Positive " + which); List<PointF> path = gesturePadView.getGesturePath(); int gestureFilter = gesturePadView.getGestureFilter(); // 拼装参数 // 拼装参数 OperationMethod method = new OperationMethod(action); method.putParam(OperationExecutor.GESTURE_PATH, JSON.toJSONString(path)); method.putParam(OperationExecutor.GESTURE_FILTER, Integer.toString(gestureFilter)); // 隐藏Dialog dialog.dismiss(); listener.onProcessFunction(method, target); } }).setNegativeButton(R.string.constant__cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LogUtil.i(TAG, "Negative " + which); dialog.dismiss(); listener.onCancel(); } }).create(); dialog.getWindow().setType(com.alipay.hulu.common.constant.Constant.TYPE_ALERT); dialog.setCanceledOnTouchOutside(false); //点击外面区域不会让dialog消失 dialog.setCancelable(false); dialog.show(); dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); } catch (Exception e) { LogUtil.e(TAG, "Login info dialog throw exception: " + e.getMessage(), e); } } /** * 截图 * @param captureFile 截图保留文件 * @return */ private static Bitmap capture(File captureFile) { DisplayMetrics metrics = new DisplayMetrics(); ((WindowManager) LauncherApplication.getInstance().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRealMetrics(metrics); ScreenCaptureService captureService = LauncherApplication.service(ScreenCaptureService.class); Bitmap bitmap = captureService.captureScreen(captureFile, metrics.widthPixels, metrics.heightPixels, metrics.widthPixels, metrics.heightPixels); // 原有截图方案失败 if (bitmap == null) { String path = FileUtils.getPathInShell(captureFile); CmdTools.execHighPrivilegeCmd("screencap -p \"" + path + "\""); MiscUtil.sleep(1000); bitmap = BitmapFactory.decodeFile(captureFile.getPath()); // 长宽不对 if (bitmap != null && bitmap.getWidth() != metrics.widthPixels) { bitmap = Bitmap.createScaledBitmap(bitmap, metrics.widthPixels, metrics.heightPixels, false); } } return bitmap; } /** * 回调 */ public interface FunctionListener { void onProcessFunction(OperationMethod method, AbstractNodeTree node); void onCancel(); } }
关于服务端:见下一篇博客