feat: dev-5.1 分支基线(v1.3.35 发版后代码 + 5.1 声道改造文档)

- 以 main(ccc65af) 为基线提交当前工作区代码
- 新增 5.1 声道改造文档两份
- .gitignore 补充 .vscode/、.screenshots/
This commit is contained in:
freeboss
2026-11-02 19:25:12 +08:00
parent ccc65afcbc
commit b7b5518834
1394 changed files with 13694 additions and 3710 deletions

0
libcore/.gitignore vendored Executable file → Normal file
View File

0
libcore/build.gradle Executable file → Normal file
View File

0
libcore/consumer-rules.pro Executable file → Normal file
View File

0
libcore/proguard-rules.pro vendored Executable file → Normal file
View File

0
libcore/src/main/AndroidManifest.xml Executable file → Normal file
View File

View File

@@ -14,14 +14,14 @@ public class BaseConst {
public static final String header = "huashitv";
//渠道code
public static String agentVendorCode = "1100101010208919998410060";
//渠道code(橘视厅5.1环绕声业务渠道,与 ChannelManger.initChannel 保持一致)
public static String agentVendorCode = "1100101010200492358120169";
//业务组合code
public static String serviceComboCode = "1100121010208920760580061";
public static String serviceComboCode = "1100121044377892530980001";
//业务分组code
public static String serviceGroupCode = "1100122010208920968780062";
public static String serviceGroupCode = "1100122010201752209710182";
//业务线参数
public static String businessLine = "4100450011967708592750001";
public static String businessLine = "4100450044057893707260002";
public static String getMemberCode(String token){

View File

View File

View File

View File

View File

View File

@@ -1,27 +1,13 @@
package com.pukka.libcore.boxShared;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.text.TextUtils;
import android.widget.Toast;
import com.pukka.libcore.utils.LogUtils;
import com.pukka.libcore.utils.SharedPreferenceUtils;
import com.pukka.libcore.utils.StringUtil;
import com.pukka.libcore.utils.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Locale;
/**
@@ -31,226 +17,70 @@ import java.util.Locale;
*/
public class MacUtils {
/**
* 获取mac地址(适配所有Android版本)
* 获取 mac 地址(适配所有 Android 版本)
* 优先取 ETH_MAC 缓存,再读设备;都拿不到就如实返回空字符串,
* 不再伪造(伪造 MAC 在服务端设备库里匹配不到,行为更糟)。
*
* @param context 上下文
* @return mac地址
* @return mac地址;拿不到时返回 ""
*/
public static String getMac(Context context) {
// String mac = getMacAddressIP();
// if (TextUtils.isEmpty(mac)) {
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// mac = getMacDefault(context);
// } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
// mac = getMacAddress();
// } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// mac = getMacFromHardware();
// }
// }
// if (TextUtils.isEmpty(mac)){
String mac = SharedPreferenceUtils.getString("ETH_MAC","");
LogUtils.d("从APK缓存中获取有线mac="+mac);
if(StringUtils.isEmpty(mac)) {
//安柠获取mac的方法
// 1) 优先从缓存取(有线 MAC 第一次成功读取后会写到这里)
String mac = SharedPreferenceUtils.getString("ETH_MAC", "");
LogUtils.d("从APK缓存中获取有线mac=" + mac);
if (StringUtils.isEmpty(mac)) {
// 2) 缓存未命中,按多路策略读设备
mac = StringUtil.getLocalMac();
LogUtils.d("从设备中获取mac="+mac);
}
if(StringUtils.isEmpty(mac)) {
//自己生成mac的方法
mac = MacUtils2.getMAC(context);
LogUtils.d("从设备中自生成mac="+mac);
LogUtils.d("从设备中获取mac=" + mac);
}
return mac;
//}
//return mac;
}
/**
* 读 /sys/class/net/wlan0/address,走 shell cat 以绕过 SELinux 对 Java 直读 sysfs 的拒绝。
* 循环读满直到 EOF,避免单次 read(buffer) 不保证读满的坑。
*
* @return wlan0 MAC;读不到返回 ""
*/
public static String getWlan0Mac() {
String mac = "";
try {
Process p = Runtime.getRuntime().exec("cat /sys/class/net/wlan0/address");
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader bf = new BufferedReader(isr);
String line = "";
if ((line = bf.readLine()) != null) {
mac = line;
}
bf.close();
isr.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return mac.toUpperCase();
// 复用 StringUtil.readSysFsMac,仅末尾大小写归一化与本方法旧行为一致
return StringUtil.readSysFsMac("/sys/class/net/wlan0/address").toUpperCase(Locale.ENGLISH);
}
/**
* Android 6.0 之前(不包括6.0)获取mac地址
* 必须的权限 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
* 统一封装:拿不到有线 MAC 时以 WifiMAC 为主(两者相同),避免下游出现"伪 mac"。
* 返回 String[2] = {mac, wifiMac}:
* <ul>
* <li>有线拿到了:mac = 有线MAC,wifiMac = wlan0MAC(两者可能不同——双网卡设备)</li>
* <li>有线没拿到、wifi 拿到了:mac = wlan0MAC,wifiMac = wlan0MAC(两者完全一致)</li>
* <li>都拿不到:两者均为 ""</li>
* </ul>
* 当两者真值相同(wifi-only 设备)但大小写不同时,统一成大写——便于后端按字符串比对 MAC。
* 双网卡设备上两者来自不同接口(有线 ≠ 无线),保留各自原值不强制统一。
*
* @param context * @return
* @param context 上下文
* @return 长度固定为 2 的数组:[mac, wifiMac]
*/
private static String getMacDefault(Context context) {
String mac = "";
if (context == null) {
return mac;
public static String[] getDeviceMacPair(Context context) {
// 独立取两条路径,避免继承 getMac() 内部任何兜底行为
String ethMac = getMac(context);
String wifiMac = getWlan0Mac();
// 拿不到有线时以 wifi 为主(两者相同)
if (TextUtils.isEmpty(ethMac) && !TextUtils.isEmpty(wifiMac)) {
ethMac = wifiMac;
}
WifiManager wifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo info = null;
try {
info = wifi.getConnectionInfo();
} catch (Exception e) {
e.printStackTrace();
// 拿不到 wifi 时以有线为主(双向兜底,保持两者一致)
if (TextUtils.isEmpty(wifiMac) && !TextUtils.isEmpty(ethMac)) {
wifiMac = ethMac;
}
if (info == null) {
return null;
// 两者都非空且真值相同(wifi-only 设备的常见场景),统一大小写
// 双网卡设备上两者来源不同,equalsIgnoreCase 不命中,保留各自原值
if (!TextUtils.isEmpty(ethMac) && !TextUtils.isEmpty(wifiMac)
&& ethMac.equalsIgnoreCase(wifiMac)) {
String canonical = ethMac.toUpperCase(Locale.ENGLISH);
ethMac = canonical;
wifiMac = canonical;
}
mac = info.getMacAddress();
if (!TextUtils.isEmpty(mac)) {
mac = mac.toUpperCase(Locale.ENGLISH);
}
return mac;
}
/**
* Android 6.0-Android 7.0 获取mac地址
*/
private static String getMacAddress() {
String macSerial = null;
String str = "";
try {
Process pp = Runtime.getRuntime().exec("cat /sys/class/net/wlan0/address");
InputStreamReader ir = new InputStreamReader(pp.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
while (null != str) {
str = input.readLine();
if (str != null) {
macSerial = str.trim();//去空格
break;
}
}
} catch (IOException ex) {
// 赋予默认值
ex.printStackTrace();
}
return macSerial;
}
/**
* Android 7.0之后获取Mac地址
* 遍历循环所有的网络接口,找到接口是 wlan0
* 必须的权限 <uses-permission android:name="android.permission.INTERNET"></uses-permission>
*
* @return
*/
private static String getMacFromHardware() {
Enumeration<NetworkInterface> interfaces = null;
try {
interfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
e.printStackTrace();
}
String hardWareAddress = null;
NetworkInterface iF = null;
if (interfaces == null) {
return null;
}
while (interfaces.hasMoreElements()) {
iF = interfaces.nextElement();
try {
hardWareAddress = bytesToString(iF.getHardwareAddress());
if (hardWareAddress != null) {
break;
}
} catch (SocketException e) {
e.printStackTrace();
}
}
return hardWareAddress;
}
/**
* Android 6.0 之前(不包括6.0)
* 获取有线网络mac
*
* @return 有线网络mac
*/
private static String getMacAddressIP() {
String strMacAddr = null;
try {
InetAddress ip = getLocalInetAddress();
byte[] b = NetworkInterface.getByInetAddress(ip)
.getHardwareAddress();
StringBuffer buffer = new StringBuffer();
if(b != null) {
for (int i = 0; i < b.length; i++) {
if (i != 0) {
buffer.append(':');
}
String str = Integer.toHexString(b[i] & 0xFF);
buffer.append(str.length() == 1 ? 0 + str : str);
}
}
strMacAddr = buffer.toString().toUpperCase();
} catch (Exception e) {
e.printStackTrace();
}
return strMacAddr;
}
/***
* byte转为String
*
* @param bytes
* @return
*/
private static String bytesToString(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
StringBuilder buf = new StringBuilder();
for (byte b : bytes) {
buf.append(String.format("%02X:", b));
}
if (buf.length() > 0) {
buf.deleteCharAt(buf.length() - 1);
}
return buf.toString();
}
/**
* 获取本地IP
*
* @return
*/
private static InetAddress getLocalInetAddress() {
InetAddress ip = null;
try {
//列举
Enumeration en_netInterface = NetworkInterface.getNetworkInterfaces();
while (en_netInterface.hasMoreElements()) {//是否还有元素
NetworkInterface ni = (NetworkInterface) en_netInterface.nextElement();//得到下一个元素
Enumeration en_ip = ni.getInetAddresses();//得到一个ip地址的列举
while (en_ip.hasMoreElements()) {
ip = (InetAddress) en_ip.nextElement();
if (!ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {
break;
} else {
ip = null;
}
}
if (ip != null) {
break;
}
}
} catch (SocketException e) {
e.printStackTrace();
}
return ip;
return new String[]{ethMac, wifiMac};
}
}

View File

@@ -1,73 +0,0 @@
package com.pukka.libcore.boxShared;
import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import com.pukka.libcore.utils.LogUtils;
import java.security.MessageDigest;
public class MacUtils2 {
public static String getMAC(Context context) {
// 获取基础信息
String baseInfo = getBaseDeviceInfo(context);
// 生成MAC格式字符串
String macAddress = generateMacFromHash(baseInfo);
LogUtils.i("MacUtils", "return Mac:" + macAddress);
return macAddress;
}
public static String getBaseDeviceInfo(Context context) {
// 获取设备基本信息
String androidId = "";
androidId = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ANDROID_ID
);
LogUtils.i("MacUtils", "androidId:" + androidId);
String deviceInfo = Build.BRAND
+ Build.DEVICE
+ Build.MANUFACTURER
+ Build.MODEL;
LogUtils.i("MacUtils", "BRAND:" + Build.BRAND);
LogUtils.i("MacUtils", "DEVICE:" + Build.DEVICE);
LogUtils.i("MacUtils", "MANUFACTURER:" + Build.MANUFACTURER);
LogUtils.i("MacUtils", "MODEL:" + Build.MODEL);
return androidId + deviceInfo;
}
public static String getModel() {
LogUtils.i("getModel", Build.BRAND + " " + Build.MODEL);
return Build.BRAND + " " + Build.MODEL;
}
private static String generateMacFromHash(String input) {
LogUtils.i("MacUtils", "generateMacFromHash:" + input);
try {
// 生成MD5
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(input.getBytes());
// 取前6个字节构造MAC
StringBuilder mac = new StringBuilder();
for (int i = 0; i < 6; i++) {
if (i > 0) mac.append(":");
mac.append(String.format("%02X", hash[i] & 0xFF));
}
// 确保第一个字节是偶数(本地管理的MAC地址)
char firstChar = mac.charAt(0);
if ((Character.digit(firstChar, 16) & 0x1) != 0) {
mac.setCharAt(0, (char) (firstChar - 1));
}
return mac.toString();
} catch (Exception e) {
e.printStackTrace();
return "02:00:00:00:00:00"; // 默认值
}
}
}

View File

View File

@@ -1,6 +1,6 @@
package com.pukka.libcore.cipher;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -29,7 +29,7 @@ public class HMacShaUtils {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA256_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
mac.init(signingKey);
return mac.doFinal(data.getBytes(Charset.forName("UTF-8")));
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
@@ -51,7 +51,7 @@ public class HMacShaUtils {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
return mac.doFinal(data.getBytes(Charset.forName("UTF-8")));
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
} catch (Exception ex) {
ex.printStackTrace();
}

View File

@@ -2,7 +2,7 @@ package com.pukka.libcore.cipher;
import com.pukka.libcore.utils.ByteConversionUtils;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
/**
@@ -61,7 +61,7 @@ public class Md5Utils {
public static String encrypt(String message) {
String md5str = "";
try {
md5str = encrypt(message.getBytes(Charset.forName("UTF-8")));
md5str = encrypt(message.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@@ -113,17 +113,62 @@ public class MacAddressUtil {
if (interfaces == null) {
return null;
}
// 优先取 eth0,再退回 wlan0。原实现是"遍历到第一个有 MAC 就 break",
// 但 Linux 设备上 dummy0/ifb0/tunl0/gre* 等虚拟接口都有内核分配的本地随机 MAC,
// 会被错误命中。改为过滤虚拟接口并按 eth0 > wlan0 优先级返回。
String ethMac = null;
String wlanMac = null;
while (interfaces.hasMoreElements()) {
iF = interfaces.nextElement();
String name = iF.getName();
String lower = name == null ? "" : name.toLowerCase();
try {
hardWareAddress = bytesToString(iF.getHardwareAddress());
if (hardWareAddress != null)
break;
// 跳过回环
if (iF.isLoopback()) continue;
// 跳过 DOWN 状态
if (!iF.isUp()) continue;
} catch (SocketException e) {
e.printStackTrace();
continue;
}
// 跳过明显的虚拟接口(dummy/ifb/隧道/p2p)
if (isVirtualNetworkInterface(lower)) continue;
try {
hardWareAddress = bytesToString(iF.getHardwareAddress());
} catch (SocketException e) {
e.printStackTrace();
continue;
}
if (hardWareAddress == null) continue;
// 跳过 Android 7.0+ 默认占位 MAC
if ("02:00:00:00:00:00".equals(hardWareAddress)) continue;
if ("eth0".equals(lower)) {
ethMac = hardWareAddress;
} else if ("wlan0".equals(lower)) {
wlanMac = hardWareAddress;
}
}
return hardWareAddress;
return ethMac != null ? ethMac : wlanMac;
}
/**
* 判断是否为虚拟网络接口(不应作为设备主 MAC 返回)。
* 覆盖常见 Linux 虚拟接口:dummy/ifb/tunl/gre/gretap/erspan/ip_vti/ip6_vti/sit/ip6tnl/ip6gre/p2p。
*/
private static boolean isVirtualNetworkInterface(String lowerName) {
return lowerName.startsWith("dummy")
|| lowerName.startsWith("ifb")
|| lowerName.startsWith("tunl")
|| lowerName.startsWith("gre") // 包括 gre0 / gretap0 / ip6gre0
|| lowerName.startsWith("gretap")
|| lowerName.startsWith("erspan")
|| lowerName.startsWith("ip_vti")
|| lowerName.startsWith("ip6_vti")
|| lowerName.startsWith("sit")
|| lowerName.startsWith("ip6tnl")
|| lowerName.startsWith("ip6gre")
|| lowerName.startsWith("p2p")
|| lowerName.equals("lo");
}
private static String bytesToString(byte[] bytes) {

View File

View File

View File

View File

View File

View File

@@ -2,6 +2,9 @@ package com.pukka.libcore.utils;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Locale;
public class StringUtil {
@@ -10,8 +13,11 @@ public class StringUtil {
public static String getLocalMac() {
String mac = "";
mac= MacAddressUtil.getMacAddressFromFile("/sys/class/net/eth0/address");
LogUtils.d("有线(eth0)mac:"+mac);
// 第 1 层:sysfs 直读 eth0。走 shell `cat` 而非 FileInputStream,绕过 SELinux 对
// Java 进程直读 /sys/class/net/*/address 的拒绝(这是导致 wifi-only / 严格 SELinux
// 设备上"7 层全空"的根因)。
mac = readSysFsMac("/sys/class/net/eth0/address");
LogUtils.d("有线(eth0)mac:" + mac);
if (!TextUtils.isEmpty(mac)) {
//本地存有线mac
if (!StringUtils.isEmpty(mac)) {
@@ -20,43 +26,88 @@ public class StringUtil {
return mac.toLowerCase(Locale.getDefault());
}
mac= MacAddressUtil.getLocalMacAddress();
LogUtils.d("有线(LocalMac)mac:"+mac);
if (!TextUtils.isEmpty(mac)) {
return mac.toLowerCase(Locale.getDefault());
}
mac= MacAddressUtil.getMacAddressFromFile("/sys/class/net/wlan0/address");
LogUtils.d("无线mac:"+mac);
// 第 2 层:NetworkInterface 枚举 eth0 -> wlan0 硬件地址(API 路径)
mac = MacAddressUtil.getLocalMacAddress();
LogUtils.d("有线(LocalMac)mac:" + mac);
if (!TextUtils.isEmpty(mac) && !TextUtils.equals("02:00:00:00:00:00", mac)) {
return mac.toLowerCase(Locale.getDefault());
}
// 第 3 层:sysfs 直读 wlan0。同样走 shell `cat`。
mac = readSysFsMac("/sys/class/net/wlan0/address");
LogUtils.d("无线mac:" + mac);
if (!TextUtils.isEmpty(mac)) {
return mac.toLowerCase(Locale.getDefault());
}
// 第 4 层:InetAddress 反查(API 路径)
mac = MacAddressUtil.getMacAddress();
LogUtils.d("通过InetAddress mac:"+mac);
LogUtils.d("通过InetAddress mac:" + mac);
if (!TextUtils.isEmpty(mac)) {
return mac.toLowerCase(Locale.getDefault());
}
// 第 5 层:ifconfig eth0(shell 路径)
mac = MacAddressUtil.getLocalMac("ifconfig eth0");
LogUtils.d("通过ifconfig eth0 mac:"+mac);
LogUtils.d("通过ifconfig eth0 mac:" + mac);
if (!TextUtils.isEmpty(mac)) {
return mac.toLowerCase(Locale.getDefault());
}
// 第 6 层:ifconfig wlan0(shell 路径)
mac = MacAddressUtil.getLocalMac("ifconfig wlan0");
LogUtils.d("通过ifconfig wlan0 mac:"+mac);
LogUtils.d("通过ifconfig wlan0 mac:" + mac);
if (!TextUtils.isEmpty(mac)) {
return mac.toLowerCase(Locale.getDefault());
}
// 第 7 层:NetworkInterface 遍历硬件地址(API 路径)
mac = MacAddressUtil.getMachineHardwareAddress();
LogUtils.d("通过MachineHardwareAddress mac:"+mac);
LogUtils.d("通过MachineHardwareAddress mac:" + mac);
if (!TextUtils.isEmpty(mac)) {
return mac.toLowerCase(Locale.getDefault());
}
// 如果所有方法都失败,返回空字符串
// 如果所有方法都失败,返回空字符串(不再伪造;MacUtils2 已删除)
return "";
}
/**
* 走 shell `cat <path>` 读 sysfs 网络节点。
* 走 shell 而非 FileInputStream 的原因:
* SELinux 策略下 untrusted_app / system_app 直读 sysfs class net 下各 address 常被 MAC 拒绝,
* 而 Runtime.exec("cat ...") 启动的进程 label 是 init_shell,能绕过该限制。
* 同时循环 read 直到 EOF,避免单次 read(buffer) 不保证读满的坑。
*
* @return 读到的内容;读不到返回 ""
*/
public static String readSysFsMac(String path) {
BufferedReader reader = null;
try {
Process process = Runtime.getRuntime().exec("cat " + path);
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
if (sb.length() > 0) sb.append('\n');
sb.append(line);
}
try {
process.waitFor();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
return sb.toString().trim();
} catch (IOException e) {
LogUtils.d("readSysFsMac 读取 " + path + " 失败: " + e.getMessage());
return "";
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ignored) {
}
}
}
}
}

View File

View File

View File

View File