You've already forked ChameleonBLEAPI
mirror of
https://github.com/RfidResearchGroup/ChameleonBLEAPI.git
synced 2026-05-12 11:20:47 -07:00
765 lines
30 KiB
Java
765 lines
30 KiB
Java
|
|
package com.proxgrind.chameleon.utils.tools;
|
||
|
|
|
||
|
|
import android.Manifest;
|
||
|
|
import android.app.Activity;
|
||
|
|
import android.app.ActivityManager;
|
||
|
|
import android.bluetooth.BluetoothAdapter;
|
||
|
|
import android.bluetooth.BluetoothDevice;
|
||
|
|
import android.content.Context;
|
||
|
|
import android.content.DialogInterface;
|
||
|
|
import android.content.Intent;
|
||
|
|
import android.content.SharedPreferences;
|
||
|
|
import android.content.pm.PackageManager;
|
||
|
|
import android.content.pm.ResolveInfo;
|
||
|
|
import android.net.Uri;
|
||
|
|
import android.os.Build;
|
||
|
|
import android.os.Environment;
|
||
|
|
import android.os.Handler;
|
||
|
|
import android.os.Looper;
|
||
|
|
import android.util.Log;
|
||
|
|
import android.view.View;
|
||
|
|
import android.widget.AbsListView;
|
||
|
|
import android.widget.Button;
|
||
|
|
import android.widget.TextView;
|
||
|
|
|
||
|
|
import androidx.annotation.Nullable;
|
||
|
|
import androidx.appcompat.app.AlertDialog;
|
||
|
|
|
||
|
|
import java.io.File;
|
||
|
|
import java.io.FileFilter;
|
||
|
|
import java.io.IOException;
|
||
|
|
import java.text.SimpleDateFormat;
|
||
|
|
import java.util.ArrayList;
|
||
|
|
import java.util.Arrays;
|
||
|
|
import java.util.Collections;
|
||
|
|
import java.util.Comparator;
|
||
|
|
import java.util.Date;
|
||
|
|
import java.util.Iterator;
|
||
|
|
import java.util.LinkedHashMap;
|
||
|
|
import java.util.List;
|
||
|
|
import java.util.Locale;
|
||
|
|
import java.util.Map;
|
||
|
|
import java.util.Set;
|
||
|
|
import java.util.UUID;
|
||
|
|
|
||
|
|
import com.proxgrind.chameleon.R;
|
||
|
|
import com.proxgrind.chameleon.activities.DevicesFastActivity;
|
||
|
|
import com.proxgrind.chameleon.executor.ChameleonExecutorProxy;
|
||
|
|
import com.proxgrind.chameleon.javabean.DevBean;
|
||
|
|
import com.proxgrind.chameleon.javabean.DumpBean;
|
||
|
|
import com.proxgrind.chameleon.utils.mifare.DumpUtils;
|
||
|
|
import com.proxgrind.chameleon.utils.stream.FileUtils;
|
||
|
|
import com.proxgrind.chameleon.utils.system.AppContextUtils;
|
||
|
|
import com.proxgrind.chameleon.utils.system.AppListUtils;
|
||
|
|
import com.proxgrind.chameleon.utils.system.AppResourceUtils;
|
||
|
|
import com.proxgrind.chameleon.utils.system.LogUtils;
|
||
|
|
import com.proxgrind.chameleon.widget.MaterialAlertDialog;
|
||
|
|
|
||
|
|
import org.json.JSONArray;
|
||
|
|
import org.json.JSONException;
|
||
|
|
import org.json.JSONObject;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Created by DXL on 2017/10/27.
|
||
|
|
*/
|
||
|
|
public class Commons {
|
||
|
|
|
||
|
|
//短称路径
|
||
|
|
public static final String LOG_TAG = Commons.class.getSimpleName();
|
||
|
|
// 子模块包名!
|
||
|
|
private static final String SUB_DEBUG_PACK_NAME = "com.proxgrind.debug";
|
||
|
|
// Dump文件固定的时间格式!
|
||
|
|
public static final String DEFAULT_TIME_DECRATE = "yyyy-MM-dd_HH:mm:ss";
|
||
|
|
public static SharedPreferences sp = AppContextUtils.app.getSharedPreferences("SaveMap", Context.MODE_PRIVATE);
|
||
|
|
public static SharedPreferences.Editor editor = sp.edit();
|
||
|
|
|
||
|
|
private Commons() {
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String[] getPermissionsOfAppRequired() {
|
||
|
|
ArrayList<String> perList = new ArrayList<>();
|
||
|
|
perList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||
|
|
perList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
|
||
|
|
// perList.add(Manifest.permission.ACCESS_COARSE_LOCATION);
|
||
|
|
perList.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
||
|
|
return perList.toArray(new String[0]);
|
||
|
|
}
|
||
|
|
|
||
|
|
//呼叫QQ
|
||
|
|
public static void callQQ(Context context, String qq, Runnable onFaild) {
|
||
|
|
//这里的228451878是自己指定的QQ号码,可以自己更换
|
||
|
|
String url = "mqqwpa://im/chat?chat_type=wpa&uin=" + qq;
|
||
|
|
try {
|
||
|
|
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
||
|
|
} catch (Exception e) {
|
||
|
|
onFaild.run();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//打开浏览器,链接到指定的链接
|
||
|
|
public static void openUrl(Context context, String url) {
|
||
|
|
try {
|
||
|
|
Uri uri = Uri.parse(url);
|
||
|
|
Intent intent = new Intent();
|
||
|
|
intent.setAction("android.intent.action.VIEW");
|
||
|
|
intent.setData(uri);
|
||
|
|
context.startActivity(intent);
|
||
|
|
} catch (Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//获取包中的文件的URI
|
||
|
|
public static Uri getUriFromResource(String packagePath, String filePath) {
|
||
|
|
return Uri.parse("android.resource://" + packagePath + "/" + filePath);
|
||
|
|
}
|
||
|
|
|
||
|
|
//移除设备对象从集合中!
|
||
|
|
public static boolean removeDevByList(DevBean devBean, List<DevBean> list) {
|
||
|
|
if (devBean != null) {
|
||
|
|
String name = devBean.getDevName();
|
||
|
|
String addr = devBean.getMacAddress();
|
||
|
|
if (name == null) return false;
|
||
|
|
for (int i = 0; i < list.size(); i++) {
|
||
|
|
DevBean tmpBean = list.get(i);
|
||
|
|
if (tmpBean == null) return false;
|
||
|
|
String n = tmpBean.getDevName();
|
||
|
|
String a = tmpBean.getMacAddress();
|
||
|
|
if (n == null) return false;
|
||
|
|
if (n.equals(name) && a.equals(addr)) {
|
||
|
|
list.remove(tmpBean);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//该设备实体之中的字段是否是空的!
|
||
|
|
public static boolean isDevBeanDataNotNull(DevBean devBean) {
|
||
|
|
if (devBean == null) return false;
|
||
|
|
return devBean.getMacAddress() != null;
|
||
|
|
}
|
||
|
|
|
||
|
|
//判断两个设备是否是一致的
|
||
|
|
public static boolean equalDebBean(DevBean a, DevBean b) {
|
||
|
|
if (a == b) return true;
|
||
|
|
if (a == null || b == null) return false;
|
||
|
|
|
||
|
|
if (isDevBeanDataNotNull(a) && isDevBeanDataNotNull(b)) {
|
||
|
|
return a.getMacAddress().equals(b.getMacAddress());
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//从蓝牙适配器中取出历史连接的设备列表!
|
||
|
|
public static DevBean[] getDevsFromBTAdapter(BluetoothAdapter btAdapter) {
|
||
|
|
ArrayList<DevBean> devList = new ArrayList<>();
|
||
|
|
Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();
|
||
|
|
if (pairedDevices == null) return null;
|
||
|
|
if (pairedDevices.size() > 0) {
|
||
|
|
ArrayList<BluetoothDevice> tmpList = new ArrayList<>(pairedDevices);
|
||
|
|
for (int i = 0; i < tmpList.size(); ++i) {
|
||
|
|
devList.add(new DevBean(tmpList.get(i).getName(),
|
||
|
|
tmpList.get(i).getAddress()));
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return ArrayUtils.list2Arr(devList);
|
||
|
|
}
|
||
|
|
|
||
|
|
//设备是否是USB设备!
|
||
|
|
public static boolean isUsbDevice(String address) {
|
||
|
|
if (address == null) return false;
|
||
|
|
//这三种mac是开发者定义的用于区分USB设备和蓝牙设备的特征符!
|
||
|
|
switch (address) {
|
||
|
|
case "00:00:00:00:00:00":
|
||
|
|
case "00:00:00:00:00:01":
|
||
|
|
case "00:00:00:00:00:02":
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获得相应的从值中!
|
||
|
|
public static int getPositionFromValue(String str, List<String> list) {
|
||
|
|
return list.indexOf(str);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 保存Dump到目录!
|
||
|
|
public static boolean saveDump2Local(byte[] dump, String name) {
|
||
|
|
File dumpDir = FileUtils.getAppFilesDir("dump");
|
||
|
|
try {
|
||
|
|
FileUtils.writeBytes(dump, FileUtils.newFile(dumpDir, name), false);
|
||
|
|
return true;
|
||
|
|
} catch (IOException e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static File[] listInternalDump() {
|
||
|
|
File dumpDir = FileUtils.getAppFilesDir("dump");
|
||
|
|
return dumpDir.listFiles(new FileFilter() {
|
||
|
|
@Override
|
||
|
|
public boolean accept(File pathname) {
|
||
|
|
return pathname.getName().endsWith(".dump");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getTimeDecorate() {
|
||
|
|
Date date = new Date(); //获取当前的系统时间。
|
||
|
|
SimpleDateFormat dateFormat = new SimpleDateFormat(DEFAULT_TIME_DECRATE, Locale.getDefault()); //使用了默认的格式创建了一个日期格式化对象。
|
||
|
|
return dateFormat.format(date);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getTimeDecorate(String fileName) {
|
||
|
|
String time = RegexGroupUtils.matcherGroup(
|
||
|
|
fileName,
|
||
|
|
".*\\|(.*)\\..*",
|
||
|
|
1,
|
||
|
|
0);
|
||
|
|
if (time == null) return null;
|
||
|
|
try {
|
||
|
|
return new SimpleDateFormat(DEFAULT_TIME_DECRATE, Locale.CHINESE).format(time);
|
||
|
|
} catch (Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getInternalDumpDir() {
|
||
|
|
return FileUtils.getAppFilesDir("dump").getAbsolutePath();
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getSdcardDir() {
|
||
|
|
return Environment.getExternalStorageDirectory().getAbsolutePath();
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getSdcardDownloadDir() {
|
||
|
|
return getSdcardDir() + File.separator + "Download";
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getSdcardAppDownloadDir() {
|
||
|
|
String filePath = getSdcardDownloadDir() + File.separator + AppContextUtils.app.getPackageName();
|
||
|
|
FileUtils.createPaths(new File(filePath));
|
||
|
|
return filePath;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getDumpName(String prefix) {
|
||
|
|
return getDumpName(prefix, getTimeDecorate());
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getDumpRawName(String name) {
|
||
|
|
// 1A0B42C8(data.dump)|2020-02-20_15:49:06.dump
|
||
|
|
return RegexGroupUtils.matcherGroup(name, ".*\\((.*)\\)\\|.*", 1, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getDumpName(String prefix, String centerfix) {
|
||
|
|
return prefix + "|" + centerfix + ".dump";
|
||
|
|
}
|
||
|
|
|
||
|
|
public static String getDumpFile(String name) {
|
||
|
|
return FileUtils.getAppFilesDir("dump") + File.separator + name;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static DumpBean[] files2DumpBeans(File[] files, boolean isDescendingOrder, boolean isNeedNameSort) {
|
||
|
|
ArrayList<DumpBean> ret = new ArrayList<>();
|
||
|
|
if (files != null) {
|
||
|
|
for (File f : files) {
|
||
|
|
String name = f.getName();
|
||
|
|
DumpBean info = new DumpBean(name);
|
||
|
|
// 裁取UID!
|
||
|
|
String uid = RegexGroupUtils.matcherGroup(
|
||
|
|
name,
|
||
|
|
"(.*)\\|.*",
|
||
|
|
1,
|
||
|
|
0);
|
||
|
|
String time = RegexGroupUtils.matcherGroup(
|
||
|
|
name,
|
||
|
|
".*\\|(.*)\\..*",
|
||
|
|
1,
|
||
|
|
0);
|
||
|
|
if (uid != null && time != null) {
|
||
|
|
info.setUid(uid);
|
||
|
|
info.setTime(time);
|
||
|
|
} else {
|
||
|
|
info.setUid(f.getName());
|
||
|
|
if (time != null) {
|
||
|
|
info.setTime(time);
|
||
|
|
} else {
|
||
|
|
info.setTime("0000-00-00_00:00:00");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
info.setName(f.getName());
|
||
|
|
ret.add(info);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Comparator<DumpBean> nameComparator = new Comparator<DumpBean>() {
|
||
|
|
@Override
|
||
|
|
public int compare(DumpBean o1, DumpBean o2) {
|
||
|
|
if (isNeedNameSort) {
|
||
|
|
String uid1 = o1.getUid();
|
||
|
|
String uid2 = o2.getUid();
|
||
|
|
if (uid1 != null && uid2 != null) {
|
||
|
|
if (isDescendingOrder) {
|
||
|
|
return uid2.compareTo(uid1);
|
||
|
|
} else {
|
||
|
|
// 升序
|
||
|
|
return uid1.compareTo(uid2);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
// 不需要使用名称来排序则直接进行无操作状态返回!
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
Comparator<DumpBean> dateComparator = new Comparator<DumpBean>() {
|
||
|
|
@Override
|
||
|
|
public int compare(DumpBean o1, DumpBean o2) {
|
||
|
|
SimpleDateFormat format = new SimpleDateFormat(DEFAULT_TIME_DECRATE, Locale.getDefault());
|
||
|
|
try {
|
||
|
|
Date dt1 = format.parse(o1.getTime());
|
||
|
|
Date dt2 = format.parse(o2.getTime());
|
||
|
|
if (dt1 != null && dt2 != null) {
|
||
|
|
if (isDescendingOrder)
|
||
|
|
return Long.compare(dt2.getTime(), dt1.getTime());
|
||
|
|
else
|
||
|
|
return Long.compare(dt1.getTime(), dt2.getTime());
|
||
|
|
} else {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
} catch (Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
List<Comparator<DumpBean>> comparatorList = new ArrayList<>(Arrays.asList(nameComparator, dateComparator));
|
||
|
|
Collections.sort(ret, new Comparator<DumpBean>() {
|
||
|
|
@Override
|
||
|
|
public int compare(DumpBean o1, DumpBean o2) {
|
||
|
|
// 用实际的比较器比较!
|
||
|
|
for (Comparator<DumpBean> tmpComparator : comparatorList) {
|
||
|
|
if (tmpComparator.compare(o1, o2) > 0) {
|
||
|
|
return 1;
|
||
|
|
} else if (tmpComparator.compare(o1, o2) < 0) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return ret.toArray(new DumpBean[0]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static boolean addDumpFileToInternal(String name, byte[] content) {
|
||
|
|
// 取出内部的文件!
|
||
|
|
DumpBean[] dumpBeans = files2DumpBeans(listInternalDump(), false, false);
|
||
|
|
boolean canAdd = true;
|
||
|
|
for (DumpBean bean : dumpBeans) {
|
||
|
|
// 如果已经存在相同的名字的文件的话,则进行MD5检测!
|
||
|
|
String rawName = getDumpRawName(bean.getName());
|
||
|
|
LogUtils.d("******************************");
|
||
|
|
LogUtils.d("名称(bean): " + rawName);
|
||
|
|
LogUtils.d("名称(file): " + name);
|
||
|
|
LogUtils.d("******************************");
|
||
|
|
if (name.equals(rawName)) {
|
||
|
|
File dumpFile = new File(getDumpFile(bean.getName()));
|
||
|
|
LogUtils.d("已经存在该文件名,将会尝试进行对比:" + dumpFile.getName());
|
||
|
|
try {
|
||
|
|
byte[] dumpDigest = FileUtils.readBytes(dumpFile);
|
||
|
|
String digestStr = MD5Utils.digest(dumpDigest);
|
||
|
|
// 如果检测到MD5相同,则跳过添加,否则添加!
|
||
|
|
if (MD5Utils.verify(digestStr, content)) {
|
||
|
|
canAdd = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
} catch (IOException e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (canAdd) {
|
||
|
|
String perviousUID = "No UID.";
|
||
|
|
// 截取UID!
|
||
|
|
int type = DumpUtils.getType(content);
|
||
|
|
if (type == DumpUtils.TYPE_TXT) {
|
||
|
|
String[] datas = DumpUtils.getTxt(content);
|
||
|
|
if (datas != null) {
|
||
|
|
perviousUID = datas[0].substring(0, 8);
|
||
|
|
}
|
||
|
|
} else if (type == DumpUtils.TYPE_BIN) {
|
||
|
|
perviousUID = HexUtil.toHexString(content, 0, 4);
|
||
|
|
} else {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// 截取完整的源文件名字!
|
||
|
|
// 获得一个时间修饰!
|
||
|
|
String time = Commons.getTimeDecorate();
|
||
|
|
// 拼接将要保存到内部的文件名!
|
||
|
|
File target = new File(Commons.getDumpFile(
|
||
|
|
perviousUID
|
||
|
|
+ "("
|
||
|
|
+ name
|
||
|
|
+ ")|"
|
||
|
|
+ time
|
||
|
|
+ ".dump")
|
||
|
|
);
|
||
|
|
// 直接将已经读取出来的数据字节组写入到内部文件!
|
||
|
|
return FileUtils.copy(content, target);
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static int getMaxWidthOnChildren(AbsListView absListView) {
|
||
|
|
if (absListView == null || absListView.getAdapter() == null) {
|
||
|
|
// pre-condition
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
//int totalHeight = 0;
|
||
|
|
int maxWidth = 0;
|
||
|
|
for (int i = 0; i < absListView.getAdapter().getCount(); i++) {
|
||
|
|
View listItem = absListView.getAdapter().getView(i, null, absListView);
|
||
|
|
listItem.measure(0, 0);
|
||
|
|
//totalHeight += listItem.getMeasuredHeight();
|
||
|
|
int width = listItem.getMeasuredWidth();
|
||
|
|
if (width > maxWidth) maxWidth = width;
|
||
|
|
LogUtils.d("tmp: " + width);
|
||
|
|
}
|
||
|
|
return maxWidth;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 是否快速在30秒内反复重启!
|
||
|
|
public static boolean isAppFastRepeatedRestart() {
|
||
|
|
File appDir = FileUtils.getAppFilesDir("config");
|
||
|
|
if (appDir != null) {
|
||
|
|
int maxCount = 5;
|
||
|
|
String key = "FastRestart";
|
||
|
|
File timeFile = new File(appDir.getAbsolutePath() + File.separator + key + ".config");
|
||
|
|
if (!timeFile.exists()) FileUtils.createFile(timeFile);
|
||
|
|
// 判断反复重启的思路就是,存入5个时间段的时间戳,如果这五个时间段的总共距离不超过30s,则判定为快速反复重启!
|
||
|
|
try {
|
||
|
|
String[] values = new String[0];
|
||
|
|
if (DiskKVUtil.isKVExists(key, timeFile)
|
||
|
|
&& (values = DiskKVUtil.queryKVLine(key, timeFile)).length == maxCount) {
|
||
|
|
// 有五次记录,我们需要看他的时间!
|
||
|
|
long[] times = new long[values.length];
|
||
|
|
// 转换为时间戳!
|
||
|
|
for (int i = 0; i < times.length; i++) {
|
||
|
|
String tmpTimeStr0 = values[i];
|
||
|
|
times[i] = Long.valueOf(tmpTimeStr0);
|
||
|
|
}
|
||
|
|
// 进行排序!
|
||
|
|
Arrays.sort(times);
|
||
|
|
LogUtils.d(Arrays.toString(times));
|
||
|
|
// 获取最小值与当前时间对比!
|
||
|
|
long timeCount = 0;
|
||
|
|
for (int i = times.length - 1; i > 0; ) {
|
||
|
|
LogUtils.d("双方时间戳: " + times[i] + "," + times[i - 1]);
|
||
|
|
timeCount += times[i] - times[--i];
|
||
|
|
LogUtils.d("差值timeCount: " + timeCount);
|
||
|
|
}
|
||
|
|
System.arraycopy(times, 1, times, 0, times.length - 1);
|
||
|
|
//实现左移,然后最后一个位置更新距离开机的时间,如果最后一个时间和最开始时间小于DURATION,即连续5次启动
|
||
|
|
times[times.length - 1] = System.currentTimeMillis();
|
||
|
|
// 转换为时间戳重新写入!
|
||
|
|
for (int i = 0; i < values.length; i++) {
|
||
|
|
values[i] = String.valueOf(times[i]);
|
||
|
|
}
|
||
|
|
DiskKVUtil.update2Disk(key, values, timeFile);
|
||
|
|
boolean isValueFarAway = (System.currentTimeMillis() - times[0]) > ((1000 * 30));
|
||
|
|
if (timeCount < 1000 * 30 && !isValueFarAway) {
|
||
|
|
// 三十秒内超五次
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (values.length < maxCount) {
|
||
|
|
LogUtils.d("键值对(操作次数)不到五个,将会自动插入: " + values.length);
|
||
|
|
DiskKVUtil.insertKV(key, String.valueOf(System.currentTimeMillis()), timeFile);
|
||
|
|
} else {
|
||
|
|
DiskKVUtil.deleteKV(key, timeFile);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (IOException e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
LogUtils.d("配置目录不存在!");
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 是否拥有DEBUG的权限!
|
||
|
|
public static boolean isCanAccessRoot() {
|
||
|
|
// 进行信息检测,查找索引!
|
||
|
|
List<ResolveInfo> appList = AppListUtils.getInstalledApplication(AppContextUtils.app, false);
|
||
|
|
for (ResolveInfo resolveInfo : appList) {
|
||
|
|
if (SUB_DEBUG_PACK_NAME.equalsIgnoreCase(resolveInfo.activityInfo.packageName)) {
|
||
|
|
// 有SUB APP,可以通过放行!
|
||
|
|
appList.clear();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
appList.clear();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static void showDialogOnOfflineMode(Context context) {
|
||
|
|
if (!ChameleonExecutorProxy.getInstance().isConnected()) {
|
||
|
|
new AlertDialog.Builder(context)
|
||
|
|
.setTitle(R.string.tips)
|
||
|
|
.setMessage(R.string.tips_offline_mode)
|
||
|
|
.setPositiveButton(R.string.go2, new DialogInterface.OnClickListener() {
|
||
|
|
@Override
|
||
|
|
public void onClick(DialogInterface dialog, int which) {
|
||
|
|
context.startActivity(new Intent(context, DevicesFastActivity.class));
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.setCancelable(false)
|
||
|
|
.setNegativeButton(R.string.cancel, null).show();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 当前的操作是否是在主线程中执行的!
|
||
|
|
public static boolean isRunOnMainThread() {
|
||
|
|
return Thread.currentThread().getId()
|
||
|
|
== Looper.getMainLooper().getThread().getId();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return 获取本地包
|
||
|
|
*/
|
||
|
|
public static long getVerCode() {
|
||
|
|
long verCode = -1;
|
||
|
|
try {
|
||
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||
|
|
verCode = AppContextUtils.app.getPackageManager().getPackageInfo(
|
||
|
|
AppContextUtils.app.getPackageName(), 0).getLongVersionCode();
|
||
|
|
} else {
|
||
|
|
verCode = AppContextUtils.app.getPackageManager().getPackageInfo(
|
||
|
|
AppContextUtils.app.getPackageName(), 0).versionCode;
|
||
|
|
}
|
||
|
|
} catch (PackageManager.NameNotFoundException e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
return verCode;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return 获取本地包
|
||
|
|
*/
|
||
|
|
public static String getVerName() {
|
||
|
|
String verCode = "NoName";
|
||
|
|
try {
|
||
|
|
verCode = AppContextUtils.app.getPackageManager().getPackageInfo(
|
||
|
|
AppContextUtils.app.getPackageName(), 0).versionName;
|
||
|
|
} catch (PackageManager.NameNotFoundException e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
return verCode;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static void logStackTrace() {
|
||
|
|
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||
|
|
for (StackTraceElement stackTraceElement : stack) {
|
||
|
|
Log.d("logStackTrace", stackTraceElement.getClassName() + " }:{ " + stackTraceElement.getMethodName());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static void showExportSuccessDialog(@Nullable Activity activity, Uri path) {
|
||
|
|
if (activity == null) return;
|
||
|
|
activity.runOnUiThread(new Runnable() {
|
||
|
|
@Override
|
||
|
|
public void run() {
|
||
|
|
// 当前的目标使用的是外部路径!
|
||
|
|
new AlertDialog.Builder(activity)
|
||
|
|
.setTitle(R.string.tips)
|
||
|
|
.setCancelable(false)
|
||
|
|
.setMessage(activity.getString(R.string.tips_dump_export_success) + ": " + FileUtils.getFilePathByUri(path))
|
||
|
|
.setPositiveButton(R.string.ok, null)
|
||
|
|
.setNegativeButton(R.string.share, new DialogInterface.OnClickListener() {
|
||
|
|
@Override
|
||
|
|
public void onClick(DialogInterface dialog, int which) {
|
||
|
|
FileUtils.shareFile(activity, path);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.show();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 保存文件到临时目录!
|
||
|
|
public static String saveFileTemp(Context context, Uri uri) {
|
||
|
|
String internalPath = FileUtils.getAppFilesDir("temp").getPath();
|
||
|
|
// 截取完整的源文件名字!
|
||
|
|
String sourceName = FileUtils.getFileNameByUri(context, uri, false);
|
||
|
|
if (sourceName == null) {
|
||
|
|
String tempPath = FileUtils.getFilePathByUri(uri);
|
||
|
|
if (tempPath != null) {
|
||
|
|
sourceName = new File(tempPath).getName();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
String targetPath = internalPath + File.separator +
|
||
|
|
// 尝试拼接源文件名
|
||
|
|
(sourceName == null ? UUID.randomUUID() : sourceName);
|
||
|
|
try {
|
||
|
|
byte[] data = FileUtils.readBytes(uri);
|
||
|
|
File targetFile = new File(targetPath);
|
||
|
|
FileUtils.createFile(targetFile);
|
||
|
|
FileUtils.writeBytes(data, Uri.fromFile(targetFile));
|
||
|
|
} catch (IOException e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return targetPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static void showLowBatteryDialog(Context context, int percent) {
|
||
|
|
if (percent > 0 && percent < 10) {
|
||
|
|
Runnable runnable = new Runnable() {
|
||
|
|
@Override
|
||
|
|
public void run() {
|
||
|
|
new MaterialAlertDialog.Builder(context)
|
||
|
|
.setTitle(R.string.warning)
|
||
|
|
.setMessage(context.getString(R.string.tips_battery_low) + " : " + percent + "%")
|
||
|
|
.setStyle(new MaterialAlertDialog.OnWidgetStyle() {
|
||
|
|
@Override
|
||
|
|
public void onStyle(TextView title, TextView msg, Button btn1, Button btn2) {
|
||
|
|
title.setTextColor(AppResourceUtils.getColor(R.color.colorTextError));
|
||
|
|
}
|
||
|
|
}).show();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
if (isRunOnMainThread()) {
|
||
|
|
runnable.run();
|
||
|
|
} else {
|
||
|
|
new Handler(Looper.getMainLooper()).post(runnable);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 存放MAP
|
||
|
|
public static void setMap(String key, LinkedHashMap<String, String> datas) {
|
||
|
|
JSONArray mJsonArray = new JSONArray();
|
||
|
|
Iterator<Map.Entry<String, String>> iterator = datas.entrySet().iterator();
|
||
|
|
JSONObject object = new JSONObject();
|
||
|
|
while (iterator.hasNext()) {
|
||
|
|
Map.Entry<String, String> entry = iterator.next();
|
||
|
|
try {
|
||
|
|
object.put(entry.getKey(), entry.getValue());
|
||
|
|
} catch (JSONException e) {
|
||
|
|
}
|
||
|
|
}
|
||
|
|
mJsonArray.put(object);
|
||
|
|
editor.putString(key, mJsonArray.toString());
|
||
|
|
editor.commit();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 取出MAP
|
||
|
|
public static LinkedHashMap<String, String> getMap(String key) {
|
||
|
|
LinkedHashMap<String, String> datas = new LinkedHashMap<>();
|
||
|
|
String result = sp.getString(key, "");
|
||
|
|
try {
|
||
|
|
JSONArray array = new JSONArray(result);
|
||
|
|
for (int i = 0; i < array.length(); i++) {
|
||
|
|
JSONObject itemObject = array.getJSONObject(i);
|
||
|
|
JSONArray names = itemObject.names();
|
||
|
|
if (names != null) {
|
||
|
|
for (int j = 0; j < names.length(); j++) {
|
||
|
|
String name = names.getString(j);
|
||
|
|
String value = itemObject.getString(name);
|
||
|
|
datas.put(name, value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (JSONException e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
return datas;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static boolean isSelfBackground(Context context) {
|
||
|
|
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||
|
|
List<ActivityManager.RunningAppProcessInfo> appProcesses;
|
||
|
|
if (activityManager != null) {
|
||
|
|
appProcesses = activityManager.getRunningAppProcesses();
|
||
|
|
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
|
||
|
|
if (appProcess.processName.equals(context.getPackageName())) {
|
||
|
|
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
|
||
|
|
Log.i("后台", appProcess.processName);
|
||
|
|
return true;
|
||
|
|
} else {
|
||
|
|
Log.i("前台", appProcess.processName);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static int getAutoDisconnectTime() {
|
||
|
|
return sp.getInt(Properties.APP_AUTO_CLOSE_TIME_KEY, 300);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static void setAutoDisconnectTime(int time) {
|
||
|
|
editor.putInt(Properties.APP_AUTO_CLOSE_TIME_KEY, time).apply();
|
||
|
|
}
|
||
|
|
|
||
|
|
public static boolean isAutoDisconnect() {
|
||
|
|
return sp.getBoolean(Properties.APP_AUTO_CLOSE_KEY, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static void setAutoDisconnect(boolean enable) {
|
||
|
|
editor.putBoolean(Properties.APP_AUTO_CLOSE_KEY, enable).apply();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 获取当前的UI模式,
|
||
|
|
* 0 为跟随系统
|
||
|
|
* 1 为暗黑模式
|
||
|
|
* 2 为明亮模式
|
||
|
|
*/
|
||
|
|
public static int getUIMode() {
|
||
|
|
return sp.getInt(Properties.APP_UI_MODE, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 设置当前的UI模式,
|
||
|
|
* 0 为跟随系统
|
||
|
|
* 1 为暗黑模式
|
||
|
|
* 2 为明亮模式
|
||
|
|
*
|
||
|
|
* @param mode 新的模式
|
||
|
|
*/
|
||
|
|
public static void setUIMode(int mode) {
|
||
|
|
editor.putInt(Properties.APP_UI_MODE, mode).apply();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 获取当前的Dump模式
|
||
|
|
* 0 bin模式
|
||
|
|
* 1 hex模式
|
||
|
|
*/
|
||
|
|
public static int getDumpMode() {
|
||
|
|
return sp.getInt(Properties.APP_DUMP_MODE, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 设置当前的Dump模式
|
||
|
|
* 0 bin模式
|
||
|
|
* 1 hex模式
|
||
|
|
*/
|
||
|
|
public static void setDumpMode(int mode) {
|
||
|
|
editor.putInt(Properties.APP_DUMP_MODE, mode).apply();
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|