diff --git a/appmain/utils/tools/ArrayUtils.java b/appmain/utils/tools/ArrayUtils.java new file mode 100644 index 0000000..e49d53f --- /dev/null +++ b/appmain/utils/tools/ArrayUtils.java @@ -0,0 +1,48 @@ +package com.proxgrind.chameleon.utils.tools; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +/* + * 一些数组,集合操作的封装类! + * */ +public class ArrayUtils { + //将字符串链表或者数列转换为一般字符串数组 + public static T[] list2Arr(List list) { + if (list == null) return null; + if (list.size() == 0) return null; + return list.toArray((T[]) Array.newInstance(list.get(0).getClass(), list.size())); + } + + //obj数组去重 + public static T[] unrepeat(T[] objs) { + if (objs == null) return null; + if (objs.length == 0) return null; + ArrayList list = new ArrayList<>(); + for (int i = 0; i < objs.length; ++i) { + if (list.indexOf(objs[i]) == -1) { + //-1证明没有找到这个元素的存在,添加进去! + list.add(objs[i]); + } + } + return list.toArray((T[]) Array.newInstance(objs[0].getClass(), list.size())); + } + + /** + * 得到一个数组中的元素,如果越界则为空! + * + * @param array 需要被取出元素的数组! + * @param index 需要取出的对应数组的元素的索引! + * @return 如果不越界,则取出对应的值,否则直接返回null + */ + public static T getElement(T[] array, int index) { + return index >= array.length ? null : array[index]; + } + + // 得到距离数组开始的有效长度! + public static int getLength(T[] array, int offset) { + if (offset >= array.length) return -1; + return array.length - offset; + } +} diff --git a/appmain/utils/tools/AssetsUtil.java b/appmain/utils/tools/AssetsUtil.java new file mode 100644 index 0000000..85e9cbe --- /dev/null +++ b/appmain/utils/tools/AssetsUtil.java @@ -0,0 +1,99 @@ +package com.proxgrind.chameleon.utils.tools; + +import android.content.Context; +import android.content.res.AssetManager; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/* + * 针对Assets进行操作的类! + * */ +public class AssetsUtil { + + private Context context; + + public AssetsUtil(Context context) { + this.context = context; + } + + //判断assets文件是否存在! + public boolean isFileExists(String fileName) { + InputStream is = null; + boolean ret = false; + try { + is = context.getResources().getAssets().open(fileName); + ret = true; + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (is != null) + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return ret; + } + + //得到assets指定目录下的所有文件! + public String[] getFiles(String root) { + String[] files = new String[0]; + try { + files = context.getAssets().list(root); + } catch (IOException e) { + e.printStackTrace(); + } + if (files == null || files.length == 0) return null; + return files; + } + + //移动assets中的文件到指定目录 + public boolean moveFile(String asFile, String targetPath) { + File file = new File(targetPath); + if (!file.exists() || !file.isFile()) { + try { + if (!file.createNewFile()) { + return false; + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + boolean ret = false; + //移动默认key文件 + AssetManager am = context.getResources().getAssets(); + InputStream is = null; + FileOutputStream fos = null; + try { + is = am.open(asFile); + fos = new FileOutputStream(file); + int len = is.available(); + for (int i = 0; i < len; ++i) { + fos.write((byte) is.read()); + } + ret = true; + } catch (IOException e) { + e.printStackTrace(); + try { + if (is != null) is.close(); + if (fos != null) fos.close(); + ret = false; + } catch (IOException e1) { + e1.printStackTrace(); + } + } finally { + try { + if (is != null) is.close(); + if (fos != null) fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return ret; + } +} diff --git a/appmain/utils/tools/Commons.java b/appmain/utils/tools/Commons.java new file mode 100644 index 0000000..749a46d --- /dev/null +++ b/appmain/utils/tools/Commons.java @@ -0,0 +1,764 @@ +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 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 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 devList = new ArrayList<>(); + Set pairedDevices = btAdapter.getBondedDevices(); + if (pairedDevices == null) return null; + if (pairedDevices.size() > 0) { + ArrayList 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 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 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 nameComparator = new Comparator() { + @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 dateComparator = new Comparator() { + @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> comparatorList = new ArrayList<>(Arrays.asList(nameComparator, dateComparator)); + Collections.sort(ret, new Comparator() { + @Override + public int compare(DumpBean o1, DumpBean o2) { + // 用实际的比较器比较! + for (Comparator 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 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 datas) { + JSONArray mJsonArray = new JSONArray(); + Iterator> iterator = datas.entrySet().iterator(); + JSONObject object = new JSONObject(); + while (iterator.hasNext()) { + Map.Entry 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 getMap(String key) { + LinkedHashMap 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 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(); + } + +} diff --git a/appmain/utils/tools/CountDown.java b/appmain/utils/tools/CountDown.java new file mode 100644 index 0000000..e3ec940 --- /dev/null +++ b/appmain/utils/tools/CountDown.java @@ -0,0 +1,77 @@ +package com.proxgrind.chameleon.utils.tools; + +import java.util.Timer; +import java.util.TimerTask; + +/* + * 倒计时任务! + * */ +public class CountDown extends TimerTask { + + private int timeDelay = 1000; + private transient int countdynamic = 0; + private Progress mProgress; + private Timer timer; + + public CountDown(Progress progress) { + timer = new Timer(); + mProgress = progress; + } + + //倒计时回调接口 + public interface Progress { + boolean onProgress(int countdynamic); + } + + //设置回调! + public void setProgress(Progress progress) { + mProgress = progress; + } + + //设置每次的延迟 + public CountDown setTimeDelay(int ms) { + timeDelay = ms; + return this; + } + + //设置需要延迟多少次! + public CountDown setCountDown(int count) { + countdynamic = count; + return this; + } + + //开始倒计时! + public CountDown startCountDown() { + if (countdynamic == 0) return this; + timer.schedule(this, 0, timeDelay); + return this; + } + + //取消倒计时 + public CountDown cancelCountDown() { + cancel(); + countdynamic = 0; + return this; + } + + //是否处于倒计时状态! + public boolean isRunning() { + return this.countdynamic != 0; + } + + @Override + public void run() { + //判断为零,立刻回调结束 + if (countdynamic == 0) { + mProgress.onProgress(0); + cancelCountDown(); + } else { + //回调且判断需不需要继续下次回调! + if (!mProgress.onProgress(countdynamic)) { + cancelCountDown(); + return; + } + --countdynamic; + } + } +} diff --git a/appmain/utils/tools/DiskKVUtil.java b/appmain/utils/tools/DiskKVUtil.java new file mode 100644 index 0000000..27b4a16 --- /dev/null +++ b/appmain/utils/tools/DiskKVUtil.java @@ -0,0 +1,220 @@ +package com.proxgrind.chameleon.utils.tools; + +import android.util.Log; + +import com.proxgrind.chameleon.utils.stream.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +/** + * @author DXL + * @Date 2019/4/12 + */ +public class DiskKVUtil { + + private static String LOG_TAG = "DiskKVUtil"; + + /** + * 封装由设置写到持久化层的实现 + * + * @param key 键 + * @param value 值 + * @param file 最终写入的文件 + */ + public static void update2Disk(String key, String value, File file) throws IOException { + //由键值对到底层持久化的实现 + byte[] dataBuf = FileUtils.readBytes(file); + String[] dats = new String(dataBuf).split("\n"); + //迭代判断行! + for (int i = 0; i < dats.length; i++) { + String kvLine = dats[i]; + //如果是注释则跳过处理 + if (kvLine.startsWith("#")) continue; + //否则判断键值对! + if (kvLine.matches(".* : .*")) { + if (kvLine.startsWith(key)) { + //判断键是否对应行开头,更新具体内容! + String newKVLine = warp2KvLine(key, value); + dats[i] = newKVLine; + } + } + } + String result = StringUtils.arr2Str(dats, "\n", true); + //重新写入到磁盘! + FileUtils.writeBytes(result.getBytes(), file, false); + } + + /** + * 封装由设置写到持久化层的实现 + * + * @param key 键 + * @param value 值,数组形式,表示一键多值! + * @param file 最终写入的文件 + */ + public static void update2Disk(String key, String[] value, File file) throws IOException { + //由键值对到底层持久化的实现 + byte[] dataBuf = FileUtils.readBytes(file); + String[] dats = new String(dataBuf).split("\n"); + //迭代判断行! + for (int i = 0, j = 0; i < dats.length; i++) { + String kvLine = dats[i]; + //如果是注释则跳过处理 + if (kvLine.startsWith("#")) continue; + //否则判断键值对! + if (kvLine.matches(".* : .*")) { + if (kvLine.startsWith(key)) { + if (j >= value.length) { + //可能会越界! + Log.d(LOG_TAG, "update2Disk 有越界风险!"); + } else { + //判断键是否对应行开头,更新具体内容! + String newKVLine = warp2KvLine(key, value[j]); + j++; + dats[i] = newKVLine; + } + } + } + } + String result = StringUtils.arr2Str(dats, "\n", true); + //重新写入到磁盘! + FileUtils.writeBytes(result.getBytes(), file, false); + } + + /** + * 查询键值对! + * + * @param key 键 + * @param file 欲操作的文件 + * @return 查询的结果集(值)! + * @throws IOException 操作出现的各种问题! + */ + public static String[] queryKVLine(String key, File file) throws IOException { + byte[] dataBuf = FileUtils.readBytes(file); + String[] dats = new String(dataBuf).split("\n"); + ArrayList valueList = new ArrayList<>(16); + for (String line : dats) { + //如果是注释则跳过处理 + if (line.startsWith("#")) continue; + if (line.startsWith(key)) { + valueList.add(getValue(line)); + } + } + return valueList.toArray(new String[0]); + } + + /** + * 查询键是否存在! + * + * @param key 键 + * @param file 查询的文件! + * @return 对应的键是否存在于配置文件中! + */ + public static boolean isKVExists(String key, File file) throws IOException { + byte[] dataBuf = FileUtils.readBytes(file); + String[] dats = new String(dataBuf).split("\n"); + for (String line : dats) { + //如果是注释则跳过处理 + if (line.startsWith("#")) continue; + if (line.startsWith(key)) { + return true; + } + } + return false; + } + + /** + * @param key 键 + * @param value 值 + * @param file 欲操作的文件! + */ + public static void insertKV(String key, String value, File file) throws IOException { + String line = warp2KvLine(key, value) + "\n"; + FileUtils.writeBytes(line.getBytes(), file, true); + } + + /** + * 凡是匹配到的键信息行,全部略过,然后将区间外的行写回去! + * + * @param key 键 + * @param file 操作的文件 + */ + public static void deleteKV(String key, File file) throws IOException { + byte[] dataBuf = FileUtils.readBytes(file); + String[] dats = new String(dataBuf).split("\n"); + ArrayList valueList = new ArrayList<>(16); + for (String line : dats) { + if (!line.startsWith(key)) { + valueList.add(getValue(line)); + } + } + String ret = StringUtils.arr2Str(valueList.toArray(new String[0]), "\n", true); + FileUtils.writeBytes(ret.getBytes(), file, false); + } + + public static char toChar(String v, char defaultVar) { + try { + return v.toCharArray()[0]; + } catch (Exception e) { + return defaultVar; + } + } + + public static short toShort(String v, short defaultVar) { + try { + return Short.valueOf(v); + } catch (Exception e) { + return defaultVar; + } + } + + public static int toInt(String v, int defaultVar) { + try { + return Integer.valueOf(v); + } catch (Exception e) { + return defaultVar; + } + } + + public static long toLong(String v, long defaultVar) { + try { + return Long.valueOf(v); + } catch (Exception e) { + return defaultVar; + } + } + + public static float toFloat(String v, float defaultVar) { + try { + return Float.valueOf(v); + } catch (Exception e) { + return defaultVar; + } + } + + public static double toDouble(String v, double defaultVar) { + try { + return Double.valueOf(v); + } catch (Exception e) { + return defaultVar; + } + } + + public static boolean toBoolean(String v, boolean defaultVar) { + try { + return Boolean.valueOf(v); + } catch (Exception e) { + return defaultVar; + } + } + + private static String getValue(String line) { + //pm3DelayTime : + return RegexGroupUtils.matcherGroup(line, ".* : (.*)", 1, 0); + } + + private static String warp2KvLine(String key, String value) { + return key + " : " + value; + } +} diff --git a/appmain/utils/tools/FragmentUtils.java b/appmain/utils/tools/FragmentUtils.java new file mode 100644 index 0000000..a75e015 --- /dev/null +++ b/appmain/utils/tools/FragmentUtils.java @@ -0,0 +1,41 @@ +package com.proxgrind.chameleon.utils.tools; + +import android.app.Activity; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import com.proxgrind.chameleon.R; + +import java.util.List; + +/* + * 一些碎片开发使用上的工具! + * */ +public class FragmentUtils { + + //隐藏所有的碎片! + public static void hides(FragmentManager manager, Fragment exclude) { + List fragmentList = manager.getFragments(); + //迭代隐藏所有的碎片! + for (Fragment fragment : fragmentList) + //排除指定的碎片! + if (fragment != exclude) + manager.beginTransaction().hide(fragment).commitAllowingStateLoss(); + } + + public static void showAndAddBackStack(FragmentActivity activity, Fragment fragment) { + if (activity == null || fragment == null) return; + FragmentManager manager = activity.getSupportFragmentManager(); + manager.beginTransaction() + .add(R.id.mainContainer, fragment) + .addToBackStack(fragment.getTag()).commit(); + } + + public static void runOnUiThread(Activity activity, Runnable runnable) { + if (activity != null) + activity.runOnUiThread(runnable); + } +} diff --git a/appmain/utils/tools/GlobalTag.java b/appmain/utils/tools/GlobalTag.java new file mode 100644 index 0000000..38a5755 --- /dev/null +++ b/appmain/utils/tools/GlobalTag.java @@ -0,0 +1,44 @@ +package com.proxgrind.chameleon.utils.tools; + +import android.nfc.Tag; + +import java.util.ArrayList; +import java.util.List; + +/** + * 全局的标签缓存! + */ +public class GlobalTag { + private static Tag tag = null; + private static List listeners = new ArrayList<>(); + + public static Tag getTag() { + return tag; + } + + public static void setTag(Tag tag) { + GlobalTag.tag = tag; + } + + public interface OnNewTagListener { + void onNewTag(Tag tag); + } + + public static void addListener(OnNewTagListener listener) { + listeners.add(listener); + } + + public static void removeListener(OnNewTagListener listener) { + listeners.remove(listener); + } + + public static void notifyOnNewTag(Tag tag) { + //直接通知TAG的变化! + for (OnNewTagListener l : listeners) { + try { + l.onNewTag(tag); + } catch (Exception ignored) { + } + } + } +} diff --git a/appmain/utils/tools/HexUtil.java b/appmain/utils/tools/HexUtil.java new file mode 100644 index 0000000..2d50cce --- /dev/null +++ b/appmain/utils/tools/HexUtil.java @@ -0,0 +1,261 @@ +package com.proxgrind.chameleon.utils.tools; + +import android.util.Log; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +public class HexUtil { + private final static char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + public static String dumpHexString(byte[] array) { + return dumpHexString(array, 0, array.length); + } + + public static String dumpHexString(byte[] array, int offset, int length) { + StringBuilder result = new StringBuilder(); + + byte[] line = new byte[16]; + int lineIndex = 0; + + result.append("\n0x"); + result.append(toHexString(offset)); + + for (int i = offset; i < offset + length; i++) { + if (lineIndex == 16) { + result.append(" "); + + for (int j = 0; j < 16; j++) { + if (line[j] > ' ' && line[j] < '~') { + result.append(new String(line, j, 1)); + } else { + result.append("."); + } + } + + result.append("\n0x"); + result.append(toHexString(i)); + lineIndex = 0; + } + + byte b = array[i]; + result.append(" "); + result.append(HEX_DIGITS[(b >>> 4) & 0x0F]); + result.append(HEX_DIGITS[b & 0x0F]); + + line[lineIndex++] = b; + } + + if (lineIndex != 16) { + int count = (16 - lineIndex) * 3; + count++; + for (int i = 0; i < count; i++) { + result.append(" "); + } + + for (int i = 0; i < lineIndex; i++) { + if (line[i] > ' ' && line[i] < '~') { + result.append(new String(line, i, 1)); + } else { + result.append("."); + } + } + } + + return result.toString(); + } + + public static String toHexString(byte b) { + return toHexString(toByteArray(b)); + } + + public static String toHexString(byte[] array) { + if (array == null) return null; + return toHexString(array, 0, array.length); + } + + public static String toHexString(byte[] array, int offset, int length) { + char[] buf = new char[length * 2]; + + int bufIndex = 0; + for (int i = offset; i < offset + length; i++) { + byte b = array[i]; + buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; + buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; + } + + return new String(buf); + } + + public static String toHexString(int i) { + return toHexString(toByteArray(i)); + } + + public static String toHexString(short i) { + return toHexString(toByteArray(i)); + } + + public static String decorateHex(String hexStr) { + StringBuilder sb = new StringBuilder(); + char[] charArr = hexStr.toCharArray(); + for (int i = 0; i < charArr.length; i += 2) { + sb.append("0x").append(charArr[i]) + .append(charArr[i + 1]).append(" "); + } + return sb.toString(); + } + + public static byte[] toByteArray(byte b) { + byte[] array = new byte[1]; + array[0] = b; + return array; + } + + public static byte[] toByteArray(int i) { + byte[] array = new byte[4]; + + array[3] = (byte) (i & 0xFF); + array[2] = (byte) ((i >> 8) & 0xFF); + array[1] = (byte) ((i >> 16) & 0xFF); + array[0] = (byte) ((i >> 24) & 0xFF); + + return array; + } + + public static byte[] toByteArray(short i) { + byte[] array = new byte[2]; + + array[1] = (byte) (i & 0xFF); + array[0] = (byte) ((i >> 8) & 0xFF); + + return array; + } + + private static int toByte(char c) { + if (c >= '0' && c <= '9') + return (c - '0'); + if (c >= 'A' && c <= 'F') + return (c - 'A' + 10); + if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + Log.d("HexUtil", "The byte char is invalid: " + c); + //throw new RuntimeException("Invalid hex char '" + c + "'"); + return 0; + } + + public static int toInt(byte b) { + return b & 0xFF; + } + + public static int toIntFrom2Byte(byte[] b) { + return Integer.parseInt(toHexString(b), 16); + } + + /** + * 拆分byte数组 + * + * @param bytes 要拆分的数组 + * @param size 要按几个组成一份 + * @return + */ + public static byte[][] splitBytes(byte[] bytes, int size) { + double splitLength = Double.parseDouble(size + ""); + int arrayLength = (int) Math.ceil(bytes.length / splitLength); + byte[][] result = new byte[arrayLength][]; + int from, to; + for (int i = 0; i < arrayLength; i++) { + from = (int) (i * splitLength); + to = (int) (from + splitLength); + if (to > bytes.length) + to = bytes.length; + result[i] = Arrays.copyOfRange(bytes, from, to); + } + return result; + } + + public static byte[] bytesMerge(byte[]... arrs) { + byte[] ret = new byte[byteArraysLength(arrs)]; + int pos = 0; + for (byte[] tmp : arrs) { + if (tmp != null && tmp.length > 0) { + System.arraycopy(tmp, 0, ret, pos, tmp.length); + pos += tmp.length; + } + } + return ret; + } + + public static int byteArraysLength(byte[]... arrs) { + int ret = 0; + for (byte[] tmp : arrs) { + if (tmp != null) + ret += tmp.length; + } + return ret; + } + + public static int byteArrayToInt(byte[] b) { + return (b[3] & 0xFF) | + (b[2] & 0xFF) << 8 | + (b[1] & 0xFF) << 16 | + (b[0] & 0xFF) << 24; + } + + public static int byte2Int(byte[] data, int offset) { + byte[] bs = new byte[4]; + for (int i = offset, j = 0; j < bs.length; i++, j++) { + bs[j] = data[i]; + } + return HexUtil.byteArrayToInt(bs); + } + + public static int hexString2Int(String hexString) { + byte[] hexByte = hexStringToByteArray(hexString); + return byteArrayToInt(hexByte); + } + + public static byte[] intToByteArray(int a) { + return new byte[]{ + (byte) ((a >> 24) & 0xFF), + (byte) ((a >> 16) & 0xFF), + (byte) ((a >> 8) & 0xFF), + (byte) (a & 0xFF) + }; + } + + public static byte[] hexStringToByteArray(String hexString) { + if (hexString == null) return null; + if (hexString.length() == 0) return null; + int length = hexString.length(); + byte[] buffer = new byte[length / 2]; + for (int i = 0; i < length; i += 2) { + buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString + .charAt(i + 1))); + } + return buffer; + } + + public static byte[] getAsciiBytes(String str) { + return str.getBytes(StandardCharsets.US_ASCII); + } + + //判断是否是十六进制格式的字符串 + public static boolean isHexString(String str) { + if (str == null) return false; + if (str.matches("[0-9a-fA-F]+")) return true; + if (str.matches("0x[0-9a-fA-F]+")) return true; + return str.matches("0x[0-9a-fA-F] +"); + } + + public static boolean sequenceEqual(byte[] a, byte[] b, int len) { + if (len > a.length && len > b.length) return false; + int minLen = Math.min(a.length, b.length); + if (minLen > len) return false; + for (int i = 0; i < minLen; ++i) { + if (a[i] != b[i]) return false; + } + return true; + } +} diff --git a/appmain/utils/tools/MD5Utils.java b/appmain/utils/tools/MD5Utils.java new file mode 100644 index 0000000..f887be0 --- /dev/null +++ b/appmain/utils/tools/MD5Utils.java @@ -0,0 +1,33 @@ +package com.proxgrind.chameleon.utils.tools; + +import java.security.MessageDigest; + +//MD5加密不可逆 +public class MD5Utils { + /** + * 对字节内容进行加密 + * + * @param source 摘要对象! + * @return 密文 + */ + public static String digest(byte[] source) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + return HexUtil.toHexString(digest.digest(source)); + } catch (Exception ex) { + ex.printStackTrace(); + return ""; + } + } + + /** + * 对字节内容进行摘要检查! + * + * @param digest 已经确定的摘要信息! + * @param source 未知的摘要对象! + * @return 摘要信息对比结果! + */ + public static boolean verify(String digest, byte[] source) { + return digest(source).equals(digest); + } +} diff --git a/appmain/utils/tools/MifareUtils.java b/appmain/utils/tools/MifareUtils.java new file mode 100644 index 0000000..4a36329 --- /dev/null +++ b/appmain/utils/tools/MifareUtils.java @@ -0,0 +1,105 @@ +package com.proxgrind.chameleon.utils.tools; + +/* + * MifareClassic标签用得到的工具! + * */ +public class MifareUtils { + + public static boolean validateSector(int sector) { + // Do not be too strict on upper bounds checking, since some cards + // have more addressable memory than they report. For example, + // MIFARE Plus 2k cards will appear as MIFARE Classic 1k cards when in + // MIFARE Classic compatibility mode. + // Note that issuing a command to an out-of-bounds block is safe - the + // tag should report error causing IOException. This validation is a + // helper to guard against obvious programming mistakes. + + int NR_TRAILERS_4k = 40; + if (sector < 0 || sector >= NR_TRAILERS_4k) { + return false; + } + return true; + } + + public static boolean validateBlock(int block) { + // Just looking for obvious out of bounds... + int NR_BLOCKS_4k = 0xFF; + if (block < 0 || block >= NR_BLOCKS_4k) { + return false; + } + return true; + } + + public static boolean validateValueOperand(int value) { + if (value < 0) { + return false; + } + return true; + } + + public static int blockToSector(int blockIndex) { + if (!validateBlock(blockIndex)) return 0; + if (blockIndex < 32 * 4) { + return (blockIndex / 4); + } else { + return (32 + (blockIndex - 32 * 4) / 16); + } + } + + public static int sectorToBlock(int sectorIndex) { + if (!validateSector(sectorIndex)) { + return -1; + } + if (sectorIndex < 32) { + return (sectorIndex * 4); + } else { + return (32 * 4 + (sectorIndex - 32) * 16); + } + } + + public static boolean isFirstBlock(int uiBlock) { + // 测试我们是否处于小扇区或者大扇区? + if (uiBlock < 128) + return ((uiBlock) % 4 == 0); + else + return ((uiBlock) % 16 == 0); + } + + public static boolean isTrailerBlock(int uiBlock) { + // 测试我们处于小区块还是大扇区 + if (uiBlock < 128) + return ((uiBlock + 1) % 4 == 0); + else + return ((uiBlock + 1) % 16 == 0); + } + + public static int getBlockCountInSector(int sectorIndex) { + if (!validateSector(sectorIndex)) return -1; + if (sectorIndex < 32) { + return 4; + } else { + return 16; + } + } + + public static int get_trailer_block(int uiFirstBlock) { + // Test if we are in the small or big sectors + int trailer_block; + if (uiFirstBlock < 128) { + trailer_block = uiFirstBlock + (3 - (uiFirstBlock % 4)); + } else { + trailer_block = uiFirstBlock + (15 - (uiFirstBlock % 16)); + } + return trailer_block; + } + + public static int getIndexOnSector(int block, int sector) { + int index = 0; + //得到当前的块在扇区中的具体索引! + for (int i = 0; i < getBlockCountInSector(sector); i++) { //得到当前扇区的块总数! + if (block == (sectorToBlock(block) + i)) break; + ++index; + } + return index; + } +} diff --git a/appmain/utils/tools/NetworkUtils.java b/appmain/utils/tools/NetworkUtils.java new file mode 100644 index 0000000..6454059 --- /dev/null +++ b/appmain/utils/tools/NetworkUtils.java @@ -0,0 +1,195 @@ +package com.proxgrind.chameleon.utils.tools; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.Enumeration; + +public class NetworkUtils { + /** + * 是否使用代理(WiFi状态下的,避免被抓包) + */ + public static boolean isWifiProxy() { + String proxyAddress; + int proxyPort; + proxyAddress = System.getProperty("http.proxyHost"); + String portstr = System.getProperty("http.proxyPort"); + proxyPort = Integer.parseInt((portstr != null ? portstr : "-1")); + System.out.println(proxyAddress + "~"); + System.out.println("port = " + proxyPort); + return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1); + } + + /** + * 是否正在使用VPN + */ + public static boolean isVpnUsed() { + try { + Enumeration niList = NetworkInterface.getNetworkInterfaces(); + if (niList != null) { + for (Object intf : Collections.list(niList)) { + NetworkInterface ni = (NetworkInterface) intf; + if (!ni.isUp() || ni.getInterfaceAddresses().size() == 0) { + continue; + } + Log.d("-----", "isVpnUsed() NetworkInterface Name: " + ni.getName()); + if ("tun0".equals(ni.getName()) || "ppp0".equals(ni.getName())) { + return true; // The VPN is up + } + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + return false; + } + + /** + * 网络是否连接! + * + * @param context 上下文 + * @return 有网时返回true,没网时返回false + */ + public static boolean isNetworkConnected(Context context) { + if (context != null) { + ConnectivityManager mConnectivityManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); + if (mNetworkInfo != null) { + return mNetworkInfo.isAvailable(); + } + } + return false; + } + + /** + * wifi是否连接 + * + * @param context 上下文 + * @return 是WIFI网络返回true,不是WIFI返回false + */ + public static boolean isWifiConnected(Context context) { + if (context != null) { + ConnectivityManager mConnectivityManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo mWiFiNetworkInfo = mConnectivityManager + .getNetworkInfo(ConnectivityManager.TYPE_WIFI); + if (mWiFiNetworkInfo != null) { + return mWiFiNetworkInfo.isAvailable(); + } + } + return false; + } + + /** + * 移动网络是否连接 + * + * @param context 上下文 + * @return 是数据流量时返回true,不是返回false + */ + public static boolean isMobileConnected(Context context) { + if (context != null) { + ConnectivityManager mConnectivityManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo mMobileNetworkInfo = mConnectivityManager + .getNetworkInfo(ConnectivityManager.TYPE_MOBILE); + if (mMobileNetworkInfo != null) { + return mMobileNetworkInfo.isAvailable(); + } + } + return false; + } + + /** + * 获取当前网络连接的类型信息 + * + * @param context 上下文 + * @return 判断结果, + */ + public static int getConnectedType(Context context) { + if (context != null) { + ConnectivityManager mConnectivityManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); + if (mNetworkInfo != null && mNetworkInfo.isAvailable()) { + return mNetworkInfo.getType(); + } + } + return -1; + } + + /** + * 判断主机是否存在! + * + * @param host 主机 + * @param timeout 超时值! + */ + public static boolean isHostReachable(String host, int timeout) { + try { + return InetAddress.getByName(host).isReachable(timeout); + } catch (UnknownHostException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + /** + * 判断主机是否可以连接! + * + * @param host 主机 + * @param port 端口 + * @param timeout 超时值 + */ + public static boolean isHostConnectable(String host, int port, int timeout) { + Socket socket = new Socket(); + try { + socket.connect(new InetSocketAddress(host, port), timeout); + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + try { + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return true; + } + + /** + * 判断主机是否可以连接! + * + * @param host 主机 + * @param port 端口 + */ + public static boolean isHostConnectable(String host, int port) { + Socket socket = new Socket(); + try { + socket.connect(new InetSocketAddress(host, port)); + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + try { + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return true; + } + +} \ No newline at end of file diff --git a/appmain/utils/tools/Properties.java b/appmain/utils/tools/Properties.java new file mode 100644 index 0000000..0d54add --- /dev/null +++ b/appmain/utils/tools/Properties.java @@ -0,0 +1,31 @@ +package com.proxgrind.chameleon.utils.tools; + +import com.proxgrind.chameleon.utils.stream.FileUtils; + +import java.io.File; + +public interface Properties { + String APP_SDCARD = FileUtils.getAppFilesDir("files").getAbsolutePath(); + String APP_SETTINH_DIR = "settings"; + String APP_SETTINGS_NAME = "settings.ky"; + // APP的配置文件保存目录! + String APP_CONF_PATH = APP_SDCARD + File.separator + APP_SETTINH_DIR; + // app的通用配置文件! + String APP_CONF_FILE = APP_CONF_PATH + File.separator + APP_SETTINGS_NAME; + // 设备的自动关闭状态设置key! + String APP_AUTO_CLOSE_KEY = "autoClose"; + // 设备的自动断开时间! + String APP_AUTO_CLOSE_TIME_KEY = "autoCloseTime"; + // 新手模式的标志! + String APP_NOVICE_MODE = "noviceMode"; + // 绑定的mac地址 + String APP_BIND_MAC = "bindMac"; + // 是否启用了MAC地址绑定! + String APP_BIND_MAC_STATUS = "bindMacStatus"; + // 设备备注 + String APP_DEVICE_REMARKS = "device_remarks"; + // UI模式 + String APP_UI_MODE = "ui_mode"; + // DUMP模式 + String APP_DUMP_MODE = "dump_mode"; +} diff --git a/appmain/utils/tools/RegexGroupUtils.java b/appmain/utils/tools/RegexGroupUtils.java new file mode 100644 index 0000000..c582623 --- /dev/null +++ b/appmain/utils/tools/RegexGroupUtils.java @@ -0,0 +1,51 @@ +package com.proxgrind.chameleon.utils.tools; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/* + * TODO 正则组匹配工具类 + * 根据给出的组标签反选或者正选内容! + * */ +public class RegexGroupUtils { + + /*public static void main(String args[]) { + String c = "UID : 8b 36 8b eb"; + String s = matcherGroup(c, ".*UID : (.{11}).*", 1, 0); + System.out.println("最终测试组匹配截取: " + s); + }*/ + + /** + * @param content 被匹配的内容! + * @param regex 匹配规则 + * @return 匹配的组! + * @Method 匹配组多个,根据组得到内容结果! + */ + public static String[] matcherGroups(String content, String regex, int group) { + //准备格式匹配对象! + Pattern p = Pattern.compile(regex); + //准备匹配器对象! + Matcher m = p.matcher(content); + //结果集! + ArrayList ret = new ArrayList<>(); + //迭代查组 + while (m.find()) { + ret.add(m.group(group)); + } + return ret.toArray(new String[0]); + } + + /* + * Method 匹配一个组 + * @Param content 被匹配的内容! + * @Param regex 被匹配的正则! + * @Param index 组索引,位于字符串中的第一次pos + * @Return 返回指定索引的组内容! + * */ + public static String matcherGroup(String content, String regex, int group, int index) { + String[] res = matcherGroups(content, regex, group); + if (index >= res.length || index < 0) return null; + return res[index]; + } +} diff --git a/appmain/utils/tools/StringUtils.java b/appmain/utils/tools/StringUtils.java new file mode 100644 index 0000000..62c8807 --- /dev/null +++ b/appmain/utils/tools/StringUtils.java @@ -0,0 +1,69 @@ +package com.proxgrind.chameleon.utils.tools; + +public class StringUtils { + + //字符串数组转字符串,可选择性的添加分隔符! + public static String arr2Str(String[] arr, String split, boolean addSplitInEnd) { + StringBuilder sb = new StringBuilder(); + //进行迭代添加! + for (int i = 0; i < arr.length; ++i) { + //判断是否是在尾部 + if (i == arr.length - 1 && !addSplitInEnd) { + //不添加换行符! + sb.append(arr[i]); + } else { + sb.append(arr[i]).append(split); + } + } + return sb.toString(); + } + + //是否是空串 + public static boolean isEmpty(String str) { + return str.isEmpty() || str.equals(" ") || str.matches("\\s*"); + } + + //是否是十六进制字符串 + public static boolean isHexStr(String str) { + return str.matches("[0-9a-fA-F]+"); + } + + //是否是数字 + public static boolean isNumStr(String str) { + return str.matches("[0-9]+"); + } + + //是否是字母 + public static boolean isLetter(String str) { + return str.matches("[A-Fa-f]+"); + } + + //是否是纯空格 + public static boolean isSpaces(String str) { + return str.matches(" +"); + } + + //删除所有的空格和转为大写! + public static String trimO2Upper(String content) { + return content.replaceAll(" ", "").toUpperCase(); + } + + //删除所有的空格和转为小写! + public static String trimO2Lower(String content) { + return content.replaceAll(" ", "").toLowerCase(); + } + + //删除所有的空格和转为大写! + public static void trimO2Upper(String[] datas) { + for (int i = 0; i < datas.length; i++) { + datas[i] = trimO2Upper(datas[i]); + } + } + + //删除所有的空格和转为小写! + public static void trimO2Lower(String[] datas) { + for (int i = 0; i < datas.length; i++) { + datas[i] = trimO2Lower(datas[i]); + } + } +} diff --git a/appmain/utils/tools/TextStyleUtils.java b/appmain/utils/tools/TextStyleUtils.java new file mode 100644 index 0000000..4aa1539 --- /dev/null +++ b/appmain/utils/tools/TextStyleUtils.java @@ -0,0 +1,44 @@ +package com.proxgrind.chameleon.utils.tools; + +import android.content.Context; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.TextAppearanceSpan; + +/* + * 设置文本的样式! + * */ +public class TextStyleUtils { + + /* + * 合并多个富文本对象到构造器里! + * */ + public static SpannableStringBuilder merge(SpannableString... text) { + SpannableStringBuilder ssb = new SpannableStringBuilder(); + for (SpannableString ss : text) { + ssb.append(ss); + } + return ssb; + } + + /* + * 设置字体前景色! + * */ + public static SpannableString getColorString(String str, int color) { + SpannableString ret = new SpannableString(str); + ret.setSpan(new ForegroundColorSpan(color), 0, str.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + return ret; + } + + /* + * 设置背景样式! + * */ + public static SpannableString getStyleString(Context context, String str, int style) { + // TextAppearanceSpan 文本外貌(包括字体、大小、样式和颜色) + SpannableString ret = new SpannableString(str); + ret.setSpan(new TextAppearanceSpan(context, style), 0, str.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + return ret; + } +} diff --git a/appmain/utils/tools/ViewUtils.java b/appmain/utils/tools/ViewUtils.java new file mode 100644 index 0000000..3a5c801 --- /dev/null +++ b/appmain/utils/tools/ViewUtils.java @@ -0,0 +1,53 @@ +package com.proxgrind.chameleon.utils.tools; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ScrollView; + +public class ViewUtils { + //加载layout文件并且返回view引用 + public static View inflate(Context context, int layID) { + View v = LayoutInflater.from(context).inflate(layID, null); + + return v; + } + + public static void measureUnspecified(View view) { + view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + } + + // ScrollView自动滑动到底部! + public static void fullScroll(ScrollView scrollView) { + scrollView.getViewTreeObserver() + .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + scrollView.post(new Runnable() { + public void run() { + scrollView.fullScroll(View.FOCUS_DOWN); + } + }); + } + }); + } + + //给编辑器请求焦点和虚拟键盘 + public static void requestFocusAndShowInputMethod(EditText edt) { + if (edt == null) return; + edt.post(new Runnable() { + @Override + public void run() { + edt.setFocusable(true); + edt.setFocusableInTouchMode(true); + edt.requestFocus(); + InputMethodManager imm = (InputMethodManager) edt.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_NOT_ALWAYS); + } + }); + } +}