当前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();
}
}

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

安卓端-APPUI自动化实战【上】的更多相关文章

  1. Selenium2学习-039-WebUI自动化实战实例-文件上传下载

    通常在 WebUI 自动化测试过程中必然会涉及到文件上传的自动化测试需求,而开发在进行相应的技术实现是不同的,粗略可划分为两类:input标签类(类型为file)和非input标签类(例如:div.a ...

  2. 融云技术分享:融云安卓端IM产品的网络链路保活技术实践

    本文来自融云技术团队原创分享,原文发布于“ 融云全球互联网通信云”公众号,原题<IM 即时通讯之链路保活>,即时通讯网收录时有部分改动. 1.引言 众所周知,IM 即时通讯是一项对即时性要 ...

  3. Selenium2学习-027-WebUI自动化实战实例-025-JavaScript 在 Selenium 自动化中的应用实例之三(页面滚屏,模拟鼠标拖动滚动条)

    日常的 Web UI 自动化测试过程中,get 或 navigate 到指定的页面后,若想截图的元素或者指定区域范围不在浏览器的显示区域内,则通过截屏则无法获取相应的信息,反而浪费了无畏的图片服务器资 ...

  4. Selenium2学习-018-WebUI自动化实战实例-016-自动化脚本编写过程中的登录验证码问题

    日常的 Web 网站开发的过程中,为提升登录安全或防止用户通过脚本进行黄牛操作(宇宙最贵铁皮天朝魔都的机动车牌照竞拍中),很多网站在登录的时候,添加了验证码验证,而且验证码的实现越来越复杂,对其进行脚 ...

  5. Selenium2学习-016-WebUI自动化实战实例-014-Selenium 窗口选择

    在日常的 WebUI 自动化测试脚本编写过程中,经常需要打开新的页面,或者在多个打开的页面之间进行切换,以对页面元素进行相应的操作,以模拟用户的行为,实现 UI 的自动化测试.在过往的时间中,经常有初 ...

  6. Selenium2学习-014-WebUI自动化实战实例-012-Selenium 操作下拉列表实例-div+{js|jquery}

    之前已经讲过了 Selenium 操作 Select 实现的下拉列表:Selenium2学习-010-WebUI自动化实战实例-008-Selenium 操作下拉列表实例-Select,但是在实际的日 ...

  7. Selenium2学习-007-WebUI自动化实战实例-005-解决 Firefox 版本不兼容:org.openqa.selenium.WebDriverException: Failed to connect to binary FirefoxBinary

    此文主要讲述 Java 运行 Selenium 脚本时,因 Friefox 浏览器版本与 selenium-server-standalone-x.xx.x.jar 不兼容引起的 org.openqa ...

  8. 【转】随身HiFi 安卓OTG功能在音频上的妙用

    原文网址:http://article.pchome.net/content-1745467.html 随身HiFi 安卓OTG功能在音频上的妙用 [PChome电脑之家音频频道原创]说起Androi ...

  9. 恩布企业 IM 安卓端 1.3,服务端 1.12 公布

    恩布企业IM的 Android 安卓开源手机client EntboostIM 公布 1.3 版本号.同一时候恩布IM服务端更新至 1.12 版本号; 安卓端主要更新内容: 添加收发手机文件功能: 登 ...

  10. JAVA 后台SSM框架接收安卓端的json数据

    最近项目上与安卓端做JSON数据交互,使用的SSM框架,刚开始的时候感觉很简单,想着不就是把安卓端的JSON数据封装为Bean类对象吗? 于是就这样写了 可是这样一直报400,百度原因是因为请求url ...

随机推荐

  1. Go 调用系统默认浏览器打开链接

    Go Package 相关包 os/exec 实例 调用Windows系统默认浏览器打开链接 package main import ( "fmt" "os/exec&q ...

  2. unigui如何直接显示一个PDF文件【13】

    这个问题有点搞笑. PDF.js v1.9.426 (build: 2558a58d) 信息:Unexpected server response (204) while retrieving PDF ...

  3. 关于er图的几个工具

    建立数据库包括其他的er图,这个太重要了.因为这关于效率和清晰思路. 但是目前感觉好用的还是ER/Studio.如果不差银子还是建议用这一款.真的好方便. 1.正向逆向工程非常顺利和快捷. 2.物理模 ...

  4. study Rust-4【所有权】这个太重要了!

    由于Rust内存垃圾自动回收,那就得搞清楚这个所有权玩意.这个太重要了.因为关系到贯穿于你以后的程序编写. 几个概念: 一.移动 1.咱们一般语言,自己申请内存,自己管理和释放.就是new和free. ...

  5. xe10.3+paserver在Ubuntu下运行错误

    xe.3的paserver在Ubuntu下执行呈现乱七八糟的错误提示. 原因:Ubuntu的版本和paserver编译的环境不一致. 注意:使用ARM64的版本.如ubuntu-18.04.2-des ...

  6. exe4j工具使用-jar包转exe可执行文件

    exe4j介绍 exe4j可以将java打包的jar包转为exe可执行文件,实现在没有jdk环境下运行jar包. 下载链接 https://pan.baidu.com/s/1sfEJyxPABmhsl ...

  7. Oracle的listagg函数(多行按顺序合并字符串)(与wm_concat的区别)

    场景: 使用wm_concat函数时,会发现无法对其拼接的字符串进行排序 使用listagg函数可实现按排序进行字符串拼接 select myGroup, listagg(myStr, ',') wi ...

  8. Android编译时动态插入代码原理与实践

    本文同步发布于公众号:移动开发那些事:Android编译时动态插入代码原理与实践 Android开发中,编译时动态插入代码是一种高效,并且对业务逻辑是低侵入性的方案,常用于增加通用的埋点能力,或者插入 ...

  9. DP刷题总结-2

    同步于Luogu blog T1 AT_joisc2007_buildi ビルの飾り付け (Building) 简化题意 最长上升子序列模板 分析 \(O(n^2)\)做法 考虑DP 定义状态:\(d ...

  10. 【HUST】网安|操作系统实验|实验一 内核编译、系统调用、编写批处理脚本

    文章目录 目的 任务 前言 一.linux内核编译 非常靠谱的两篇参考文章: 补注: 总结 二.添加新的系统调用 特别靠谱的参考文章: 补注: 1. 我修改的文件: 2. 图中需要敲入的全部代码: 3 ...