init
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
package dji.sampleV5.aircraft.keyvalue
|
||||
|
||||
|
||||
import dji.sdk.keyvalue.key.ComponentType
|
||||
import dji.v5.manager.capability.CapabilityManager
|
||||
import dji.v5.manager.capability.CapabilityParser
|
||||
import dji.v5.utils.common.*
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.CompletableObserver
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import java.lang.Exception
|
||||
import java.lang.StringBuilder
|
||||
import dji.v5.ux.core.util.ToastUtils
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/09/13 5:43 下午
|
||||
* @description: 能力集key 测试,结果中只展示未通过的key
|
||||
* 结果存储在包目录/keycheck/result.txt
|
||||
*/
|
||||
object CapabilityKeyChecker {
|
||||
|
||||
|
||||
private val TAG = LogUtils.getTag(this)
|
||||
|
||||
|
||||
/**
|
||||
* 根据key名称从能力集获取测试用例json集合 每个对象可反序列化为key的参数对象
|
||||
*/
|
||||
fun getKeyParamList(keyName: String): MutableList<String> {
|
||||
return CapabilityParser.getInstance().getValueParamList(keyName)
|
||||
}
|
||||
|
||||
|
||||
fun getKeyItem(keyName: String): KeyItem<*, *>? {
|
||||
val allList: MutableList<KeyItem<*, *>> = ArrayList()
|
||||
var item: KeyItem<*, *>? = null
|
||||
KeyItemDataUtil.getAllKeyList(allList)
|
||||
allList.forEach {
|
||||
if (it.toString() == keyName) {
|
||||
item = it;
|
||||
}
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
data class ItemDecoder(
|
||||
var componetIndex: Int = 0, // LEFT_OR_MAIN
|
||||
var subComponetType: Int = 65534, // DEFAULT
|
||||
var subComponetIndex: Int = 0,
|
||||
var jsonString: String
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取枚举对应的Json list字符串
|
||||
* eg:CameraMode 对象 obj cameramodeMsg field[obj] CameraMode
|
||||
*/
|
||||
fun getDJIValueBeanStr(item: KeyItem<*, *>): String {
|
||||
var tagBegin = "{\"valueParamList\": ["
|
||||
var tagEnd = "]}"
|
||||
try {
|
||||
val pFields = item.param?.javaClass?.declaredFields
|
||||
if (pFields != null) {
|
||||
for (field in pFields) {
|
||||
|
||||
field.isAccessible = true
|
||||
val clazz = field.type
|
||||
if (clazz.isEnum) {
|
||||
val itemList =
|
||||
(item.subItemMap as Map<String?, List<EnumItem>>)[clazz.canonicalName]!!
|
||||
var jsonList = StringBuilder(tagBegin)
|
||||
itemList.forEach {
|
||||
field[item.param] = KeyItemHelper.getEnumData(
|
||||
clazz as Class<Enum<*>>,
|
||||
it.getName().toString()
|
||||
)
|
||||
var jsonString =
|
||||
"\"" + item.param.toString().replace("\"", "\\\"") + "\""
|
||||
|
||||
var result = jsonString + ","
|
||||
if (!result.contains("65535")) {//过滤unknown
|
||||
jsonList.append(result)
|
||||
}
|
||||
}
|
||||
jsonList.deleteAt(jsonList.lastIndex)
|
||||
jsonList.append(tagEnd)
|
||||
return jsonList.toString()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.e(KeyItemHelper.TAG, e.message)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据item生成 枚举类型的json文件
|
||||
*/
|
||||
fun generateAllEnumList(productType: String) {
|
||||
val allList: MutableList<KeyItem<*, *>> = ArrayList()
|
||||
DJIExecutor.getExecutorFor(DJIExecutor.Purpose.IO).execute {
|
||||
KeyItemDataUtil.getAllKeyList(allList)
|
||||
allList
|
||||
.filter {
|
||||
val keyName = "Key$it"
|
||||
it.canSet()
|
||||
&& CapabilityManager.getInstance().isKeySupported(
|
||||
productType,
|
||||
"",
|
||||
ComponentType.find(it.getKeyInfo().componentType),
|
||||
keyName
|
||||
)
|
||||
|
||||
}
|
||||
.forEach() { item ->
|
||||
val jsonStr = getDJIValueBeanStr(item)
|
||||
if (jsonStr.isNotEmpty()) {
|
||||
var filePath = DiskUtil.getExternalCacheDirPath(
|
||||
ContextUtil.getContext(),
|
||||
"keycheck/$item.json"
|
||||
)
|
||||
FileUtils.writeFile(filePath, jsonStr, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkOneType(
|
||||
productType: String,
|
||||
componentTypeName: String,
|
||||
keyCheckType: KeyCheckType,
|
||||
componentIndex: Int
|
||||
): Completable {
|
||||
|
||||
var keyOperatorCommand = when (keyCheckType) {
|
||||
KeyCheckType.SET -> KeySetCommand(productType, componentTypeName, componentIndex)
|
||||
KeyCheckType.ACTION -> KeyActionCommand(productType, componentTypeName, componentIndex)
|
||||
KeyCheckType.GET -> KeyGetCommand(productType, componentTypeName, componentIndex)
|
||||
}
|
||||
return keyOperatorCommand.execute()
|
||||
}
|
||||
|
||||
fun check(
|
||||
productType: String,
|
||||
componentTypeName: String,
|
||||
componentIndex: Int
|
||||
) {
|
||||
checkOneType(productType, componentTypeName, KeyCheckType.SET, componentIndex)
|
||||
.andThen(checkOneType(productType, componentTypeName, KeyCheckType.SET, componentIndex))
|
||||
.andThen(checkOneType(productType, componentTypeName, KeyCheckType.ACTION, componentIndex))
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : CompletableObserver {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
LogUtils.e(TAG, "begin check")
|
||||
ToastUtils.showToast("begin check")
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
LogUtils.e(TAG, "-check finish-")
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
LogUtils.e(TAG, "check error${e.message}")
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package dji.sampleV5.aircraft.keyvalue
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/03/11 9:42 上午
|
||||
* @description:
|
||||
*/
|
||||
enum class ChannelType(name: String) {
|
||||
/**
|
||||
* 电池
|
||||
*/
|
||||
CHANNEL_TYPE_BATTERY("BATTERY"),
|
||||
|
||||
/**
|
||||
* 云台
|
||||
*/
|
||||
CHANNEL_TYPE_GIMBAL("GIMBAL"),
|
||||
|
||||
/**
|
||||
* 相机
|
||||
*/
|
||||
CHANNEL_TYPE_CAMERA("CAMERA"),
|
||||
|
||||
|
||||
/**
|
||||
* Airlink
|
||||
*/
|
||||
CHANNEL_TYPE_AIRLINK("AIRLINK"),
|
||||
|
||||
/**
|
||||
* Flight Assistant
|
||||
*/
|
||||
CHANNEL_TYPE_FLIGHT_ASSISTANT("ASSISTANT"),
|
||||
|
||||
/**
|
||||
* Flight Control
|
||||
*/
|
||||
CHANNEL_TYPE_FLIGHT_CONTROL("FLIGHT CONTROL"),
|
||||
|
||||
/**
|
||||
* Remote Controller
|
||||
*/
|
||||
CHANNEL_TYPE_REMOTE_CONTROLLER("REMOTE CONTROLLER"),
|
||||
|
||||
/**
|
||||
* BLE
|
||||
*/
|
||||
CHANNEL_TYPE_BLE("BLE"),
|
||||
|
||||
/**
|
||||
* RTK
|
||||
*/
|
||||
CHANNEL_TYPE_RTK_BASE_STATION("RTK BASE STATION"),
|
||||
|
||||
/**
|
||||
* RTK
|
||||
*/
|
||||
CHANNEL_TYPE_RTK_MOBILE_STATION("RTK MOBILE STATION"),
|
||||
|
||||
/**
|
||||
* Product
|
||||
*/
|
||||
CHANNEL_TYPE_PRODUCT("PRODUCT"),
|
||||
|
||||
/**
|
||||
* OcuSync
|
||||
*/
|
||||
CHANNEL_TYPE_OCU_SYNC("OCU SYNC"),
|
||||
|
||||
/**
|
||||
* Radar
|
||||
*/
|
||||
CHANNEL_TYPE_RADAR("RADAR"),
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Mobile Network
|
||||
*/
|
||||
CHANNEL_TYPE_MOBILE_NETWORK("MOBILE NETWORK"),
|
||||
|
||||
|
||||
/**
|
||||
* on board
|
||||
*/
|
||||
CHANNEL_TYPE_ON_BOARD("BOARD"),
|
||||
|
||||
/**
|
||||
* Payload
|
||||
*/
|
||||
CHANNEL_TYPE_ON_PAYLOAD("PAYLOAD"),
|
||||
|
||||
/**
|
||||
* lidar
|
||||
*/
|
||||
CHANNEL_TYPE_LIDAR("LIDAR"),
|
||||
|
||||
/**
|
||||
* IntelligentBox
|
||||
*/
|
||||
INTELLIGENT_BOX("INTELLIGENT BOX");
|
||||
|
||||
private val value: String
|
||||
override fun toString(): String {
|
||||
return value
|
||||
}
|
||||
|
||||
init {
|
||||
value = name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package dji.sampleV5.aircraft.keyvalue
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/03/10 9:58 上午
|
||||
* @description:
|
||||
*/
|
||||
class EnumItem : Serializable {
|
||||
/**
|
||||
* 需要显示的条目描述
|
||||
*/
|
||||
private var description: String? = null
|
||||
|
||||
/**
|
||||
* SDK对应的枚举
|
||||
*/
|
||||
private var name: String? = null
|
||||
|
||||
/**
|
||||
* 是否选中
|
||||
*/
|
||||
private var selected = false
|
||||
fun getName(): String? {
|
||||
return name
|
||||
}
|
||||
|
||||
fun setName(name: String?) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
fun getDescription(): String? {
|
||||
return description
|
||||
}
|
||||
|
||||
fun setDescription(description: String?) {
|
||||
this.description = description
|
||||
}
|
||||
|
||||
fun isSelected(): Boolean {
|
||||
return selected
|
||||
}
|
||||
|
||||
fun setSelected(selected: Boolean) {
|
||||
this.selected = selected
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = 876323262645176354L
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package dji.sampleV5.aircraft.keyvalue
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/10/26 2:25 下午
|
||||
* @description:
|
||||
*/
|
||||
class KeyActionCommand(
|
||||
private val productType: String,
|
||||
private val componentTypeName: String,
|
||||
private val componentIndex: Int
|
||||
) : KeyOperatorCommand(productType, componentTypeName, componentIndex) {
|
||||
|
||||
private val TAG_GET = "【ACTION】"
|
||||
private val TAG_ERROR = "ActionErrorMsg"
|
||||
|
||||
override fun filter(item: KeyItem<*, *>): Boolean {
|
||||
return item.canAction()
|
||||
}
|
||||
|
||||
override fun run(item: KeyItem<*, *>) {
|
||||
super.doKeyParam(item, KeyCheckType.ACTION)
|
||||
}
|
||||
|
||||
override fun getTAG(): String {
|
||||
return TAG_GET
|
||||
}
|
||||
|
||||
override fun getErrorTAG(): String {
|
||||
return TAG_ERROR
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package dji.sampleV5.aircraft.keyvalue;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import dji.sdk.keyvalue.key.DJIActionKeyInfo;
|
||||
import dji.sdk.keyvalue.key.DJIKey;
|
||||
import dji.sdk.keyvalue.key.DJIKeyInfo;
|
||||
import dji.sdk.keyvalue.key.KeyTools;
|
||||
import dji.v5.common.callback.CommonCallbacks;
|
||||
import dji.v5.manager.KeyManager;
|
||||
import dji.v5.utils.common.LogUtils;
|
||||
|
||||
|
||||
public class KeyBaseStructure<P, R> {
|
||||
|
||||
private static final String TAG = KeyBaseStructure.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* 设置参数
|
||||
*/
|
||||
protected P param;
|
||||
|
||||
/**
|
||||
* 返回结果
|
||||
*/
|
||||
protected R result;
|
||||
|
||||
/**
|
||||
* 推送数据记录
|
||||
*/
|
||||
protected String listenRecord = "";
|
||||
|
||||
/**
|
||||
* 推送Listener宿主
|
||||
*/
|
||||
protected Object listenHolder;
|
||||
|
||||
public int getComponetIndex() {
|
||||
return componetIndex;
|
||||
}
|
||||
|
||||
public void setComponetIndex(int componetIndex) {
|
||||
this.componetIndex = componetIndex;
|
||||
}
|
||||
|
||||
public int getSubComponetType() {
|
||||
return subComponetType;
|
||||
}
|
||||
|
||||
public void setSubComponetType(int subComponetType) {
|
||||
this.subComponetType = subComponetType;
|
||||
}
|
||||
|
||||
public int getSubComponetIndex() {
|
||||
return subComponetIndex;
|
||||
}
|
||||
|
||||
public void setSubComponetIndex(int subComponetIndex) {
|
||||
this.subComponetIndex = subComponetIndex;
|
||||
}
|
||||
|
||||
protected int componetIndex = -1;
|
||||
|
||||
protected int subComponetType = -1;
|
||||
|
||||
protected int subComponetIndex = -1;
|
||||
|
||||
|
||||
/**
|
||||
* 枚举列表
|
||||
*/
|
||||
protected Map<String, List<EnumItem>> subItemMap = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* 通过反射获取泛型类型数据并实例化
|
||||
*/
|
||||
protected void initGenericInstance() {
|
||||
try {
|
||||
KeyItemHelper.INSTANCE.initClassData(param);
|
||||
KeyItemHelper.INSTANCE.initClassData(result);
|
||||
initSubItemData();
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG ,e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果为枚举或者枚举嵌套列表,则初始化该列表数据
|
||||
*/
|
||||
protected void initSubItemData() {
|
||||
if (param == null) {
|
||||
return;
|
||||
}
|
||||
subItemMap.clear();
|
||||
subItemMap.putAll(KeyItemHelper.INSTANCE.initSubItemData(param));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取需要设置的参数实例
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public P getParam() {
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数映射列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, List<EnumItem>> getSubItemMap() {
|
||||
return subItemMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推送数据记录
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getListenRecord() {
|
||||
return listenRecord;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 异步get
|
||||
*
|
||||
* @param keyInfo
|
||||
* @param getCallback
|
||||
*/
|
||||
protected void get(DJIKeyInfo<R> keyInfo, CommonCallbacks.CompletionCallbackWithParam<R> getCallback) {
|
||||
DJIKey<R> key = createKey(keyInfo);
|
||||
KeyManager.getInstance().getValue(key, getCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步get
|
||||
*
|
||||
* @param keyInfo
|
||||
* @return
|
||||
*/
|
||||
protected R get(DJIKeyInfo<R> keyInfo) {
|
||||
DJIKey<R> key = createKey(keyInfo);
|
||||
return KeyManager.getInstance().getValue(key);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性
|
||||
*
|
||||
* @param keyInfo
|
||||
* @param setCallback
|
||||
*/
|
||||
protected void set(DJIKeyInfo<P> keyInfo, P param, CommonCallbacks.CompletionCallback setCallback) {
|
||||
|
||||
DJIKey<P> key = createKey(keyInfo);
|
||||
KeyManager.getInstance().setValue(key, param, setCallback);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 设置Listener
|
||||
*
|
||||
* @param keyInfo
|
||||
* @param listenHolder
|
||||
* @param listenCallback
|
||||
*/
|
||||
protected void listen(DJIKeyInfo<R> keyInfo, Object listenHolder, CommonCallbacks.KeyListener<R> listenCallback) {
|
||||
this.listenHolder = listenHolder;
|
||||
|
||||
DJIKey<R> key = createKey(keyInfo);
|
||||
KeyManager.getInstance().listen(key, listenHolder, listenCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消Listener
|
||||
*
|
||||
* @param keyInfo
|
||||
* @param listenHolder
|
||||
*/
|
||||
protected void cancelListen(DJIKeyInfo<R> keyInfo, Object listenHolder) {
|
||||
|
||||
KeyManager.getInstance().cancelListen(createKey(keyInfo), listenHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带参action
|
||||
*
|
||||
* @param keyInfo
|
||||
* @param param
|
||||
* @param actonCallback
|
||||
*/
|
||||
protected void action(DJIActionKeyInfo<P, R> keyInfo, P param, CommonCallbacks.CompletionCallbackWithParam<R> actonCallback) {
|
||||
|
||||
DJIKey.ActionKey<P,R> key = createActionKey(keyInfo);
|
||||
KeyManager.getInstance().performAction(key, param, actonCallback);
|
||||
}
|
||||
|
||||
protected DJIKey.ActionKey<P,R> createActionKey(DJIActionKeyInfo<P,R> keyInfo) {
|
||||
DJIKey.ActionKey<P,R> key = null;
|
||||
key = KeyTools.createKey(keyInfo, 0, getComponetIndex(),getSubComponetType(), getSubComponetIndex());
|
||||
return key;
|
||||
}
|
||||
|
||||
protected<Parame> DJIKey<Parame> createKey(DJIKeyInfo<Parame> keyInfo ) {
|
||||
DJIKey<Parame> key = null;
|
||||
key = KeyTools.createKey(keyInfo, 0 , getComponetIndex(),getSubComponetType(), getSubComponetIndex());
|
||||
return key;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package dji.sampleV5.aircraft.keyvalue
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/10/26 2:09 下午
|
||||
* @description:
|
||||
*/
|
||||
enum class KeyCheckType {
|
||||
GET,
|
||||
SET,
|
||||
ACTION
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package dji.sampleV5.aircraft.keyvalue
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/10/26 2:25 下午
|
||||
* @description:
|
||||
*/
|
||||
class KeyGetCommand(
|
||||
private val productType: String,
|
||||
private val componentTypeName: String,
|
||||
private val componentIndex: Int
|
||||
) : KeyOperatorCommand(productType, componentTypeName, componentIndex) {
|
||||
|
||||
private val TAG_GET = "【GET】"
|
||||
private val TAG_ERROR = "GetErrorMsg"
|
||||
|
||||
override fun filter(item: KeyItem<*, *>): Boolean {
|
||||
return item.canGet()
|
||||
}
|
||||
|
||||
override fun run(item: KeyItem<*, *>) {
|
||||
super.doKeyParam(item, KeyCheckType.GET)
|
||||
}
|
||||
|
||||
override fun getTAG(): String {
|
||||
return TAG_GET
|
||||
}
|
||||
|
||||
override fun getErrorTAG(): String {
|
||||
return TAG_ERROR
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
415
sample/src/main/java/dji/sampleV5/aircraft/keyvalue/KeyItem.java
Normal file
415
sample/src/main/java/dji/sampleV5/aircraft/keyvalue/KeyItem.java
Normal file
@@ -0,0 +1,415 @@
|
||||
package dji.sampleV5.aircraft.keyvalue;
|
||||
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import dji.v5.ux.core.util.ToastUtils;
|
||||
import dji.sampleV5.aircraft.util.Util;
|
||||
import dji.sdk.keyvalue.converter.DJIValueConverter;
|
||||
import dji.sdk.keyvalue.converter.EmptyValueConverter;
|
||||
import dji.sdk.keyvalue.converter.SingleValueConverter;
|
||||
import dji.sdk.keyvalue.key.DJIActionKeyInfo;
|
||||
import dji.sdk.keyvalue.key.DJIKeyInfo;
|
||||
import dji.sdk.keyvalue.value.base.DJIValue;
|
||||
import dji.sdk.keyvalue.value.common.EmptyMsg;
|
||||
import dji.v5.common.callback.CommonCallbacks;
|
||||
import dji.v5.common.error.IDJIError;
|
||||
import dji.v5.utils.common.LogUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* KeyItem作为key能力和动作的载体来进行封装
|
||||
*/
|
||||
|
||||
public class KeyItem<P, R> extends KeyBaseStructure<P , R> implements Comparable<KeyItem<?,?>>{
|
||||
|
||||
private static final String TAG = KeyItem.class.getSimpleName();
|
||||
public KeyItem(DJIKeyInfo<?> keyInfo) {
|
||||
super();
|
||||
this.keyInfo = (DJIKeyInfo<R>)keyInfo;
|
||||
this.keyInfoSet = (DJIKeyInfo<P>)keyInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性展示名
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(long count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用次数,用户排序
|
||||
*/
|
||||
private long count;
|
||||
public boolean isSingleDJIValue;
|
||||
|
||||
/**
|
||||
* 参数key的能力携带实体
|
||||
*/
|
||||
protected DJIKeyInfo<R> keyInfo;
|
||||
|
||||
protected DJIKeyInfo<P> keyInfoSet;
|
||||
|
||||
|
||||
/**
|
||||
* 需要调用者注入的回调接口,用于结果通知
|
||||
*/
|
||||
protected KeyItemActionListener<Object> keyOperateCallBack;
|
||||
private boolean isItemSelected ;
|
||||
public boolean isItemSelected() {
|
||||
return isItemSelected;
|
||||
}
|
||||
|
||||
public void setItemSelected(boolean itemSelected) {
|
||||
isItemSelected = itemSelected;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 推送数据回调(需要调用者注入)
|
||||
*/
|
||||
protected KeyItemActionListener<String> pushCallBack;
|
||||
|
||||
public String getName() {
|
||||
return Util.isBlank(name) ? keyInfo.getIdentifier() : name;
|
||||
}
|
||||
public void setName(String name){
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public DJIKeyInfo<P> getKeyInfo() {
|
||||
return keyInfoSet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取listen宿主
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Object getListenHolder() {
|
||||
return listenHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以Get
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean canGet() {
|
||||
return keyInfo.isCanGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以Set
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean canSet() {
|
||||
return keyInfo.isCanSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以Listen
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean canListen() {
|
||||
return keyInfo.isCanListen();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为action
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean canAction() {
|
||||
return keyInfo.isCanPerformAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要调用者注入的回调接口,用于结果通知
|
||||
*
|
||||
* @param keyOperateCallBack
|
||||
*/
|
||||
public void setKeyOperateCallBack(KeyItemActionListener<Object> keyOperateCallBack) {
|
||||
this.keyOperateCallBack = keyOperateCallBack;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送回调
|
||||
*
|
||||
* @param pushCallBack
|
||||
*/
|
||||
public void setPushCallBack(KeyItemActionListener<String> pushCallBack) {
|
||||
this.pushCallBack = pushCallBack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get请求
|
||||
*/
|
||||
public void doGet() {
|
||||
try {
|
||||
|
||||
get(keyInfo, new CommonCallbacks.CompletionCallbackWithParam<R>() {
|
||||
@Override
|
||||
public void onSuccess(R data) {
|
||||
|
||||
if (keyOperateCallBack != null && data != null) {
|
||||
keyOperateCallBack.actionChange(getName()+"【GET】 == success " + data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
if (keyOperateCallBack != null) {
|
||||
keyOperateCallBack.actionChange(getName() + "【GET】 GetErrorMsg ==" + error.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG ,e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncGet请求
|
||||
*/
|
||||
public void doSyncGet() {
|
||||
try {
|
||||
DJIValue getResult = (DJIValue) get(keyInfo);
|
||||
if(keyOperateCallBack != null){
|
||||
keyOperateCallBack.actionChange(null == getResult ? "" : getResult.toJson());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG ,e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set请求
|
||||
*/
|
||||
public void doSet(String jsonStr) {
|
||||
try {
|
||||
final P p = validPrams(jsonStr);
|
||||
if (p == null) {
|
||||
keyOperateCallBack.actionChange(getName() + "【SET】SetErrorMsg== json error");
|
||||
return;
|
||||
}
|
||||
|
||||
set(keyInfoSet, p, new CommonCallbacks.CompletionCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LogUtils.e(TAG, "set success : " + getName());
|
||||
if (keyOperateCallBack != null) {
|
||||
// 保存上一次设置成功的对象,下次set时可使用保存过的对象 序列化json
|
||||
if (getKeyInfo().getTypeConverter() instanceof DJIValueConverter) {
|
||||
param = p;
|
||||
}
|
||||
keyOperateCallBack.actionChange(getName() + "【SET】==" + p.toString() + " | " + " success");
|
||||
}
|
||||
ToastUtils.INSTANCE.showToast("set " + p.getClass().getSimpleName() + " success");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
LogUtils.e(TAG, "set error : " + error);
|
||||
if (keyOperateCallBack != null) {
|
||||
keyOperateCallBack.actionChange(getName() + "【SET】SetErrorMsg== " + p.toString() + "|" + error.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
ToastUtils.INSTANCE.showToast("输入参数出错");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* action请求
|
||||
*/
|
||||
public void doAction(String jsonStr) {
|
||||
|
||||
DJIActionKeyInfo<P,R> actionKeyInfo = (DJIActionKeyInfo<P,R>) keyInfo;
|
||||
P p = null;
|
||||
if(jsonStr != null && !jsonStr.isEmpty()){
|
||||
p = validPrams(jsonStr);
|
||||
}
|
||||
if (p == null && actionKeyInfo.getTypeConverter()!= EmptyValueConverter.converter) {
|
||||
return;
|
||||
}
|
||||
|
||||
P pRes = p;
|
||||
action(actionKeyInfo, p, new CommonCallbacks.CompletionCallbackWithParam<R>() {
|
||||
@Override
|
||||
public void onSuccess(Object data) {
|
||||
if (keyOperateCallBack != null) {
|
||||
if (data != null && !(data instanceof EmptyMsg)) {
|
||||
keyOperateCallBack.actionChange(getName() + "【ACTION】== " + getActionTipsStr(pRes) + " success: " + data.toString());
|
||||
} else {
|
||||
keyOperateCallBack.actionChange(getName() + "【ACTION】== " + getActionTipsStr(pRes) + " result: success");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull IDJIError error) {
|
||||
if (keyOperateCallBack != null) {
|
||||
keyOperateCallBack.actionChange(getName() +"【ACTION】 ActionErrorMsg==" + error.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getActionTipsStr(P pRes){
|
||||
return pRes == null ? "" : pRes.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送回调
|
||||
*/
|
||||
private CommonCallbacks.KeyListener<R> listenSDKCallback =
|
||||
( oldValue ,newValue) -> {
|
||||
StringBuffer sb = new StringBuffer("【LISTEN】");
|
||||
sb.append(getName());
|
||||
sb.append(" result:");
|
||||
sb.append("oldValue:").append(oldValue);
|
||||
sb.append(" newValue:").append(newValue);
|
||||
|
||||
if (pushCallBack != null) {
|
||||
pushCallBack.actionChange(sb.toString());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 注册Listen(新接口)
|
||||
*
|
||||
* @param listenHolder
|
||||
*/
|
||||
public void listen(Object listenHolder) {
|
||||
listen(keyInfo, listenHolder, listenSDKCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消Listen(新接口)
|
||||
*
|
||||
* @param listenHolder
|
||||
*/
|
||||
public void cancelListen(Object listenHolder) {
|
||||
if (this.listenHolder == listenHolder) {
|
||||
this.listenHolder = null;
|
||||
cancelListen(keyInfo, listenHolder);
|
||||
pushCallBack = null;
|
||||
listenRecord = "";
|
||||
}
|
||||
//listenSDKCallback = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证参数
|
||||
*
|
||||
* @param jsonStr
|
||||
* @return
|
||||
*/
|
||||
private P validPrams(String jsonStr) {
|
||||
if (Util.isBlank(jsonStr)) {
|
||||
ToastUtils.INSTANCE.showToast("请先设置参数");
|
||||
return null;
|
||||
}
|
||||
final P p = buildParamFromJsonStr(jsonStr);
|
||||
if (p == null) {
|
||||
ToastUtils.INSTANCE.showToast("请先设置" + jsonStr + " 参数");
|
||||
return null;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化:根据JSON串获取对象
|
||||
*
|
||||
* @param jsonStr
|
||||
* @return
|
||||
*/
|
||||
public P buildParamFromJsonStr(String jsonStr) {
|
||||
P p;
|
||||
if (keyInfo.getTypeConverter() instanceof SingleValueConverter && !isSingleDJIValue) {
|
||||
p = (P) keyInfo.getTypeConverter().fromStr(getSingleJsonValue(jsonStr));
|
||||
} else {
|
||||
p = (P) keyInfo.getTypeConverter().fromStr(jsonStr);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SingleValue 中原始类型包装类的value值
|
||||
* @param jsonStr
|
||||
* @return
|
||||
*/
|
||||
private String getSingleJsonValue(String jsonStr) {
|
||||
String value = "";
|
||||
try {
|
||||
JSONObject jsonObj = new JSONObject(jsonStr);
|
||||
value = jsonObj.getString("value");
|
||||
}catch (Exception e) {
|
||||
LogUtils.e(TAG ,e.getMessage());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化:获取默认JSON串
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getParamJsonStr() {
|
||||
String jsonStr = null;
|
||||
try {
|
||||
jsonStr = param.toString();
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG ,e.getMessage());
|
||||
}
|
||||
return jsonStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除回调
|
||||
*/
|
||||
public void removeCallBack() {
|
||||
cancelListen(listenHolder);
|
||||
keyOperateCallBack = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(KeyItem keyItem) {
|
||||
if (keyItem.count - this.count > 0) {
|
||||
return 1;
|
||||
} else if (keyItem.count - this.count < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isSingleDJIValue() {
|
||||
return isSingleDJIValue;
|
||||
}
|
||||
|
||||
public void setSingleDJIValue(boolean singleDJIValue) {
|
||||
isSingleDJIValue = singleDJIValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return Util.isBlank(name) ? keyInfo.getIdentifier() : name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package dji.sampleV5.aircraft.keyvalue;
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/03/11 9:55 上午
|
||||
* @description:
|
||||
*/
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
*/
|
||||
public interface KeyItemActionListener<T> {
|
||||
|
||||
void actionChange(@Nullable T t);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package dji.sampleV5.aircraft.keyvalue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.collection.SparseArrayCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import dji.sampleV5.aircraft.R;
|
||||
|
||||
|
||||
public class KeyItemAdapter extends RecyclerView.Adapter<KeyItemAdapter.ComViewHolder> implements Filterable {
|
||||
|
||||
private KeyItemActionListener<KeyItem<?, ?>> callback;
|
||||
|
||||
protected List<KeyItem<?, ?>> dataList;
|
||||
protected List<KeyItem<?, ?>> mFilterList;
|
||||
protected Context context;
|
||||
|
||||
|
||||
public KeyItemAdapter(Context context, List<KeyItem<?, ?>> dataList, KeyItemActionListener<KeyItem<?, ?>> callback) {
|
||||
this.context = context;
|
||||
this.dataList = dataList;
|
||||
this.mFilterList = dataList;
|
||||
this.callback = callback;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new ComViewHolder(LayoutInflater.from(context).inflate(R.layout.item_camera_key_list, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ComViewHolder holder, int position) {
|
||||
if (mFilterList.size() <= position) {
|
||||
return;
|
||||
}
|
||||
convert(holder, mFilterList.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mFilterList == null ? 0 : mFilterList.size();
|
||||
}
|
||||
|
||||
|
||||
public void convert(ComViewHolder viewHolder, final KeyItem<?, ?> keyItem) {
|
||||
if (viewHolder == null || keyItem == null) {
|
||||
return;
|
||||
}
|
||||
TextView textView = viewHolder.getView(R.id.tv_item_name);
|
||||
textView.setText(keyItem.getName());
|
||||
if (keyItem.isItemSelected()) {
|
||||
textView.setBackgroundColor(Color.GRAY);
|
||||
} else {
|
||||
textView.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
textView.setOnClickListener(v -> {
|
||||
if (callback != null) {
|
||||
callback.actionChange(keyItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence charSequence) {
|
||||
String charString = charSequence.toString();
|
||||
if (charString.isEmpty()) {
|
||||
mFilterList = dataList;
|
||||
} else {
|
||||
List<KeyItem<?, ?>> filteredList = new ArrayList<>();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int i = 0, length = charSequence.length(); i < length; i++) {
|
||||
if (stringBuilder.length() > 0) {
|
||||
stringBuilder.append("+.*");
|
||||
}
|
||||
stringBuilder.append(charSequence.charAt(i));
|
||||
}
|
||||
Pattern pattern = Pattern.compile(stringBuilder.toString(), Pattern.CASE_INSENSITIVE);
|
||||
for (KeyItem<?, ?> item : dataList) {
|
||||
if (pattern.matcher(item.keyInfo.getIdentifier()).find()) {
|
||||
filteredList.add(item);
|
||||
}
|
||||
|
||||
}
|
||||
mFilterList = filteredList;
|
||||
}
|
||||
|
||||
FilterResults filterResults = new FilterResults();
|
||||
filterResults.values = mFilterList;
|
||||
return filterResults;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
|
||||
mFilterList = (ArrayList<KeyItem<?, ?>>) filterResults.values;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 缓存容器
|
||||
*/
|
||||
public class ComViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private View convertView;
|
||||
private SparseArrayCompat<View> views;
|
||||
|
||||
public ComViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.convertView = itemView;
|
||||
this.views = new SparseArrayCompat<>();
|
||||
}
|
||||
|
||||
|
||||
public <T extends View> T getView(int layoutId) {
|
||||
View view = views.get(layoutId);
|
||||
if (view == null) {
|
||||
view = convertView.findViewById(layoutId);
|
||||
views.put(layoutId, view);
|
||||
}
|
||||
return (T) view;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package dji.sampleV5.aircraft.keyvalue;
|
||||
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dji.sdk.keyvalue.converter.DJIValueConverter;
|
||||
import dji.sdk.keyvalue.converter.IDJIValueConverter;
|
||||
import dji.sdk.keyvalue.converter.SingleValueConverter;
|
||||
import dji.sdk.keyvalue.key.AIBoxKey;
|
||||
import dji.sdk.keyvalue.key.AirLinkKey;
|
||||
import dji.sdk.keyvalue.key.BatteryKey;
|
||||
import dji.sdk.keyvalue.key.BleKey;
|
||||
import dji.sdk.keyvalue.key.CameraKey;
|
||||
import dji.sdk.keyvalue.key.DJIKeyInfo;
|
||||
import dji.sdk.keyvalue.key.FlightAssistantKey;
|
||||
import dji.sdk.keyvalue.key.FlightControllerKey;
|
||||
import dji.sdk.keyvalue.key.GimbalKey;
|
||||
import dji.sdk.keyvalue.key.LidarKey;
|
||||
import dji.sdk.keyvalue.key.MobileNetworkKey;
|
||||
import dji.sdk.keyvalue.key.MobileNetworkLinkRCKey;
|
||||
import dji.sdk.keyvalue.key.OcuSyncKey;
|
||||
import dji.sdk.keyvalue.key.OnboardKey;
|
||||
import dji.sdk.keyvalue.key.PayloadKey;
|
||||
import dji.sdk.keyvalue.key.ProductKey;
|
||||
import dji.sdk.keyvalue.key.RadarKey;
|
||||
import dji.sdk.keyvalue.key.RemoteControllerKey;
|
||||
import dji.sdk.keyvalue.key.RtkBaseStationKey;
|
||||
import dji.sdk.keyvalue.key.RtkMobileStationKey;
|
||||
import dji.sdk.keyvalue.value.base.DJIValue;
|
||||
import dji.v5.utils.common.LogUtils;
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/03/16 3:36 下午
|
||||
* @description:
|
||||
*/
|
||||
public class KeyItemDataUtil {
|
||||
private static final String TAG = KeyItemDataUtil.class.getSimpleName();
|
||||
private static final List<KeyItem<?, ?>> allKeyList = new ArrayList<>();
|
||||
|
||||
private KeyItemDataUtil() {
|
||||
//do something
|
||||
}
|
||||
|
||||
public static void initBatteryKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, BatteryKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initAirlinkKeyList(List<KeyItem<?, ?>> keylist) {
|
||||
initList(keylist, AirLinkKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initGimbalKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, GimbalKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initCameraKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, CameraKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initWiFiKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
// initList(keyList , WiFiKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initFlightAssistantKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, FlightAssistantKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initFlightControllerKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, FlightControllerKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initRemoteControllerKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, RemoteControllerKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initBleKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, BleKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initProductKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, ProductKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initRtkBaseStationKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, RtkBaseStationKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initRtkMobileStationKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, RtkMobileStationKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initOcuSyncKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, OcuSyncKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initRadarKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, RadarKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initIntelligentBoxList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, AIBoxKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initMobileNetworkKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, MobileNetworkKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initMobileNetworkLinkRCKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, MobileNetworkLinkRCKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initOnboardKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, OnboardKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initPayloadKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, PayloadKey.getKeyList());
|
||||
}
|
||||
|
||||
public static void initLidarKeyList(List<KeyItem<?, ?>> keyList) {
|
||||
initList(keyList, LidarKey.getKeyList());
|
||||
}
|
||||
|
||||
private static void initList(List<KeyItem<?, ?>> keyList, List<DJIKeyInfo<?>> keyInfoList) {
|
||||
if (keyList == null || !keyList.isEmpty()){
|
||||
return;
|
||||
}
|
||||
for (DJIKeyInfo<?> info : keyInfoList) {
|
||||
KeyItem<DJIValue, DJIValue> item = new KeyItem<>(info);
|
||||
genericItem(item, info);
|
||||
keyList.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static <P extends DJIValue, R extends DJIValue> void genericItem(KeyItem<P, R> item, DJIKeyInfo<?> keyInfo) {
|
||||
|
||||
Class<?> tdClazz;
|
||||
Field field = null;
|
||||
boolean isDjiValue = false;
|
||||
try {
|
||||
IDJIValueConverter<P, R> clazzConvert = keyInfo.getTypeConverter();
|
||||
if (clazzConvert instanceof SingleValueConverter) {
|
||||
field = clazzConvert.getClass().getDeclaredField("dClass");
|
||||
Field tmp = clazzConvert.getClass().getDeclaredField("isDJIValue");
|
||||
tmp.setAccessible(true);
|
||||
isDjiValue = tmp.getBoolean(clazzConvert);
|
||||
} else if (clazzConvert instanceof DJIValueConverter) {
|
||||
field = clazzConvert.getClass().getDeclaredField("tClass");
|
||||
}
|
||||
|
||||
if (field != null) {
|
||||
field.setAccessible(true);
|
||||
tdClazz = (Class<?>) field.get(clazzConvert);
|
||||
item.param = (P) tdClazz.newInstance();
|
||||
item.result = (R) tdClazz.newInstance();
|
||||
item.setSingleDJIValue(isDjiValue);
|
||||
item.initGenericInstance();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static int getAllKeyListCount() {
|
||||
List<KeyItem<?, ?>> allKeyList = new ArrayList<>();
|
||||
getAllKeyList(allKeyList);
|
||||
return allKeyList.size();
|
||||
}
|
||||
|
||||
public static void getAllKeyList(List<KeyItem<?, ?>> keylist) {
|
||||
if (!allKeyList.isEmpty()) {
|
||||
keylist.addAll(allKeyList);
|
||||
return;
|
||||
}
|
||||
List<KeyItem<?, ?>> keyList = new ArrayList<>();
|
||||
initBatteryKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initAirlinkKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initGimbalKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initCameraKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initFlightAssistantKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initFlightControllerKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initRemoteControllerKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initBleKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initProductKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initRtkBaseStationKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initRtkMobileStationKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initOcuSyncKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initRadarKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initMobileNetworkKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initMobileNetworkLinkRCKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initOnboardKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initPayloadKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initLidarKeyList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
|
||||
initIntelligentBoxList(keyList);
|
||||
allKeyList.addAll(keyList);
|
||||
keyList.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
package dji.sampleV5.aircraft.keyvalue
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
|
||||
|
||||
import dji.sampleV5.aircraft.util.Util
|
||||
import dji.v5.utils.common.LogUtils
|
||||
import java.lang.Exception
|
||||
import java.lang.StringBuilder
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/03/10 11:23 上午
|
||||
* @description:
|
||||
*/
|
||||
object KeyItemHelper {
|
||||
|
||||
val TAG = LogUtils.getTag(this)
|
||||
val LISTEN_RECORD_MAX_LENGTH = 2000
|
||||
val FILED_CHANGE = "\$change"
|
||||
/**
|
||||
* 通过反射给字段赋值
|
||||
*
|
||||
* @param obj
|
||||
*/
|
||||
fun initSubItemData(obj: Any): Map<String?, List<EnumItem>> {
|
||||
val dataMap: MutableMap<String?, List<EnumItem>> = HashMap()
|
||||
try {
|
||||
val fields = obj.javaClass.declaredFields
|
||||
for (field in fields) {
|
||||
if (field.name == FILED_CHANGE || field.name == "serialVersionUID") {
|
||||
continue
|
||||
}
|
||||
val clazz = field.type
|
||||
if (clazz.isEnum) {
|
||||
dataMap[clazz.canonicalName] =
|
||||
buildParamsSubItemListWithEnum(field.type as Class<Enum<*>>)
|
||||
} else if (isEnumList(field)) {
|
||||
val type = field.genericType
|
||||
if (type is ParameterizedType) {
|
||||
val subObject: Class<out Enum<*>> =
|
||||
type.actualTypeArguments[0] as Class<Enum<*>>
|
||||
dataMap[clazz.canonicalName] = buildParamsSubItemListWithEnum(subObject)
|
||||
}
|
||||
} else {
|
||||
dataMap.clear()
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.e(TAG,e.message)
|
||||
}
|
||||
return dataMap
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理界面参数选择和设置
|
||||
*
|
||||
* @param anchor
|
||||
* @param dataMap
|
||||
*/
|
||||
fun <P> processSubListLogic(
|
||||
anchor: View,
|
||||
param: P,
|
||||
dataMap: Map<String?, List<EnumItem>>,
|
||||
callBack: KeyItemActionListener<String?>
|
||||
) {
|
||||
try {
|
||||
val nameList = Util.getMapKeyList(dataMap)
|
||||
val subItemList = Util.getMapValueList(dataMap)
|
||||
if (dataMap.size == 1) {
|
||||
//简单列表
|
||||
val list = subItemList[0]
|
||||
val clazz = Class.forName(nameList[0]!!) as Class<Enum<*>>
|
||||
showSimpleSubItemList(anchor.context, list, clazz, object :
|
||||
KeyItemActionListener<List<String>?> {
|
||||
override fun actionChange(t: List<String>?) {
|
||||
updateClassData(param, dataMap)
|
||||
callBack.actionChange(param.toString())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
//复合列表
|
||||
KeyValueDialogUtil.showListConfirmWindow(
|
||||
anchor,
|
||||
getSimpleNameList(nameList),
|
||||
"select item for setting",
|
||||
object :
|
||||
KeyItemActionListener<String?> {
|
||||
override fun actionChange(msg: String?) {
|
||||
if ("confirm" == msg) {
|
||||
updateClassData(param, dataMap)
|
||||
callBack.actionChange(param.toString())
|
||||
} else {
|
||||
val clazz = getClassWithName(msg, nameList) as Class<Enum<*>>?
|
||||
val list = dataMap[clazz!!.canonicalName]!!
|
||||
showSimpleSubItemList(
|
||||
anchor.context,
|
||||
list,
|
||||
clazz
|
||||
) { updateClassData(param, dataMap) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e: ClassNotFoundException) {
|
||||
LogUtils.e(TAG , e.message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 类名列表获取:通过全名list获取简名list
|
||||
*
|
||||
* @param nameList
|
||||
* @return
|
||||
*/
|
||||
fun getSimpleNameList(nameList: List<String?>): List<String> {
|
||||
val simpleNameList: MutableList<String> = ArrayList()
|
||||
for (str in nameList) {
|
||||
simpleNameList.add(str!!.substring(str!!.lastIndexOf(".") + 1))
|
||||
}
|
||||
return simpleNameList
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过简名获取类字节码
|
||||
*
|
||||
* @param simpleName
|
||||
* @param nameList
|
||||
* @return
|
||||
*/
|
||||
fun getClassWithName(simpleName: String?, nameList: List<String?>): Class<*>? {
|
||||
var clazz: Class<*>? = null
|
||||
try {
|
||||
for (str in nameList) {
|
||||
if (str!!.endsWith(simpleName!!)) {
|
||||
clazz = Class.forName(str!!)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e: ClassNotFoundException) {
|
||||
LogUtils.e(TAG , e.message)
|
||||
}
|
||||
return clazz
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建参数条目的选项子列表
|
||||
*
|
||||
* @param <E>
|
||||
* @return
|
||||
</E> */
|
||||
fun <E : Enum<*>?> buildParamsSubItemListWithEnum(clazz: Class<E>): List<EnumItem> {
|
||||
val list: MutableList<EnumItem> = ArrayList()
|
||||
try {
|
||||
var item: EnumItem
|
||||
val objs: Array<out E>? = clazz.getEnumConstants()
|
||||
for (obj in objs!!) {
|
||||
item = EnumItem()
|
||||
item.setName(obj.toString())
|
||||
list.add(item)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.e(TAG,e.message)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性子列表数据
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
fun getSubItemNameList(data: List<EnumItem>): List<String> {
|
||||
val result: MutableList<String> = ArrayList()
|
||||
for (item in data) {
|
||||
result.add(item.getName().toString())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选中属性的顺序值
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
fun getSelectedIndex(data: List<EnumItem>): Int {
|
||||
var index = 0
|
||||
for (i in data.indices) {
|
||||
if (data[i].isSelected()) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选中属性的value
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
fun getSelectedValue(data: List<EnumItem>): String {
|
||||
var value = ""
|
||||
for (i in data.indices) {
|
||||
if (data[i].isSelected()) {
|
||||
value = data[i].getName().toString()
|
||||
break
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新选择状态
|
||||
*
|
||||
* @param data
|
||||
* @param name
|
||||
*/
|
||||
fun updatedSelectedInfo(data: List<EnumItem>, names: List<String?>) {
|
||||
for (item in data) {
|
||||
item.setSelected(false)
|
||||
for (name in names) {
|
||||
if (item.getName().equals(name)) {
|
||||
item.setSelected(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单参数列表,逻辑处理
|
||||
*/
|
||||
fun <E : Enum<*>?> showSimpleSubItemList(
|
||||
context: Context?,
|
||||
simpleItemList: List<EnumItem>,
|
||||
clazz: Class<E>,
|
||||
callBack: KeyItemActionListener<List<String>?>
|
||||
) {
|
||||
val StrList = getSubItemNameList(simpleItemList)
|
||||
if (StrList.size == 0) {
|
||||
return
|
||||
}
|
||||
val selectedIndex = getSelectedIndex(simpleItemList)
|
||||
if (clazz.isEnum) {
|
||||
KeyValueDialogUtil.showSingleChoiceDialog(
|
||||
context,
|
||||
StrList,
|
||||
selectedIndex,
|
||||
object : KeyItemActionListener<List<String>?> {
|
||||
override fun actionChange(values: List<String>?) {
|
||||
updatedSelectedInfo(simpleItemList, values!!)
|
||||
callBack.actionChange(values)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
KeyValueDialogUtil.showMultiChoiceDialog(
|
||||
context,
|
||||
StrList,
|
||||
) { values ->
|
||||
updatedSelectedInfo(simpleItemList, values!!)
|
||||
callBack.actionChange(values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射给字段赋值
|
||||
*
|
||||
* @param obj
|
||||
*/
|
||||
fun initClassData(obj: Any?) {
|
||||
if (obj == null || Util.isBlank(obj.toString())) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
val pFields = obj.javaClass.declaredFields
|
||||
for (field in pFields) {
|
||||
if (field.name == FILED_CHANGE || field.name == "serialVersionUID") {
|
||||
continue
|
||||
}
|
||||
field.isAccessible = true
|
||||
val clazz = field.type
|
||||
if (setFieldPro(field , obj)){
|
||||
continue
|
||||
}
|
||||
|
||||
val subObj = clazz.newInstance()
|
||||
field[obj] = subObj
|
||||
initClassData(subObj)
|
||||
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.e(TAG, e.message)
|
||||
}
|
||||
}
|
||||
|
||||
fun setFieldPro(field:Field , obj: Any?):Boolean{
|
||||
val clazz = field.type
|
||||
if (clazz.isEnum) {
|
||||
field[obj] = clazz.enumConstants!![0]
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Util.isWrapClass(clazz)) {
|
||||
if (clazz == Boolean::class.javaObjectType) {
|
||||
field[obj] = false
|
||||
}
|
||||
//如果需要 可在else 中可给Integer Double 等设置初始值
|
||||
return true
|
||||
}
|
||||
if (clazz == MutableList::class.java) {
|
||||
field[obj] = ArrayList<Any>()// todo
|
||||
return true
|
||||
} else if (clazz == String::class.java) {
|
||||
field[obj] = ""
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isEnumList(field: Field): Boolean {
|
||||
if (field.type == MutableList::class.java) {
|
||||
val type = field.genericType
|
||||
if (type is ParameterizedType) {
|
||||
val subType = type.actualTypeArguments[0]
|
||||
val clazz = subType as Class<*>
|
||||
if (clazz.isEnum) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射给字段赋值
|
||||
*
|
||||
* @param obj
|
||||
*/
|
||||
fun updateClassData(obj: Any?, subItemMap: Map<String?, List<EnumItem>>) {
|
||||
try {
|
||||
val pFields = obj?.javaClass?.declaredFields
|
||||
if (pFields != null) {
|
||||
for (field in pFields) {
|
||||
if (field.name == FILED_CHANGE || field.name == "serialVersionUID") {
|
||||
continue
|
||||
}
|
||||
field.isAccessible = true
|
||||
val clazz = field.type
|
||||
if (clazz.isEnum) {
|
||||
val itemList = subItemMap[clazz.canonicalName]!!
|
||||
field[obj] = getEnumData(clazz as Class<Enum<*>>, getSelectedValue(itemList))
|
||||
} else if (isEnumList(field)) {
|
||||
setEnumListProperty(field , obj , subItemMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.e(TAG , e.message)
|
||||
}
|
||||
}
|
||||
|
||||
fun setEnumListProperty(field: Field , obj: Any? , subItemMap: Map<String?, List<EnumItem>>) {
|
||||
val type = field.genericType
|
||||
val clazz = field.type
|
||||
if (type is ParameterizedType) {
|
||||
val subObject = type.actualTypeArguments[0] as Class<Enum<*>>
|
||||
val itemList = subItemMap[clazz.canonicalName]
|
||||
val list: MutableList<Any> = ArrayList()
|
||||
val values: List<String> = getSelectedValues(itemList!!)
|
||||
for (value in values) {
|
||||
val test: Any = getEnumData(subObject, value)!!
|
||||
list.add(test)
|
||||
}
|
||||
field[obj] = list
|
||||
}
|
||||
}
|
||||
fun getSelectedValues(data: List<EnumItem>): List<String> {
|
||||
var value: String = ""
|
||||
val values: MutableList<String> = ArrayList()
|
||||
for (i in data.indices) {
|
||||
if (data[i].isSelected()) {
|
||||
value = data[i].getName()!!
|
||||
values.add(value)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加携带日期的推送字符串
|
||||
*
|
||||
* @param targetStr
|
||||
* @param appendStr
|
||||
* @return
|
||||
*/
|
||||
fun appendListenRecord(targetStr: String, appendStr: String?): String {
|
||||
if (Util.isBlank(appendStr)) {
|
||||
return targetStr
|
||||
}
|
||||
val sb = StringBuilder(targetStr)
|
||||
sb.append("\n")
|
||||
sb.append(Util.getDateStr(Date()) + ":")
|
||||
sb.append("\n")
|
||||
sb.append(appendStr)
|
||||
//长度限制
|
||||
var result = sb.toString()
|
||||
if (result.length > LISTEN_RECORD_MAX_LENGTH) {
|
||||
result = result.substring(result.length - LISTEN_RECORD_MAX_LENGTH)
|
||||
}
|
||||
val title = "push info:"
|
||||
if (!result.startsWith(title)) {
|
||||
result = """
|
||||
$title
|
||||
$result
|
||||
""".trimIndent()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过字节码和值来获取实例
|
||||
*
|
||||
* @param clazz
|
||||
* @param value
|
||||
* @param <E>
|
||||
* @return
|
||||
</E> */
|
||||
fun <E : Enum<*>?> getEnumData(clazz: Class<E>, value: String): E? {
|
||||
var data: E? = null
|
||||
try {
|
||||
val objs: Array<out E>? = clazz.getEnumConstants()
|
||||
for (obj in objs!!) {
|
||||
if (obj.toString() == value) {
|
||||
data = obj as E
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.e(TAG , e.message)
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package dji.sampleV5.aircraft.keyvalue
|
||||
|
||||
import dji.sdk.keyvalue.key.ComponentType
|
||||
import dji.sdk.keyvalue.key.ProductKey
|
||||
import dji.sdk.keyvalue.utils.MultiComponentManager
|
||||
import dji.sdk.keyvalue.value.common.CameraLensType
|
||||
import dji.sdk.keyvalue.value.product.ProductType
|
||||
import dji.v5.common.callback.CommonCallbacks
|
||||
import dji.v5.common.error.DJICommonError
|
||||
import dji.v5.common.error.IDJIError
|
||||
import dji.v5.et.create
|
||||
import dji.v5.et.get
|
||||
import dji.v5.manager.capability.CapabilityManager
|
||||
import dji.v5.manager.capability.CapabilityParser
|
||||
import dji.v5.utils.common.DateUtils
|
||||
import dji.v5.utils.common.FileUtils
|
||||
import dji.v5.utils.common.LogUtils
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.CompletableEmitter
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/10/26 11:18 上午
|
||||
* @description: key的操作基类
|
||||
*/
|
||||
abstract class KeyOperatorCommand(
|
||||
private val productType: String,
|
||||
private val componentTypeName: String,
|
||||
private val componentIndex: Int
|
||||
) {
|
||||
|
||||
private val INTERVAL_TIME = 500L // 连续快速调用key时 可能会导致失败
|
||||
private val TAG_EQUAL = "=="
|
||||
private val TAG = LogUtils.getTag(this)
|
||||
private lateinit var competableEmitter: CompletableEmitter
|
||||
private var unPassedCount = 0;
|
||||
private lateinit var curCheckType: KeyCheckType
|
||||
private var keyCount = 0
|
||||
|
||||
private var whiteList = mutableListOf<String>(
|
||||
"GimbalCalibrationStatus",
|
||||
"IsShootingPhotoPanorama",
|
||||
"PhotoPanoramaMode",
|
||||
"PhotoPanoramaProgress",
|
||||
"ThermalContrast",
|
||||
"ThermalDDE",
|
||||
"ThermalRegionMetersureTemperature",
|
||||
"ThermalBrightness",
|
||||
"ThermalGainModeTemperatureRange",
|
||||
"AircraftLocation3D",
|
||||
"PhotoRatio"
|
||||
)
|
||||
|
||||
/**
|
||||
* 过滤Key类型条件
|
||||
*/
|
||||
abstract fun filter(item: KeyItem<*, *>): Boolean
|
||||
|
||||
/**
|
||||
* 指定指定动作类型
|
||||
*/
|
||||
abstract fun run(item: KeyItem<*, *>)
|
||||
|
||||
/**
|
||||
* 写文件需要
|
||||
*/
|
||||
abstract fun getTAG(): String
|
||||
|
||||
/**
|
||||
* Key执行结果回调TAG,用来改key执行错误或者失败
|
||||
*/
|
||||
abstract fun getErrorTAG(): String
|
||||
|
||||
open fun getIntervalTime(): Long {
|
||||
return INTERVAL_TIME
|
||||
}
|
||||
|
||||
fun execute(): Completable {
|
||||
|
||||
return Completable.create { emitter ->
|
||||
competableEmitter = emitter;
|
||||
unPassedCount = 0
|
||||
val allList: MutableList<KeyItem<*, *>> = ArrayList()
|
||||
KeyItemDataUtil.getAllKeyList(allList)
|
||||
val capabilityKeyCount =
|
||||
CapabilityManager.getInstance().getCapabilityKeyCount(productType)
|
||||
|
||||
LogUtils.i(TAG, "begin check $capabilityKeyCount")
|
||||
saveResult(" ----- begin ${getTAG()} check -----\n\n", false, false)
|
||||
saveResult(" ----- begin ${getTAG()} check -----\n\n", true, false)
|
||||
|
||||
allList.filter { item ->
|
||||
filter(item) && (item.toString() !in whiteList) && CapabilityManager.getInstance().isKeySupported(
|
||||
productType, componentTypeName, ComponentType.find(item.getKeyInfo().componentType), "Key$item"
|
||||
)
|
||||
}.forEach { item ->
|
||||
LogUtils.e(TAG, "${++keyCount} doCheck $item ")
|
||||
LogUtils.e(TAG, "Thread name is 1 " + Thread.currentThread().name)
|
||||
val lock = CountDownLatch(1)
|
||||
item.componetIndex = if (MultiComponentManager.isMultiKey(item.keyInfo.componentType)) {
|
||||
componentIndex
|
||||
} else {
|
||||
0
|
||||
}
|
||||
dependKeySet(item, object : CommonCallbacks.CompletionCallback {
|
||||
override fun onSuccess() {
|
||||
//从主线程再切回io线程
|
||||
LogUtils.e(TAG, "Thread name is 2 " + Thread.currentThread().name)
|
||||
lock.countDown()
|
||||
|
||||
}
|
||||
|
||||
override fun onFailure(error: IDJIError) {
|
||||
LogUtils.e(TAG, "Set $item depend key failed!")
|
||||
lock.countDown()
|
||||
}
|
||||
})
|
||||
|
||||
lock.await()
|
||||
Thread.sleep(getIntervalTime()) // 设置完后,立即设置可能会异常如FrequencyBand
|
||||
run(item)
|
||||
}
|
||||
Thread.sleep(getIntervalTime())
|
||||
saveResult(" --------finish ${getTAG()}---------\n", true, true)
|
||||
saveResult(" --------finish ${getTAG()}---------\n", false, true)
|
||||
//遍历完成即完成
|
||||
competableEmitter.onComplete()
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行依赖key的 Set方法
|
||||
*/
|
||||
private fun dependKeySet(item: KeyItem<*, *>, callback: CommonCallbacks.CompletionCallback) {
|
||||
// 首先获取具体的依赖的keyitem
|
||||
val keyName = item.toString()
|
||||
val valueBean = CapabilityParser.getInstance().getValueBean(keyName)
|
||||
val dependKeyItem = valueBean?.dependKeyName?.let { CapabilityKeyChecker.getKeyItem(it) }
|
||||
LogUtils.e(TAG, "dependKeyItem key : " + (valueBean?.dependKeyName ?: "null"))
|
||||
|
||||
if (dependKeyItem != null) {
|
||||
dependKeyItem.setKeyOperateCallBack { res ->
|
||||
if (res.toString().contains("SetErrorMsg")) {
|
||||
callback.onFailure(DJICommonError.FACTORY.build(res.toString()))
|
||||
} else {
|
||||
callback.onSuccess()
|
||||
}
|
||||
}
|
||||
dependKeyItem.setComponetIndex(item.getComponetIndex())
|
||||
dependKeyItem.setSubComponetType(item.getSubComponetType())
|
||||
dependKeyItem.setSubComponetIndex(item.getSubComponetIndex())
|
||||
dependKeyItem.doSet(valueBean.dependKeyValue.replace("\\", ""))
|
||||
} else {
|
||||
// 没有找到前置条件,返回成功。
|
||||
callback.onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
fun doKeyParam(item: KeyItem<*, *>, type: KeyCheckType) {
|
||||
curCheckType = type
|
||||
getItemDecoderList(item).forEach {
|
||||
val lock = CountDownLatch(1)
|
||||
item.setKeyOperateCallBack {
|
||||
var result = StringBuilder()
|
||||
val resStr = it.toString()
|
||||
val keyNameIndex = resStr.indexOf(getTAG())
|
||||
if (keyNameIndex <= -1 ) return@setKeyOperateCallBack
|
||||
val keyName = resStr.substring(0, keyNameIndex)
|
||||
val isPassed: Boolean
|
||||
val failedReson = if (resStr.contains(getErrorTAG())) {
|
||||
isPassed = false
|
||||
resStr.substring(resStr.indexOf(TAG_EQUAL))
|
||||
} else {
|
||||
isPassed = true
|
||||
resStr.substring(resStr.indexOf(TAG_EQUAL))
|
||||
}
|
||||
var componentTYpe = ComponentType.find(item.getKeyInfo().componentType)
|
||||
|
||||
result.append("${++unPassedCount} KeyName :${keyName} - ${componentTYpe}\n")
|
||||
.append("SubType:${getLensName(item)}\n")
|
||||
.append("Details:${failedReson}\n")
|
||||
.append("\n ----------------------- \n")
|
||||
|
||||
saveResult(result.toString(), isPassed, true)
|
||||
LogUtils.e(TAG, "SubType:${getLensName(item)} KeyName :${keyName}} ComponentType : $componentTYpe " + resStr)
|
||||
lock.countDown()
|
||||
}
|
||||
item.setComponetIndex(it.componetIndex)
|
||||
item.setSubComponetType(it.subComponetType)
|
||||
item.setSubComponetIndex(it.subComponetIndex)
|
||||
when (type) {
|
||||
KeyCheckType.ACTION -> item.doAction(it.jsonString)
|
||||
KeyCheckType.SET -> item.doSet(it.jsonString)
|
||||
KeyCheckType.GET -> item.doGet()
|
||||
}
|
||||
lock.await()
|
||||
Thread.sleep(getIntervalTime())
|
||||
}
|
||||
|
||||
LogUtils.e(TAG, "check finish!")
|
||||
}
|
||||
|
||||
private fun getLensName(keyItem: KeyItem<*, *>): String {
|
||||
return if (keyItem.keyInfo.componentType == ComponentType.CAMERA.value()) {
|
||||
CameraLensType.find(keyItem.getSubComponetType()).name
|
||||
} else {
|
||||
"DEFAULT"
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveResult(content: String, saveType: Boolean, append: Boolean) {
|
||||
val product = ProductKey.KeyProductType.create().get(ProductType.UNRECOGNIZED)
|
||||
var filePath = LogUtils.getLogPath() + getTAG() + product.name + "【${DateUtils.getSystemTimeOnlyYMD()}】"
|
||||
|
||||
filePath += if (saveType) {
|
||||
"Success.txt"
|
||||
} else {
|
||||
"Failed.txt"
|
||||
}
|
||||
FileUtils.writeFile(filePath, content, append)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过key名称从能力集中获取keyItem需要的参数,包括lenstype ,用例json
|
||||
* 如果在能力集中没有找到用例 需要返回一个默认的(无参的action 返回空)
|
||||
*/
|
||||
private fun getItemDecoderList(keyItem: KeyItem<*, *>): MutableList<CapabilityKeyChecker.ItemDecoder> {
|
||||
val resList: MutableList<CapabilityKeyChecker.ItemDecoder> = ArrayList()
|
||||
//通过item获取 支持的lensType 列表
|
||||
var lensTypeList = if (keyItem.keyInfo.componentType == ComponentType.CAMERA.value()) {
|
||||
CapabilityManager.getInstance().getSupportLens("Key$keyItem", productType, componentTypeName)
|
||||
} else {
|
||||
arrayListOf("DEFAULT")
|
||||
}
|
||||
//获取javaBean 字符列表 用例中没有文件则返回空集合
|
||||
var keyParamList = CapabilityKeyChecker.getKeyParamList(keyItem.toString())
|
||||
|
||||
// 用例文件存在(set类型 都会有) ;action 不在用例文件中的则不自动测试需要人为测试 支持set get
|
||||
if (keyParamList.isNotEmpty() || keyItem.canGet()) {
|
||||
lensTypeList.map {
|
||||
transCameraLensTypeStr(it)
|
||||
}.forEach { subComponentType ->
|
||||
val index = if (MultiComponentManager.isMultiKey(keyItem.keyInfo.componentType)) {
|
||||
componentIndex
|
||||
} else {
|
||||
0
|
||||
}
|
||||
if (curCheckType == KeyCheckType.SET || curCheckType == KeyCheckType.ACTION) {
|
||||
keyParamList
|
||||
.forEach {
|
||||
resList.add(
|
||||
CapabilityKeyChecker.ItemDecoder(
|
||||
componetIndex = index,
|
||||
subComponetType = subComponentType,
|
||||
jsonString = it
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if (curCheckType == KeyCheckType.GET) {
|
||||
resList.add(
|
||||
CapabilityKeyChecker.ItemDecoder(
|
||||
componetIndex = index,
|
||||
subComponetType = subComponentType,
|
||||
jsonString = ""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//添加默认 确保每个key都可以执行到set方法 , action无参数不执行(在用例中未找到)
|
||||
}
|
||||
return resList
|
||||
}
|
||||
|
||||
/**
|
||||
* 将能力集中CameraLensType 字符串转为对应的value
|
||||
*/
|
||||
fun transCameraLensTypeStr(lensName: String): Int {
|
||||
CameraLensType.values().forEach {
|
||||
if (it.name.contains(lensName)) {
|
||||
return it.value()
|
||||
}
|
||||
}
|
||||
return CameraLensType.UNKNOWN.value()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package dji.sampleV5.aircraft.keyvalue
|
||||
|
||||
/**
|
||||
* @author feel.feng
|
||||
* @time 2022/10/26 2:25 下午
|
||||
* @description:
|
||||
*/
|
||||
class KeySetCommand(
|
||||
private val productType: String,
|
||||
private val componentTypeName: String,
|
||||
private val componentIndex: Int
|
||||
) : KeyOperatorCommand(productType, componentTypeName, componentIndex) {
|
||||
|
||||
private val TAG_GET = "【SET】"
|
||||
private val TAG_ERROR = "SetErrorMsg"
|
||||
private val INTERVAL_TIME = 3000L
|
||||
|
||||
//&& ("CameraMode" ==item.toString() || "RegionMeteringArea" == item.toString())
|
||||
override fun filter(item: KeyItem<*, *>): Boolean {
|
||||
return item.canSet()
|
||||
}
|
||||
|
||||
override fun run(item: KeyItem<*, *>) {
|
||||
super.doKeyParam(item, KeyCheckType.SET)
|
||||
}
|
||||
|
||||
override fun getTAG(): String {
|
||||
return TAG_GET
|
||||
}
|
||||
|
||||
override fun getErrorTAG(): String {
|
||||
return TAG_ERROR
|
||||
}
|
||||
|
||||
override fun getIntervalTime(): Long {
|
||||
return INTERVAL_TIME
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package dji.sampleV5.aircraft.keyvalue;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import dji.sampleV5.aircraft.R;
|
||||
import dji.sampleV5.aircraft.util.Util;
|
||||
import dji.v5.utils.common.ContextUtil;
|
||||
import dji.v5.utils.common.DisplayUtil;
|
||||
|
||||
|
||||
public class KeyValueDialogUtil {
|
||||
|
||||
private static final int LIST_Y_OFF_SET = 3;
|
||||
|
||||
private KeyValueDialogUtil(){
|
||||
// init something
|
||||
}
|
||||
/**
|
||||
* 显示单选对话框
|
||||
*/
|
||||
public static void showSingleChoiceDialog(Context context, List<String> data, int selectedIndex, final KeyItemActionListener<List<String>> callBack) {
|
||||
AlertDialog dialog;
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
final String[] items = data.toArray(new String[data.size()]);
|
||||
builder.setSingleChoiceItems(items, selectedIndex, (dialog1, which) -> {
|
||||
if (callBack != null) {
|
||||
callBack.actionChange(Arrays.asList(items[which]));
|
||||
dialog1.dismiss();
|
||||
}
|
||||
});
|
||||
builder.setCancelable(true);
|
||||
dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
public static void showMultiChoiceDialog(Context context, List<String> data, final KeyItemActionListener<List<String>> callBack){
|
||||
AlertDialog dialog;
|
||||
List<String> values = new ArrayList<>();
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
final String[] items = data.toArray(new String[data.size()]);
|
||||
builder.setMultiChoiceItems(items, null, (dialog1, which, isChecked) -> {
|
||||
if (isChecked) {
|
||||
values.add(items[which]);
|
||||
} else {
|
||||
values.remove(items[which]);
|
||||
}
|
||||
|
||||
});
|
||||
builder.setPositiveButton(R.string.confirm, (dialog12, which) -> callBack.actionChange(values));
|
||||
builder.setCancelable(true);
|
||||
dialog = builder.create();
|
||||
dialog.show();
|
||||
|
||||
}
|
||||
/**
|
||||
* 显示简单列表弹窗
|
||||
*
|
||||
* @param anchor
|
||||
* @param data
|
||||
* @param callback
|
||||
*/
|
||||
public static void showListConfirmWindow(View anchor, final List<String> data, String title, final KeyItemActionListener<String> callback) {
|
||||
if (anchor == null || anchor.getContext() == null) {
|
||||
return;
|
||||
}
|
||||
Context context = anchor.getContext();
|
||||
View rootView = View.inflate(context, R.layout.dialog_list_confirm, null);
|
||||
final PopupWindow window = new PopupWindow(context);
|
||||
window.setWidth(Util.getHeight(ContextUtil.getContext()) / 2);
|
||||
window.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
window.setOutsideTouchable(false);
|
||||
window.setTouchable(true);
|
||||
window.setFocusable(true);
|
||||
window.setBackgroundDrawable(new ColorDrawable(0xffffff));
|
||||
window.setContentView(rootView);
|
||||
window.showAsDropDown(anchor, 0, LIST_Y_OFF_SET, Gravity.CENTER | Gravity.BOTTOM);
|
||||
|
||||
ListView listView = rootView.findViewById(R.id.list_view);
|
||||
TextView titleView = rootView.findViewById(R.id.title);
|
||||
if (Util.isNotBlank(title)) {
|
||||
titleView.setText(title);
|
||||
titleView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
titleView.setVisibility(View.GONE);
|
||||
}
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, R.layout.item_textview, data);
|
||||
listView.setAdapter(adapter);
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> callback.actionChange(data.get(position)));
|
||||
rootView.findViewById(R.id.button).setOnClickListener(v -> {
|
||||
window.dismiss();
|
||||
callback.actionChange("confirm");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示输入确认弹窗
|
||||
*
|
||||
* @param context
|
||||
* @param item
|
||||
*/
|
||||
public static void showInputDialog(Activity context, KeyItem<? ,?> item, final KeyItemActionListener<String> callback) {
|
||||
showInputDialog(context, context.getResources().getString(R.string.key_value_set) + item.getName() + ":", item.getParamJsonStr(), "", false, callback);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 显示输入确认弹窗
|
||||
*
|
||||
* @param context
|
||||
* @param title
|
||||
* @param msg
|
||||
*/
|
||||
public static void showInputDialog(Activity context, String title, final String msg, String hint, boolean singleLine, final KeyItemActionListener<String> callback) {
|
||||
View dialogView = context.getLayoutInflater().inflate(R.layout.dialog_param_input, null);
|
||||
dialogView.setBackgroundColor(context.getResources().getColor(R.color.gray));
|
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context).setView(dialogView).create();
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
dialog.setCancelable(false);
|
||||
|
||||
TextView tvTitle = dialogView.findViewById(R.id.title);
|
||||
tvTitle.setText(title);
|
||||
|
||||
final EditText input = dialogView.findViewById(R.id.input);
|
||||
input.setSingleLine(singleLine);
|
||||
if (Util.isNotBlank(msg)) {
|
||||
input.setText(msg);
|
||||
}
|
||||
if (Util.isNotBlank(hint)) {
|
||||
input.setHint(hint);
|
||||
}
|
||||
input.setMovementMethod(ScrollingMovementMethod.getInstance());
|
||||
dialogView.findViewById(R.id.confirm).setOnClickListener(v -> {
|
||||
if (callback != null) {
|
||||
callback.actionChange(input.getText().toString().trim());
|
||||
}
|
||||
if (dialog != null) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
dialogView.findViewById(R.id.cancel).setOnClickListener(v -> {
|
||||
if (callback != null) {
|
||||
callback.actionChange(null);
|
||||
}
|
||||
if (dialog != null) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 显示通用弹窗
|
||||
*
|
||||
* @param anchor
|
||||
* @param data
|
||||
* @param callback
|
||||
*/
|
||||
public static void showFilterListWindow(View anchor, final List<KeyItem<?,?>> data, final KeyItemActionListener<KeyItem<?,?>> callback) {
|
||||
if (anchor == null || anchor.getContext() == null) {
|
||||
return;
|
||||
}
|
||||
Context context = anchor.getContext();
|
||||
View rootView = View.inflate(context, R.layout.window_simple_listview, null);
|
||||
final PopupWindow window = new PopupWindow(context);
|
||||
window.setWidth(Util.getHeight(ContextUtil.getContext()) / 2);
|
||||
window.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
window.setOutsideTouchable(true);
|
||||
window.setTouchable(true);
|
||||
window.setFocusable(true);
|
||||
window.setBackgroundDrawable(new ColorDrawable(0xffffff));
|
||||
|
||||
window.setContentView(rootView);
|
||||
window.showAsDropDown(anchor, 0, - DisplayUtil.dip2px(anchor.getContext(), 47), Gravity.LEFT | Gravity.TOP );
|
||||
|
||||
ListView listView = rootView.findViewById(R.id.list_view);
|
||||
TextView titleView = rootView.findViewById(R.id.tv_title);
|
||||
|
||||
titleView.setText(R.string.commonlyused_key);
|
||||
titleView.setVisibility(View.VISIBLE);
|
||||
|
||||
ArrayAdapter<KeyItem<?,?>> adapter = new ArrayAdapter<KeyItem<?,?>> (context, R.layout.item_textview, data);
|
||||
listView.setAdapter(adapter);
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
if (callback != null ) {
|
||||
callback.actionChange(data.get(position));
|
||||
}
|
||||
if (window != null) {
|
||||
window.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示通用弹窗
|
||||
*
|
||||
* @param anchor
|
||||
* @param data
|
||||
* @param callback
|
||||
*/
|
||||
public static void showChannelFilterListWindow(View anchor, final List<ChannelType> data , final KeyItemActionListener<ChannelType> callback) {
|
||||
if (anchor == null || anchor.getContext() == null) {
|
||||
return;
|
||||
}
|
||||
Context context = anchor.getContext();
|
||||
View rootView = View.inflate(context, R.layout.window_simple_listview, null);
|
||||
final PopupWindow window = new PopupWindow(context);
|
||||
window.setWidth(Util.getHeight(ContextUtil.getContext()) / 2);
|
||||
window.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
window.setOutsideTouchable(true);
|
||||
window.setTouchable(true);
|
||||
window.setFocusable(true);
|
||||
window.setBackgroundDrawable(new ColorDrawable(0xffffff));
|
||||
|
||||
window.setContentView(rootView);
|
||||
window.showAsDropDown(anchor, (int) (anchor.getWidth() * 1.5), - DisplayUtil.dip2px(anchor.getContext(), 37), Gravity.LEFT | Gravity.BOTTOM);
|
||||
ListView listView = rootView.findViewById(R.id.list_view);
|
||||
|
||||
ArrayAdapter<ChannelType> adapter = new ArrayAdapter<ChannelType>(context, R.layout.item_textview, data);
|
||||
listView.setAdapter(adapter);
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
if (callback != null ) {
|
||||
callback.actionChange(data.get(position));
|
||||
}
|
||||
if (window != null) {
|
||||
window.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
EditText filter = rootView.findViewById(R.id.et_filter);
|
||||
filter.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
// dosomething
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
// dosomething
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
adapter.getFilter().filter(s.toString());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static void showNormalDialog(Activity context, String title) {
|
||||
View dialogView = context.getLayoutInflater().inflate(R.layout.dialog_tips, null);
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context).setView(dialogView).create();
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
dialog.setCancelable(false);
|
||||
|
||||
TextView tvTitle = dialogView.findViewById(R.id.title);
|
||||
tvTitle.setText(title);
|
||||
|
||||
dialogView.findViewById(R.id.confirm).setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user