Files
ElderlyDialer/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java
TongTongStudio 4e93deaf65 refactor(mqtt): 优化MQTT连接管理与设备状态上报
- 使用DeviceStatus对象替代手动拼接JSON字符串
- 启用HiveMQ自动重连机制,移除自定义重连逻辑
- 添加连接状态标记防止并发重复建连
- 优化断线回调,记录服务端断开原因
- 使用设备序列号作为MQTT客户端标识
- 新增信号强度和真实屏幕分辨率获取方法
- 修复SystemUtils中系统属性获取方式
- 添加DeviceStatus单元测试
2026-08-12 08:27:33 +08:00

1412 lines
55 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.ttstd.dialer.utils;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityTaskManager;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.provider.Settings;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrength;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.WindowManager;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.security.MessageDigest;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Observer;
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import androidx.annotation.RequiresApi;
public class SystemUtils {
private static final String TAG = "SystemUtils";
/**
* 获取系统配置信息
*
* @param key
* @param defaultValue
* @return
*/
public static String getProperty(String key, String defaultValue) {
// SystemProperties.get();
try {
Class<?> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class, String.class);
return (String) (get.invoke(c, key, defaultValue));
} catch (Exception e) {
e.printStackTrace();
return defaultValue;
}
}
/**
* 获取设备序列号
*
* @return
*/
@SuppressLint("MissingPermission")
public static String getSerial() {
String serial = Build.UNKNOWN;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {//9.0+
serial = Build.getSerial();
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+
serial = Build.SERIAL;
} else {//8.0-
// Class<?> c = Class.forName("android.os.SystemProperties");
// Method get = c.getMethod("get", String.class);
// serial = (String) get.invoke(c, "ro.serialno");
getProperty("ro.serialno", serial);
}
} catch (Exception e) {
e.printStackTrace();
Logger.e("e", "读取设备序列号异常:" + e.toString());
}
return serial;
}
/**
* 获取卡槽 0 (主卡) 的 IMEI
*
* @param context 上下文
* @return IMEI 字符串,如果获取失败则返回空字符串
*/
@SuppressLint("MissingPermission") // 系统应用已通过 manifest 赋予权限,此处屏蔽 Lint 警告
public static String getDefaultImei(Context context) {
return getImei(context, 0);
}
/**
* 根据卡槽 ID 获取对应的 IMEI
* 该方法适用于 Android 6.0 (API 23) 及以上的所有版本。
* 在 Android 10+ 上,只有系统应用能成功返回数据。
*
* @param context 上下文
* @param slotIndex 卡槽索引 (0 代表卡槽1, 1 代表卡槽2)
* @return IMEI 字符串,如果获取失败则返回空字符串
*/
@SuppressLint("MissingPermission")
public static String getImei(Context context, int slotIndex) {
String imei = "";
try {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
// 优先使用标准的 getImei(int slotIndex) 方法 (API 23+)
// 对于低版本系统,可以使用反射调用,但现代系统推荐直接调用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
imei = tm.getImei(slotIndex);
} else {
// 针对极少数的 Android 6.0 以下的老旧系统兜底
imei = tm.getDeviceId(slotIndex);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return imei == null ? "" : imei;
}
/**
* 获取所有卡槽的 IMEI 数组
*
* @param context 上下文
* @return 包含各个卡槽 IMEI 的字符串数组
*/
@SuppressLint("MissingPermission")
public static String[] getAllImeis(Context context) {
String[] imeis = new String[2]; // 假设最多两个卡槽
imeis[0] = getImei(context, 0);
imeis[1] = getImei(context, 1);
return imeis;
}
public static String getDefaultImSi(Context context) {
return getImsiBySubId(context, 0);
}
public static String getImsiBySubId(Context context, int count) {
SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(count);
if (subInfo != null) {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
int subId = subInfo.getSubscriptionId();
TelephonyManager tm = telephonyManager.createForSubscriptionId(subId); //关键点拿到对应subId的tm
String mImsi = tm.getSubscriberId(subId); //imsinot subid
Log.e(TAG, "getSubInfo: mImsi = " + mImsi);
return mImsi;
} else {
return Build.UNKNOWN;
}
}
/**
* 获取MAC地址
*
* @param context
* @return
*/
public static String getWlanMacAddress(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return getMacDefault(context);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return getMacAddressM();
} else {
return getMacFromHardware();
}
}
/**
* Android 6.0 之前不包括6.0
*
* @param context
* @return
*/
private static String getMacDefault(Context context) {
String mac = "未获取到设备Mac地址";
if (context == null) {
return mac;
}
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo info = null;
try {
info = wifi.getConnectionInfo();
} catch (Exception e) {
e.printStackTrace();
}
if (info == null) {
return mac;
}
mac = info.getMacAddress();
if (!TextUtils.isEmpty(mac)) {
mac = mac.toUpperCase(Locale.ENGLISH);
return mac;
} else {
return "02:00:00:00:00:00";
}
}
/**
* Android 6.0(包括) - Android 7.0(不包括)
*
* @return
*/
private static String getMacAddressM() {
String mac = "未获取到设备Mac地址";
try {
mac = new BufferedReader(new FileReader("/sys/class/net/wlan0/address")).readLine();
} catch (IOException e) {
e.printStackTrace();
}
return mac;
}
/**
* 遍历循环所有的网络接口,找到接口是 wlan0
*
* @return
*/
private static String getMacFromHardware() {
try {
List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
if (!nif.getName().equalsIgnoreCase("wlan0")) {
continue;
}
byte[] macBytes = nif.getHardwareAddress();
StringBuilder res1 = new StringBuilder();
for (byte b : macBytes) {
res1.append(String.format("%02X:", b));
}
if (res1 != null) {
res1.deleteCharAt(res1.length() - 1);
}
return res1.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
return "未获取到设备Mac地址";
}
/**
* 获取当前移动网络(数据网络)的 IP 地址
*
* @return 形如 "10.x.x.x" 的 IPv4 地址,未连接数据网络或获取失败返回空串
*/
@SuppressLint("MissingPermission")
public static String getMobileIp(Context context) {
try {
// 优先通过 TrafficStats / 网络接口遍历移动网络接口rmnet0 / ccmni0 等)
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : interfaces) {
String name = nif.getName();
if (name == null) {
continue;
}
// 移动数据网络接口通常以 rmnet / ccmni / mnet 开头
if (name.startsWith("rmnet") || name.startsWith("ccmni") || name.startsWith("mnet")) {
for (java.net.InterfaceAddress addr : nif.getInterfaceAddresses()) {
java.net.InetAddress inet = addr.getAddress();
if (inet instanceof java.net.Inet4Address && !inet.isLoopbackAddress()) {
return inet.getHostAddress();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 获取移动数据网络的 MAC 地址
* 注意Android 6.0+ 出于隐私限制,通过 API 只能拿到 02:00:00:00:00:00
* 系统签名应用可尝试读取 /sys/class/net/rmnet0/address多数情况下为空
*
* @return MAC 地址字符串,无法获取时返回空串
*/
@SuppressLint("MissingPermission")
public static String getMobileMac() {
// 移动网络接口通常没有独立 MAC部分设备通过 rmnet 暴露
String[] candidates = {"rmnet0", "rmnet_data0", "ccmni0"};
for (String ifName : candidates) {
try {
NetworkInterface nif = NetworkInterface.getByName(ifName);
if (nif != null) {
byte[] mac = nif.getHardwareAddress();
if (mac != null && mac.length > 0) {
StringBuilder sb = new StringBuilder();
for (byte b : mac) {
sb.append(String.format("%02X:", b));
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return "";
}
/**
* 获取移动网络子类型名称(如 LTE / HSPA / EDGE 等)
*
* @param tm TelephonyManager
* @return 网络子类型描述,未接入数据网络返回空串
*/
@SuppressLint("MissingPermission")
public static String getNetworkSubtype(TelephonyManager tm) {
try {
if (tm != null) {
int subtype = tm.getNetworkType();
return getNetworkTypeName(subtype);
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 获取当前蜂窝信号强度等级0~4对应 ASU 的粗略分级4 最强)。
* 该值可用于状态栏信号格显示,与 {@link android.telephony.SignalStrength#getLevel()} 一致。
*
* @param context 上下文
* @return 信号等级 0~4无 SIM / 非蜂窝网络 / 获取失败返回 -1
*/
@SuppressLint("MissingPermission")
public static int getSignalLevel(Context context) {
try {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null) {
return -1;
}
// Android 9 (API 28) 及以下无 getAllCellInfo/SignalStrength 的公开 getLevel
// 这里统一用 getAllCellInfo 取最强小区的 level覆盖绝大多数系统版本。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
List<CellInfo> cellInfos = tm.getAllCellInfo();
if (cellInfos != null) {
int maxLevel = -1;
for (CellInfo info : cellInfos) {
if (info == null || !info.isRegistered()) {
continue;
}
CellSignalStrength strength = info.getCellSignalStrength();
if (strength != null) {
maxLevel = Math.max(maxLevel, strength.getLevel());
}
}
if (maxLevel >= 0) {
return maxLevel;
}
}
}
// 兜底API 29+ 可直接取 SignalStrength 的 level
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
SignalStrength signalStrength = tm.getSignalStrength();
if (signalStrength != null) {
return signalStrength.getLevel();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
/**
* 获取当前蜂窝信号强度dBm如 -75 表示较强,-113 接近无信号)。
* 优先使用各制式 CellInfo 的 dBm找不到则回退到 ASU 换算asu - 113 - 2 * (asu & 1))。
*
* @param context 上下文
* @return 信号强度 dBm无信号 / 获取失败返回 {@code null}
*/
@SuppressLint("MissingPermission")
public static Integer getSignalDbm(Context context) {
try {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null) {
return null;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
List<CellInfo> cellInfos = tm.getAllCellInfo();
if (cellInfos != null) {
int bestDbm = Integer.MIN_VALUE;
for (CellInfo info : cellInfos) {
if (info == null || !info.isRegistered()) {
continue;
}
CellSignalStrength strength = info.getCellSignalStrength();
if (strength != null) {
int dbm = strength.getDbm();
if (dbm != Integer.MAX_VALUE && dbm > bestDbm) {
bestDbm = dbm;
}
}
}
if (bestDbm != Integer.MIN_VALUE) {
return bestDbm;
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
SignalStrength signalStrength = tm.getSignalStrength();
if (signalStrength != null) {
int dbm = signalStrength.getDbm();
if (dbm != Integer.MAX_VALUE && dbm != Integer.MIN_VALUE) {
return dbm;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 根据当前活跃网络获取信号强度 (RSSI/dBm)
* 优先返回 Wi-Fi 信号强度,如果未连接 Wi-Fi 则返回移动网络信号强度
*
* @param context 上下文
* @return 信号强度 (dBm);获取失败返回 null
*/
public static Integer getCurrentNetworkRssi(Context context) {
try {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) return null;
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork == null || !activeNetwork.isConnected()) {
return null;
}
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo info = wifiManager.getConnectionInfo();
if (info != null) {
return info.getRssi();
}
} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
return getSignalDbm(context);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String getNetworkTypeName(int networkType) {
switch (networkType) {
case TelephonyManager.NETWORK_TYPE_GPRS:
return "GPRS";
case TelephonyManager.NETWORK_TYPE_EDGE:
return "EDGE";
case TelephonyManager.NETWORK_TYPE_UMTS:
return "UMTS";
case TelephonyManager.NETWORK_TYPE_HSDPA:
return "HSDPA";
case TelephonyManager.NETWORK_TYPE_HSUPA:
return "HSUPA";
case TelephonyManager.NETWORK_TYPE_HSPA:
return "HSPA";
case TelephonyManager.NETWORK_TYPE_CDMA:
return "CDMA";
case TelephonyManager.NETWORK_TYPE_EVDO_0:
return "EVDO_0";
case TelephonyManager.NETWORK_TYPE_EVDO_A:
return "EVDO_A";
case TelephonyManager.NETWORK_TYPE_1xRTT:
return "1xRTT";
case TelephonyManager.NETWORK_TYPE_EHRPD:
return "eHRPD";
case TelephonyManager.NETWORK_TYPE_LTE:
return "LTE";
case TelephonyManager.NETWORK_TYPE_HSPAP:
return "HSPA+";
case TelephonyManager.NETWORK_TYPE_GSM:
return "GSM";
case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
return "TD_SCDMA";
case TelephonyManager.NETWORK_TYPE_IWLAN:
return "IWLAN";
case TelephonyManager.NETWORK_TYPE_NR:
return "NR";
default:
return "UNKNOWN";
}
}
/**
* 计算已安装应用包名列表的 MD5与 SnSecurityInfoReq.mobileAppList 口径保持一致:
* 取所有非系统应用包名,按字母排序后用逗号拼接再取 MD5
*
* @param pm PackageManager
* @return 32 位小写 MD5 字符串;无应用或异常时返回空串
*/
public static String getAppListMd5(PackageManager pm) {
try {
if (pm == null) {
return "";
}
List<PackageInfo> installedApps = pm.getInstalledPackages(0);
List<String> pkgNames = new ArrayList<>();
for (PackageInfo info : installedApps) {
// 仅统计第三方(非系统)应用,与常规应用列表统计口径一致
if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
&& info.packageName != null) {
pkgNames.add(info.packageName);
}
}
Collections.sort(pkgNames);
StringBuilder sb = new StringBuilder();
for (String name : pkgNames) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(name);
}
return md5(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static String getFactoryMacAddresses(Context context) {
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
String[] factoryMacAddresses = wifiManager.getFactoryMacAddresses();
String str = (factoryMacAddresses == null || factoryMacAddresses.length <= 0) ? Build.UNKNOWN : factoryMacAddresses[0];
return str.toUpperCase(Locale.ENGLISH);
}
public static String getBluetoothMacAddress(Context context) {
String mac = Settings.Secure.getString(
context.getContentResolver(),
"bluetooth_address"
);
return mac;
}
@Deprecated
public static String getBtAddressByReflection() {
String def = "02:00:00:00:00:00";
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
return def;
}
return bluetoothAdapter.getAddress();
// Field field;
// try {
// field = BluetoothAdapter.class.getDeclaredField("mService");
// field.setAccessible(true);
// Object bluetoothManagerService = field.get(bluetoothAdapter);
// if (bluetoothManagerService == null) {
// return def;
// }
// Method method = bluetoothManagerService.getClass().getMethod("getAddress");
// if (method != null) {
// Object obj = method.invoke(bluetoothManagerService);
// if (obj != null) {
// return obj.toString();
// }
// }
// } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
// e.printStackTrace();
// }
// return def;
}
public static String getBuildId() {
return getProperty("ro.build.id", Build.ID);
}
public static String getBuildDisplayId() {
return getProperty("ro.build.display.id", Build.DISPLAY);
}
// ==================================================================
// 设备基本信息(对齐后端 DeviceSystemInfoVO
// ==================================================================
public static String getDeviceName(Context context) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
return Settings.Global.getString(context.getContentResolver(), Settings.Global.DEVICE_NAME);
}
} catch (Throwable t) {
// ignore
}
return Build.UNKNOWN;
}
public static String getDeviceManufacturer() {
return getProperty("ro.product.manufacturer", Build.MANUFACTURER);
}
public static String getHardware() {
return getProperty("ro.hardware", Build.UNKNOWN);
}
public static String getHost() {
return getProperty("ro.build.host", Build.HOST);
}
public static String getAndroidId(Context context) {
try {
return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
} catch (Throwable t) {
return "";
}
}
public static String getBuildFingerprint() {
return getProperty("ro.build.fingerprint", Build.FINGERPRINT);
}
public static String getBuildType() {
return getProperty("ro.build.type", Build.TYPE);
}
public static String getBuildUser() {
return getProperty("ro.build.user", Build.USER);
}
public static String getBuildHost() {
return getProperty("ro.build.host", Build.HOST);
}
public static String getBuildTags() {
return getProperty("ro.build.tags", Build.TAGS);
}
public static long getBuildTime() {
try {
return Build.TIME;
} catch (Throwable t) {
return 0L;
}
}
public static String getLanguage() {
try {
return Locale.getDefault().getLanguage();
} catch (Throwable t) {
return "";
}
}
public static String getTimezone() {
try {
return java.util.TimeZone.getDefault().getID();
} catch (Throwable t) {
return "";
}
}
public static long getBootTime() {
try {
return System.currentTimeMillis() - android.os.SystemClock.elapsedRealtime();
} catch (Throwable t) {
return 0L;
}
}
public static String getScreenResolution(Context context) {
try {
android.util.DisplayMetrics dm = getRealDisplayMetrics(context);
return dm.widthPixels + "x" + dm.heightPixels;
} catch (Throwable t) {
return "";
}
}
/**
* 获取设备真实屏幕显示参数(包含状态栏、导航栏,不被窗口裁切)。
* 优先使用 getRealMetrics显示设备真实分辨率低版本无此 API 时回退 getMetrics。
*/
private static android.util.DisplayMetrics getRealDisplayMetrics(Context context) {
android.util.DisplayMetrics dm = new android.util.DisplayMetrics();
android.view.WindowManager wm =
(android.view.WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
android.view.Display display = wm.getDefaultDisplay();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
display.getRealMetrics(dm);
} else {
display.getMetrics(dm);
}
return dm;
}
public static int getScreenDensity(Context context) {
try {
return (int) Math.round(context.getResources().getDisplayMetrics().density);
} catch (Throwable t) {
return 0;
}
}
public static int getScreenDensityDpi(Context context) {
try {
return context.getResources().getDisplayMetrics().densityDpi;
} catch (Throwable t) {
return 0;
}
}
public static String getKernelVersion() {
return getProperty("sys.kernel.version", "");
}
public static String getRomVersion() {
return getProperty("ro.build.version.emui",
getProperty("ro.build.version.miui",
getProperty("ro.build.version.opporom",
getProperty("ro.vivo.os.version", ""))));
}
public static String getProductName() {
return getProperty("ro.product.name", Build.PRODUCT);
}
public static String getProductModel() {
return getProperty("ro.product.model", Build.UNKNOWN);
}
public static String getProductDevice() {
return getProperty("ro.product.device", Build.UNKNOWN);
}
public static String getBoardPlatform() {
return getProperty("ro.board.platform", Build.UNKNOWN);
}
public static String getDeviceType(Context context) {
try {
int layout = context.getResources().getConfiguration().screenLayout
& android.content.res.Configuration.SCREENLAYOUT_SIZE_MASK;
if (layout >= android.content.res.Configuration.SCREENLAYOUT_SIZE_LARGE) {
return "tablet";
}
return "phone";
} catch (Throwable t) {
return "phone";
}
}
public static int getIsTablet(Context context) {
try {
int layout = context.getResources().getConfiguration().screenLayout
& android.content.res.Configuration.SCREENLAYOUT_SIZE_MASK;
return layout >= android.content.res.Configuration.SCREENLAYOUT_SIZE_LARGE ? 1 : 0;
} catch (Throwable t) {
return 0;
}
}
public static String getReleaseVersion() {
return getProperty("ro.build.version.release", Build.VERSION.RELEASE);
}
public static String getDisplayVersion() {
return getProperty("ro.build.display.id", Build.DISPLAY);
}
public static String getBootloader() {
return getProperty("ro.bootloader", Build.BOOTLOADER);
}
public static String getBasebandVersion() {
return getProperty("gsm.version.baseband", Build.UNKNOWN);
}
public static String getRadioVersion() {
try {
return Build.getRadioVersion() != null ? Build.getRadioVersion() : Build.UNKNOWN;
} catch (Throwable t) {
return Build.UNKNOWN;
}
}
public static String getScreenSize(Context context) {
try {
android.util.DisplayMetrics dm = getRealDisplayMetrics(context);
double diagonal = Math.sqrt(Math.pow(dm.widthPixels / dm.xdpi, 2)
+ Math.pow(dm.heightPixels / dm.ydpi, 2));
return String.format(Locale.US, "%.1f", diagonal);
} catch (Throwable t) {
return "";
}
}
public static double getScreenInch(Context context) {
try {
android.util.DisplayMetrics dm = getRealDisplayMetrics(context);
double diagonal = Math.sqrt(Math.pow(dm.widthPixels / dm.xdpi, 2)
+ Math.pow(dm.heightPixels / dm.ydpi, 2));
return Math.round(diagonal * 10.0) / 10.0;
} catch (Throwable t) {
return 0.0;
}
}
public static int getRefreshRate(Context context) {
try {
android.view.Display display = ((android.view.WindowManager)
context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
return Math.round(display.getRefreshRate());
} catch (Throwable t) {
return 0;
}
}
public static String getOsType() {
return "Android";
}
public static String getOsVersion() {
return Build.VERSION.RELEASE;
}
public static String getFirmwareVersion() {
return getProperty("ro.build.version.incremental", Build.UNKNOWN);
}
public static boolean isMainProcessName(Context cxt, int pid) {
String packageName = cxt.getPackageName();
ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
if (runningApps == null) {
return false;
}
for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
if (procInfo.pid == pid) {
return procInfo.processName.equals(packageName);
}
}
return false;
}
public static String getForegroundActivityPackageName(Context context) {
ActivityManager activityManager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> runningTaskInfos = activityManager.getRunningTasks(1);
if (runningTaskInfos != null && !runningTaskInfos.isEmpty()) {
ComponentName componentName = runningTaskInfos.get(0).topActivity;
if (componentName != null) {
String currentPackageName = componentName.getPackageName();
return currentPackageName;
}
}
return "";
}
/**
* 获取栈顶的应用包名
*/
public static String getForegroundActivityClassName(Context context) {
ActivityManager manager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
String currentClassName = manager.getRunningTasks(1).get(0).topActivity.getClassName();
return currentClassName;
}
public static List<String> getRunningTaskPackages(Context context) {
ActivityManager activityManager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> runningTaskInfos = activityManager.getRunningTasks(Integer.MAX_VALUE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return runningTaskInfos.stream().map(new Function<ActivityManager.RunningTaskInfo, String>() {
@Override
public String apply(ActivityManager.RunningTaskInfo runningTaskInfo) {
return runningTaskInfo.topActivity.getPackageName();
}
}).collect(Collectors.toList());
} else {
List<String> packageNames = new ArrayList<>();
for (ActivityManager.RunningTaskInfo runningTaskInfo : runningTaskInfos) {
packageNames.add(runningTaskInfo.topActivity.getPackageName());
}
return packageNames;
}
}
public static void killBackgroundProcesses(Context context, String processName) {
Logger.e(TAG, "killBackgroundProcesses: " + processName);
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
String packageName;
try {
if (!processName.contains(":")) {
packageName = processName;
} else {
packageName = processName.split(":")[0];
}
activityManager.killBackgroundProcesses(packageName);
activityManager.forceStopPackage(packageName);
// removeTask(context, processName);
// Method forceStopPackage = activityManager.getClass()
// .getDeclaredMethod("forceStopPackage", String.class);
// forceStopPackage.setAccessible(true);
// forceStopPackage.invoke(activityManager, packageName);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void removeTask(Context context, String packageName) {
Logger.e(TAG, "removeTask: " + packageName);
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
List<ActivityManager.RecentTaskInfo> list = getRecentTasks(ActivityManager.getMaxRecentTasksStatic(), getCurrentUserId());
HashMap<String, Integer> taskMap = new HashMap<>();
for (ActivityManager.RecentTaskInfo info : list) {
taskMap.put(info.realActivity.getPackageName(), info.id);
}
try {
ActivityManagerNative.getDefault().removeTask(taskMap.get(packageName));
} catch (RemoteException e) {
e.printStackTrace();
Logger.e(TAG, "removeTask: " + e.getMessage());
} catch (NullPointerException e) {
Logger.e(TAG, "removeTask: " + e.getMessage());
}
// } else {
// ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//
// }
}
/**
* @return a list of the recents tasks.
* 获取近期任务列表
*/
public static List<ActivityManager.RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
try {
return ActivityTaskManager.getService().getRecentTasks(numTasks,
RECENT_IGNORE_UNAVAILABLE, userId).getList();
} catch (RemoteException e) {
Logger.e(TAG, "Failed to get recent tasks " + e);
return new ArrayList<>();
}
}
/**
* @return the current user's id.
* 获取userId
*/
public static int getCurrentUserId() {
UserInfo ui;
try {
ui = ActivityManager.getService().getCurrentUser();
return ui != null ? ui.id : 0;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
public static boolean isDefaultLauncher(Context context, Class<?> launcherActivityClass) {
ComponentName componentName = new ComponentName(context, launcherActivityClass);
Logger.e(TAG, "isDefaultLauncher: componentName = " + componentName);
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
if (resolveInfo != null && resolveInfo.activityInfo != null) {
ComponentName defaultComponentName = resolveInfo.getComponentInfo().getComponentName();
Logger.e(TAG, "isDefaultLauncher: defaultComponentName = " + defaultComponentName);
return componentName.equals(defaultComponentName);
}
return false;
}
/**
* 将指定的 Activity 设置为系统默认桌面(需要系统签名)
*
* @param context 上下文
* @param launcherActivityClass 你的桌面 Activity 类,例如 MyLauncherActivity.class
*/
public static void setDefaultLauncher(Context context, Class<?> launcherActivityClass) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
addRoleHolderAsUser(context, context.getPackageName());
} else {
PackageManager pm = context.getPackageManager();
// 1. 创建桌面过滤的 IntentFilter
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);
// 2. 查询当前系统中所有的桌面应用(用来构建 ComponentName 数组)
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
int bestMatch = 0;
ComponentName[] set = new ComponentName[resolveInfos.size()];
for (int i = 0; i < resolveInfos.size(); i++) {
ResolveInfo info = resolveInfos.get(i);
set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
if (info.match > bestMatch) {
bestMatch = info.match; // 获取最匹配的值
}
}
// 3. 构建你自己的桌面 ComponentName
ComponentName myLauncher = new ComponentName(context, launcherActivityClass);
// 4. 替换首选 Activity核心步骤
try {
// 注意API 29 (Android 10) 中此方法对第三方应用废弃,但对系统签名应用依然有效
pm.replacePreferredActivity(filter, bestMatch, set, myLauncher);
Logger.i("LauncherHelper", "成功设置为默认桌面");
} catch (SecurityException e) {
Logger.e("LauncherHelper", "缺少权限或未生效,请检查系统签名是否正确", e);
} catch (Exception e) {
Logger.e("LauncherHelper", "设置默认桌面失败", e);
}
}
}
public static void setDefaultLauncher(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
RoleManager roleManager = (RoleManager) context.getSystemService(Context.ROLE_SERVICE);
// 检查当前应用是否已经是桌面角色持有者
if (roleManager != null && !roleManager.isRoleHeld(RoleManager.ROLE_HOME)) {
// 检查该角色是否可用
if (roleManager.isRoleAvailable(RoleManager.ROLE_HOME)) {
// 创建请求意图
Intent roleRequestIntent = roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME);
// 注意:在普通应用中,这会弹出系统选择框
// 在系统签名应用中,这通常能直接提升优先级或简化流程
context.startActivity(roleRequestIntent);
}
}
} else {
// Android 10 以下的传统做法:清除当前默认并弹出选择
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
/**
* 将指定包名设置为默认桌面 (需要系统签名)
*/
public static void addRoleHolderAsUser(Context context, String packageName) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Logger.e(TAG, "RoleManager requires Android 10 or higher.");
return;
}
RoleManager roleManager = context.getSystemService(RoleManager.class);
if (roleManager == null) return;
String roleName = RoleManager.ROLE_HOME;
// 1. 检查是否已经是当前角色持有者,避免重复调用
if (roleManager.isRoleHeld(roleName)) {
// 注意:这里最好再判断一下持有的包名是否为目标包名
Logger.i(TAG, "Role " + roleName + " is already held by some package.");
// 如果已经是自己,直接返回
}
try {
UserHandle user = Process.myUserHandle();
Executor executor = context.getMainExecutor();
// 定义回调
Consumer<Boolean> callback = successful -> {
if (successful) {
Logger.i(TAG, "Successfully set " + packageName + " as " + roleName);
} else {
Logger.e(TAG, "Failed to set " + packageName + " as " + roleName + ". Check system logs.");
}
};
// 2. 使用反射调用隐藏方法 addRoleHolderAsUser
// 方法签名: addRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer<Boolean>)
Method method = RoleManager.class.getMethod("addRoleHolderAsUser",
String.class, String.class, int.class, UserHandle.class, Executor.class, Consumer.class);
Logger.d(TAG, "Invoking addRoleHolderAsUser for package: " + packageName);
method.invoke(roleManager, roleName, packageName, 0, user, executor, callback);
// roleManager.addRoleHolderAsUser(roleName, packageName, 0, user, executor, callback);
} catch (NoSuchMethodException e) {
Logger.e(TAG, "Method not found. Is this a non-standard ROM?", e);
} catch (Exception e) {
Logger.e(TAG, "Error invoking addRoleHolderAsUser", e);
}
}
public static void setOtherDefaultLauncher(Context context) {
PackageManager pm = context.getPackageManager();
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
Optional<ResolveInfo> systemLauncher = resolveInfos.stream()
.filter(info -> !context.getPackageName().equals(info.activityInfo.packageName))
.filter(info -> ApkUtils.isSystemApp(context, info.activityInfo.packageName))
.findFirst();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
systemLauncher.ifPresent(info -> addRoleHolderAsUser(context, info.activityInfo.packageName));
} else {
pm.clearPackagePreferredActivities(context.getPackageName());
systemLauncher.ifPresent(info -> {
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);
int bestMatch = 0;
ComponentName[] set = new ComponentName[resolveInfos.size()];
for (int i = 0; i < resolveInfos.size(); i++) {
ResolveInfo res = resolveInfos.get(i);
set[i] = new ComponentName(res.activityInfo.packageName, res.activityInfo.name);
if (res.match > bestMatch) bestMatch = res.match;
}
ComponentName otherLauncher = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
try {
pm.replacePreferredActivity(filter, bestMatch, set, otherLauncher);
} catch (Exception e) {
Logger.e(TAG, "setOtherDefaultLauncher error", e);
}
});
}
}
/**
* 系统签名应用专用静默获取全屏截图Bitmap
*
* @param context 上下文
* @return 全屏截图Bitmap失败返回null
*/
public static Bitmap takeFullScreenshot(Context context) {
// 获取屏幕真实宽高(包含状态栏、导航栏)
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
wm.getDefaultDisplay().getRealMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
Logger.e(TAG, "takeFullScreenshot: screenWidth " + screenWidth);
Logger.e(TAG, "takeFullScreenshot: screenHeight " + screenHeight);
try {
// 注意:不同 Android 版本的 SurfaceControl.screenshot 方法签名可能不同
// 以下代码适用于 Android 9.0 及以上版本常见的隐藏 API 调用方式
// 1. 获取屏幕显示的 IBinder (通常为 DisplayControl 或 SurfaceControl 的内部方法)
// 在较高版本中,可能需要通过 SurfaceControl.getInternalDisplayToken() 获取
// 2. 反射调用 screenshot 方法
Class<?> surfaceControlClass = Class.forName("android.view.SurfaceControl");
// Android 10+ 建议使用新的反射路径,这里以通用逻辑为例:
// 对于 Android 11+Google 引入了 SurfaceControl.LayerCaptureArgs 等内部类
// 这是一个针对 Android 9/10 的简化逻辑参考:
Bitmap bitmap = (Bitmap) surfaceControlClass.getDeclaredMethod("screenshot",
Rect.class, Integer.TYPE, Integer.TYPE, Integer.TYPE)
.invoke(null, new Rect(), screenWidth, screenHeight, 0);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@RequiresApi(api = Build.VERSION_CODES.Q)
public static Bitmap takeScreenshotHighVersion() {
try {
// 1. 获取主屏幕的 Token (Internal Display)
// 反射调用 SurfaceControl.getInternalDisplayToken()
Method getInternalDisplayTokenMethod = SurfaceControl.class.getDeclaredMethod("getInternalDisplayToken");
getInternalDisplayTokenMethod.setAccessible(true);
IBinder displayToken = (IBinder) getInternalDisplayTokenMethod.invoke(null);
if (displayToken == null) return null;
// 2. 构造 DisplayCaptureArgs.Builder (Android 11+ 的新包装类)
Class<?> builderClass = Class.forName("android.view.SurfaceControl$DisplayCaptureArgs$Builder");
Constructor<?> builderConstructor = builderClass.getConstructor(IBinder.class);
Object builder = builderConstructor.newInstance(displayToken);
// 可以通过 Builder 设置缩放、格式等,这里直接 build()
Method buildMethod = builderClass.getDeclaredMethod("build");
Object captureArgs = buildMethod.invoke(builder);
// 3. 调用 SurfaceControl.screenshot(DisplayCaptureArgs)
// 返回值是一个 ScreenshotHardwareBuffer 对象
Class<?> captureArgsClass = Class.forName("android.view.SurfaceControl$DisplayCaptureArgs");
Method screenshotMethod = SurfaceControl.class.getDeclaredMethod("screenshot", captureArgsClass);
screenshotMethod.setAccessible(true);
Object screenshotBuffer = screenshotMethod.invoke(null, captureArgs);
if (screenshotBuffer == null) return null;
// 4. 从 ScreenshotHardwareBuffer 中提取 Bitmap
Method asBitmapMethod = screenshotBuffer.getClass().getDeclaredMethod("asBitmap");
asBitmapMethod.setAccessible(true);
return (Bitmap) asBitmapMethod.invoke(screenshotBuffer);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 保存Bitmap到公共图库Android 10+ 无需存储权限)
*
* @param context 上下文
* @param bitmap 截图Bitmap
* @return 保存成功返回true
*/
public static boolean saveScreenshotToGallery(Context context, Bitmap bitmap) {
if (bitmap == null) return false;
// 使用MediaStore保存到Pictures目录Android 10+ 推荐)
ContentValues values = new android.content.ContentValues();
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
values.put(MediaStore.Images.Media.DISPLAY_NAME, "Screenshot_" + System.currentTimeMillis() + ".png");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/SystemScreenshots");
android.net.Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (uri == null) return false;
try (OutputStream outputStream = context.getContentResolver().openOutputStream(uri)) {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
return true;
} catch (java.io.IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 智能截屏:根据系统版本选择最佳方案
* 1. 优先使用 SurfaceControl 反射 (速度快,且避开 Android 10+ 的 "Bad system call")
* 2. 失败则回退到 screencap 命令行
*
* @param context 上下文
* @param filepath 保存路径
* @param observer 结果回调 (0 为成功)
*/
public static void screenshotSnap(Context context, String filepath, Observer<Integer> observer) {
Observable.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Bitmap bitmap = null;
// 1. 对于 Android 9.0 及以上,优先尝试反射 SurfaceControl
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
bitmap = takeScreenshotHighVersion();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
bitmap = takeFullScreenshot(context);
}
if (bitmap != null) {
try (FileOutputStream fos = new FileOutputStream(filepath)) {
boolean success = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
if (success) {
Logger.i(TAG, "screenshotSnap: SurfaceControl 截屏成功");
return 0;
}
}
}
} catch (Exception e) {
Logger.e(TAG, "SurfaceControl 截屏异常: " + e.getMessage());
}
// 2. Fallback: 使用命令行 (适用于旧版本或已 Root 设备)
Logger.w(TAG, "screenshotSnap: 尝试使用命令行回退");
CmdUtil.Result result = CmdUtil.execute("screencap -p " + filepath);
if (result.code != 0) {
Logger.e(TAG, "screenshotSnap 命令行失败: " + result.error);
}
return result.code;
}
}).subscribe(observer);
}
public static void screenshotCmd(String filepath, Observer<Integer> observer) {
Observable.fromCallable(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
CmdUtil.Result result = CmdUtil.execute("screencap -p " + filepath);
Logger.e(TAG, "call: " + result.error);
return result.code;
}
}).subscribe(observer);
}
/**
* 检查指定的 VoiceInteractionService 是否被设置为系统默认助手
*/
public static boolean isAssistantServiceEnabled(Context context, String serviceName) {
String setting = Settings.Secure.getString(context.getContentResolver(), "assistant");
if (setting != null) {
ComponentName componentName = ComponentName.unflattenFromString(setting);
if (componentName != null) {
return componentName.getPackageName().equals(context.getPackageName()) &&
componentName.getClassName().equals(serviceName);
}
}
return false;
}
/**
* 尝试将当前应用的指定服务设置为系统默认助手(通常需要系统签名或 WRITE_SECURE_SETTINGS 权限)
*/
public static boolean setAssistantService(Context context, String serviceName) {
try {
String value = context.getPackageName() + "/" + serviceName;
boolean success1 = Settings.Secure.putString(context.getContentResolver(), "assistant", value);
boolean success2 = Settings.Secure.putString(context.getContentResolver(), "voice_interaction_service", value);
return success1 && success2;
} catch (Exception e) {
Logger.e(TAG, "setAssistantService error: " + e.getMessage());
return false;
}
}
/**
* 查询设备当前屏幕是否亮屏。
*/
public static boolean isScreenOn(Context context) {
try {
PowerManager powerManager =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
return powerManager != null && powerManager.isInteractive();
} catch (Throwable e) {
return false;
}
}
}