package com.isa.navi.jni.hmi;
import static java.lang.Thread.sleep;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.StatFs;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import androidx.annotation.NonNull;
import com.isa.navi.jni.hmi.update.USBListener;
import com.isa.navi.library.map.UpdateEnvironment;
import com.isa.navi.library.map.UpdateInfoBean;
import com.isa.navi.manager.CallBackManager;
import com.isa.navi.manager.CarManager;
import com.isa.navi.manager.base.BaseHandlerManager;
import com.isa.navi.receiver.USBReceiver;
import com.isa.navi.utils.GsonUtils;
import com.isa.navi.utils.LogUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.InputStreamReader;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class HmiJNIImpl extends BaseHandlerManager implements IHmiJNI {
private static final String TAG = "UPDATE";
private static final int MSG_DOWNLOAD_STATUS_CHANGE = 1;
private final HmiProxyJNI mHmiProxyJNI;
private final Format mFormat;
private static final String PASSWORD_PREFIX = "DfCs";
private static final String PASSWORD_SUFFIX = "#@";
private static final int LAST_CHARS_LENGTH = 4;
private static String updateUSBpath = "";
/**
* 车辆管理实例,用于管理车辆相关的功能。
*/
private CarManager mCarManager;
private volatile static HmiJNIImpl mInstance;
private boolean is_run = false;
private final Set<String> USBPathSet = Collections.synchronizedSet(new HashSet<>());
public static HmiJNIImpl getInstance() {
if (mInstance == null) {
synchronized (HmiJNIImpl.class) {
if (mInstance == null) {
mInstance = new HmiJNIImpl();
}
}
}
return mInstance;
}
private HmiJNIImpl() {
super(true);
mHmiProxyJNI = new HmiProxyJNI();
mFormat = Format.getInstance();
mCarManager = CarManager.getInstance();
}
@Override
public void initialize() {
super.initialize();
LogUtils.d(TAG, "initialize : ");
DfCert.getInstance().getService();
CallBackManager.getInstance().registerUSBEventChangeListener(new USBListener() {
@Override
public void path(String mountPath) {
USBPathSet.add(mountPath);
LogUtils.i(TAG, "USBPathSet:" + mountPath);
}
});
LogUtils.i(TAG, "Path:" + mContext.getFilesDir());
// 初始化C++模块
mHmiProxyJNI.InitializeCpp();
InstanceEngine();
//decryptUtilTest();
// UpdateTest();
}
// 辅助方法:记录访问被拒绝的详细原因
private void logAccessDeniedReason(File dir) {
// 记录 SELinux 拒绝日志
try {
Process process = Runtime.getRuntime().exec("dmesg | grep avc");
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
boolean found = false;
while ((line = reader.readLine()) != null) {
if (line.contains("avc: denied") && line.contains(dir.getName())) {
LogUtils.e(TAG, "SELinux拒绝记录: " + line);
found = true;
}
}
if (!found) {
LogUtils.i(TAG, "未找到相关的SELinux拒绝记录");
}
} catch (Exception e) {
LogUtils.e(TAG, "获取SELinux日志失败"+e);
}
// 记录文件系统权限
try {
Process process = Runtime.getRuntime().exec("ls -ld " + dir.getAbsolutePath());
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
LogUtils.i(TAG, "详细权限信息: " + line);
}
} catch (Exception e) {
LogUtils.e(TAG, "获取详细权限失败"+e);
}
// 记录当前应用权限
LogUtils.i(TAG, "应用权限上下文: " + getSELinuxContext());
}
// 获取当前应用的 SELinux 安全上下文
private String getSELinuxContext() {
try {
Process process = Runtime.getRuntime().exec("id -Z");
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
return reader.readLine();
} catch (Exception e) {
return "未知: " + e.getMessage();
}
}
// public void upDateTest(){
// Thread UpdateTest = new Thread(new Runnable(){
// @Override
// public void run() {
// LogUtils.i(TAG, "upDateTest start");
// startUSBDetection();
// }
// });
//
// UpdateTest.setName("ISA_NAVI_upDateTest");
// UpdateTest.start();
// }
public void UpdateTest() {
Thread UpdateTest = new Thread(new Runnable(){
@Override
public void run() {
LogUtils.i(TAG, "UpdateTest start!");
setUSBPath("/data/user/0/com.isa.navi/usbpath/KVM_EU_202502_T1.0_266dd24ba609476c99293f0ef7403a9f.zip");
updateMap();
}
});
UpdateTest.setName("ISA_NAVI_UpdateTest");
UpdateTest.start();
}
//
// public void decryptUtilTest(){
// Thread UpdateTest = new Thread(new Runnable(){
// @Override
// public void run() {
// try {
// sleep(20);
// LogUtils.i(TAG, "decryptUtilTest start");
// String encryptedZipPath = "/map/KVM_EU_202502_T1.0_266dd24ba609476c99293f0ef7403a9f.zip";
// boolean isSuccess = dataVerification(encryptedZipPath);
// LogUtils.i(TAG, "验签和解密结果: " + (isSuccess ? "成功" : "失败"));
// } catch (Exception e) {
// LogUtils.e(TAG, "测试异常: " + e.getMessage());
// }
// }
// });
//
// UpdateTest.setName("ISA_NAVI_decryptUtilTest");
// UpdateTest.start();
// }
//读取DES秘钥文本
public String readKeyFromFile(String keyDataPath) {
File keyFile = new File(keyDataPath);
StringBuilder keyBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(keyFile))) {
String line;
while ((line = reader.readLine()) != null) {
// 移除行首尾空白字符(包括换行符)
String trimmedLine = line.trim();
// 跳过空行和注释行(以#开头的行)
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) {
continue;
}
keyBuilder.append(trimmedLine);
}
// 返回读取到的内容
return keyBuilder.toString();
} catch (IOException e) {
// 处理文件读取异常
System.err.println("读取文件失败: " + e.getMessage());
e.printStackTrace();
return null;
}
}
public boolean desDecrypt(String DesDataPath,String key){
LogUtils.i(TAG, "encryptedZipPath[encryptedZipPath]:" + DesDataPath);
LogUtils.i(TAG, "key:" + key);
String signPath = get_Global_m_data_address() +"sign/";
String downLoadPath = get_Global_m_data_address();
//解压加密数据
LogUtils.i(TAG, "DesDataPath[" + DesDataPath + "] -> signPath[" + signPath + "]");
if(!unzipFile(DesDataPath, signPath)) {
//删除des解密中间文件
if(!deleteDirectory(signPath)) {
LogUtils.e(TAG, "无法删除des解密中间文件!");
}
return false;
}
String str = ZipUtils.getInstance().findFiles(signPath);
LogUtils.i(TAG, " des文件[" + str + "]");
//解密des文件
if(!desDecryptFile(new File(str), downLoadPath, key)) {
LogUtils.e(TAG, "DesdecryptFile fail!");
//删除des解密中间文件
if(!deleteDirectory(signPath)) {
LogUtils.e(TAG, "无法删除Des解密中间文件!");
}
return false;
}
//删除des解密中间文件
if(!deleteDirectory(signPath)) {
LogUtils.e(TAG, "无法删除des解密中间文件!");
}
LogUtils.i(TAG, "desDecryptUtil success");
return true;
}
private int[] getCurrentYearAndQuarter() {
int[] result = new int[2];
LocalDate currentDate = LocalDate.now();
// 获取当前年份
int currentYear = currentDate.getYear();
// 获取当前月份
Month currentMonth = currentDate.getMonth();
// 根据月份确定当前季度
int currentQuarter;
switch (currentMonth) {
case JANUARY: case FEBRUARY: case MARCH:
currentQuarter = 1;
break;
case APRIL: case MAY: case JUNE:
currentQuarter = 2;
break;
case JULY: case AUGUST: case SEPTEMBER:
currentQuarter = 3;
break;
case OCTOBER: case NOVEMBER: case DECEMBER:
currentQuarter = 4;
break;
default:
currentQuarter = 0;
}
result[0] = currentYear;
result[1] = currentQuarter;
return result;
}
private int[] parseMapInfo(String mapInfo) {
int[] result = new int[2];
// 检查输入字符串的长度是否为6(4位年份 + 2位季度)
if (mapInfo == null || mapInfo.length() != 6) {
LogUtils.e(TAG, "Input string must be in the format YYYYMM where YYYY is the year and MM is the quarter as a two-digit number.");
return result;
}
// 提取年份(前4位)
String yearStr = mapInfo.substring(0, 4);
result[0] = Integer.parseInt(yearStr);
// 提取季度(后2位),并验证它在1到4的范围内
String quarterStr = mapInfo.substring(4, 6);
int quarter = Integer.parseInt(quarterStr);
if (quarter < 1 || quarter > 4) {
LogUtils.e(TAG, "Quarter must be between 1 and 4.");
return result;
}
result[1] = quarter;
return result;
}
public void dataVersionCheck() {
Thread dataVersionCheckThread = new Thread(new Runnable() {
@Override
public void run() {
try {
UpdateInfoBean bean = new UpdateInfoBean();
String localVersion = mHmiProxyJNI.getMapInfo();
if(localVersion.isEmpty()) {
bean.setUpdateStatus(4);
handleDownloadStatusChange(bean);
LogUtils.i(TAG, "Detected no updates for a long time");
return;
}
int[] currentYearAndQuarter = getCurrentYearAndQuarter();
int[] localYearAndQuarter = parseMapInfo(localVersion);
int yearDifference = currentYearAndQuarter[0] - localYearAndQuarter[0];
int QuarterDifference = currentYearAndQuarter[1] - localYearAndQuarter[1];
if(yearDifference * 4 + QuarterDifference > 3) {
bean.setUpdateStatus(4);
handleDownloadStatusChange(bean);
LogUtils.i(TAG, "Detected no updates for a long time");
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 设置线程名称
dataVersionCheckThread.setName("ISA_NAVI_dataVersionCheck");
// 启动线程
dataVersionCheckThread.start();
}
public void setContext(Context context) {
if(mContext == null) {
mContext = context;
}
language.getInstance().setContext(mContext);
}
@Override
protected void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DOWNLOAD_STATUS_CHANGE:
handleDownloadStatusChange((String) msg.obj);
break;
default:
break;
}
}
private List<String> JsonArrayToListExample(JSONArray jsonArray) {
List<String> stringList = new ArrayList<>();
try {
// 遍历JSONArray
for (int i = 0; i < jsonArray.length(); i++) {
// 由于我们假设JSONArray只包含字符串,所以我们可以直接getString
String item = jsonArray.getString(i);
// 将字符串添加到列表中
stringList.add(item);
}
} catch (JSONException e) {
e.printStackTrace();
}
return stringList;
}
private UpdateInfoBean jsonToUpdateInfoBean(String json) {
UpdateInfoBean result = null;
try {
// 解析整个JSON字符串为一个JSONObject
JSONObject rootObject = new JSONObject(json);
// 解析mUpdateMessage对象
JSONObject mUpdateMessage = rootObject.getJSONObject("mUpdateMessage");
String country = mUpdateMessage.getString("country");
double downloadSize = mUpdateMessage.getDouble("downloadSize");
boolean isCurrentCountry = mUpdateMessage.getBoolean("isCurrentCountry");
double progress = mUpdateMessage.getDouble("progress");
double totalSize = mUpdateMessage.getDouble("totalSize");
int updateStatus = mUpdateMessage.getInt("updateStatus");
String version = mUpdateMessage.getString("version");
// 解析mUpdateNotification对象
JSONObject mUpdateNotification = rootObject.getJSONObject("mUpdateNotification");
JSONArray updateException = mUpdateNotification.getJSONArray("UpdateException");
JSONArray updateFailedList = mUpdateNotification.getJSONArray("UpdateFailedList");
boolean updateResult = mUpdateNotification.getBoolean("UpdateResult");
JSONArray updateSuccessfulList = mUpdateNotification.getJSONArray("UpdateSuccessfulList");
// 解析mapStatus
int mapStatus = rootObject.getInt("mapStatus");
UpdateInfoBean.UpdateNotification temp1 = new UpdateInfoBean.UpdateNotification(updateResult,JsonArrayToListExample(updateException),JsonArrayToListExample(updateSuccessfulList),JsonArrayToListExample(updateFailedList));
UpdateInfoBean.UpdateMessage temp2 = new UpdateInfoBean.UpdateMessage(country, version, totalSize, (int) progress, isCurrentCountry);
temp2.setUpdateStatus(updateStatus);
temp2.setDownloadSize(downloadSize);
result = new UpdateInfoBean(mapStatus, temp1, temp2);
} catch (Exception e) {
LogUtils.e(TAG, "JSON parsing failed:" + e.toString());
}
return result;
}
private void handleDownloadStatusChange(String json) {
UpdateInfoBean bean = jsonToUpdateInfoBean(json);
LogUtils.i(TAG, "handleDownloadStatusChange : " + bean);
if (bean != null) {
// 仅当status=8时检查is_run,其他状态直接放行
if(bean.getUpdateStatus() != 8 || is_run) {
List<UpdateInfoBean> list = new ArrayList<>();
list.add(bean);
CallBackManager.getInstance().notifyUpdateStatus(list);
}
}
}
private void handleDownloadStatusChange(UpdateInfoBean bean) {
LogUtils.i(TAG, "handleDownloadStatusChange : " + bean);
if (bean != null) {
// 仅当status=8时检查is_run,其他状态直接放行
if(bean.getUpdateStatus() != 8 || is_run) {
List<UpdateInfoBean> list = new ArrayList<>();
list.add(bean);
CallBackManager.getInstance().notifyUpdateStatus(list);
}
} else {
LogUtils.e(TAG, "UpdateInfoBean is null!");
}
}
public void sendUpdateMessage(String json) {
LogUtils.d(TAG, "sendUpdateMessage : " + json);
Message message = Message.obtain();
message.what = MSG_DOWNLOAD_STATUS_CHANGE;
message.obj = json;
mHandler.sendMessage(message);
}
@Override
public void updateMap() {
LogUtils.d(TAG, "activeUpdateStart : ");
// 检查更新过程是否已经在运行,防止重复创建线程
if(is_run) {
LogUtils.i(TAG, "更新程序已经在进行");
return;
}else {
is_run = true;
}
// 创建一个新线程来执行主动更新
Thread updateMapThread = new Thread(new Runnable() {
@Override
public void run() {
try {
// 调用JNI方法开始主动更新
mHmiProxyJNI.updateMap();
LogUtils.d(TAG, "activeUpdateStart end: ");
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 设置线程名称
updateMapThread.setName("ISA_NAVI_UpdateMapThread");
// 启动线程
updateMapThread.start();
}
public void updateMapend() {
is_run = false;
}
@Override
public void suspendUpdate(boolean suspend) {
mHmiProxyJNI.suspendUpdate(suspend);
}
@Override
public void cancelUpdate() {
mHmiProxyJNI.cancelUpdate();
}
/**
* 启动USB检测线程,用于检测插入的U盘并判断是否有可用的升级文件。
*/
public void startUSBDetection() {
LogUtils.i(TAG, "startUSBDetection START");
// setUSBPath("/data/KVM_EU_202404_T1.2.zip");
Thread USBDetectionThread = new Thread(new Runnable(){
@Override
public void run() {
UpdateInfoBean bean = new UpdateInfoBean();
boolean isHaveUSBData = false;
//遍历检测到的U盘路径
if(USBPathSet.isEmpty()) {
//未检测到U盘,触发提示弹窗
LogUtils.e(TAG, "No USB drive detected!");
bean.setUpdateStatus(2);
handleDownloadStatusChange(bean);
return;
}
int set_num = 0;
for(String usbPath : USBPathSet ) {
//判断U盘路径是否真实存在
set_num ++;
if(USBReceiver.isMounted(usbPath)) {
File usbFile = new File(usbPath);
File[] listFiles = usbFile.listFiles();
if(listFiles == null) {
continue;
}
File newfile = null;
String file_str = null;
for(File file : listFiles) {
LogUtils.i(TAG, "file:" + file.getAbsolutePath());
//判断U盘内是否含有升级文件
if(!file.isDirectory() && mFormat.isUpgradeFileNamingFormat(file.getName())) {
isHaveUSBData = true;
if(newfile == null) {
newfile = file;
file_str = newfile.getAbsolutePath();
} else {
if(Format.getInstance().oldIsNewVersion(file.getName().replaceAll("\\.\\w+$", ""), newfile.getName().replaceAll("\\.\\w+$", ""))) {
newfile = file;
file_str = newfile.getAbsolutePath();
}
}
}
}
//未检测到U盘指定路径下是否有数据,触发提示弹窗
if(!isHaveUSBData || newfile == null) {
LogUtils.e(TAG, "The USB drive does not contain an upgrade package!");
bean.setUpdateStatus(3);
handleDownloadStatusChange(bean);
return;
}else{
// 从文件名中提取版本号-zwx
// String localDataPath = Format.getInstance().extractVersion(getCountryDbPath());
String localDataPath = Format.getInstance().extractVersionFromDbPath(getCountryDbPath());//202501_T1.0
String newDataPath = Format.getInstance().extractVersionFromDbPath(file_str);//202502_T1.0
LogUtils.i(TAG, "file name:" + newDataPath);
LogUtils.i(TAG, "localDataPath:" + localDataPath);
if(localDataPath.isEmpty()) {
String mapinfo = getMapInfo();
if(!mapinfo.isEmpty()) {
String[] newVersionParts = new String[2];
newVersionParts = Format.getInstance().parseVersion(newDataPath);
String newUpdateVersion = newVersionParts[0]; // 取第一部分:202502
LogUtils.i(TAG, "newUpdateVersion:" + newUpdateVersion);
String newReleasesVersion = newVersionParts[1];//取第二部分:01/02
LogUtils.i(TAG, "newReleasesVersion:" + newReleasesVersion);
LogUtils.i(TAG, "localDataPath.isEmpty() newUpdateVersion: " + newUpdateVersion);
LogUtils.i(TAG, "localDataPath.isEmpty() newReleasesVersion: " + newReleasesVersion);
if(Integer.parseInt(newUpdateVersion) > Integer.parseInt(mapinfo) || (Integer.parseInt(newUpdateVersion) == Integer.parseInt(mapinfo) && Double.parseDouble(newReleasesVersion) > 1)) {
LogUtils.i(TAG,"Trigger USB drive update!");
bean.setUpdateStatus(0);
handleDownloadStatusChange(bean);
//设置U盘更新路径
updateUSBpath = usbPath;
setUSBPath(newfile.getAbsolutePath());
LogUtils.i(TAG, "filePath:" + newfile.getAbsolutePath());
} else {
LogUtils.e(TAG, "The upgrade package version on the USB drive is less than or equal to the vehicle's version!");
bean.setUpdateStatus(1);
handleDownloadStatusChange(bean);
}
} else {
LogUtils.i(TAG,"Trigger USB drive update!");
bean.setUpdateStatus(0);
handleDownloadStatusChange(bean);
//设置U盘更新路径
updateUSBpath = usbPath;
setUSBPath(newfile.getAbsolutePath());
LogUtils.i(TAG, "filePath:" + newfile.getAbsolutePath());
}
return;
}
// 判断U盘内数据版本是否大于车机数据版本
if(Format.getInstance().isNewerVersion(newDataPath, localDataPath)) {
LogUtils.i(TAG,"Trigger USB drive update!");
bean.setUpdateStatus(0);
handleDownloadStatusChange(bean);
//设置U盘更新路径
updateUSBpath = usbPath;
setUSBPath(newfile.getAbsolutePath());
LogUtils.i(TAG, "filePath:" + newfile.getAbsolutePath());
} else {
LogUtils.e(TAG, "The upgrade package version on the USB drive is less than or equal to the vehicle's version!");
bean.setUpdateStatus(1);
handleDownloadStatusChange(bean);
}
return;
}
} else {
if(set_num == USBPathSet.size()) {
//未检测到U盘,触发提示弹窗
LogUtils.e(TAG, "No USB drive detected!");
bean.setUpdateStatus(2);
handleDownloadStatusChange(bean);
return;
}
}
}
}
});
USBDetectionThread.setName("ISA_NAVI_USBDetectionThread");
USBDetectionThread.start();
}
public String getMapInfo() {
String result = mHmiProxyJNI.getMapInfo();
LogUtils.i(TAG,"MapInfo:" + result);
return result;
}
public String getUpdateVersion() {
String result = mHmiProxyJNI.getUpdateVersion();
LogUtils.i(TAG,"UpdateVersion:" + result);
return result;
}
public UpdateEnvironment checkUpdateEnvironment() {
String json = mHmiProxyJNI.checkUpdateEnvironment();
LogUtils.d(TAG,"checkUpdateEnvironment JSON:" + json);
UpdateEnvironment result = GsonUtils.fromJson(json, UpdateEnvironment.class);
if(result == null) {
LogUtils.e(TAG, "GsonUtils.fromJson(json, UpdateEnvironment.class) Fail!");
return null;
}
if (!result.getResult()) {
List<String> temp_list = new ArrayList<>();
for (int i : result.getInt_reason()) {
String str = language.getInstance().getEnvironment(i);
if(str != null && !str.isEmpty()) {
temp_list.add(str);
}
}
// 更新temp对象中的reason字段
result.setReason(temp_list);
}
LogUtils.i(TAG,"checkUpdateEnvironment :" + result);
return result;
}
public String getUpdatePurpose() {
return language.getInstance().getUpdatePurpose();
}
public String getUpdateContent() {
return language.getInstance().getUpdateContent();
}
public String getUserManual() {
return language.getInstance().getUserManual();
}
public String getUpdatedContentDescription() {
return language.getInstance().getUpdatedContentDescription();
}
public int getEstimatedTime() {
return 30;
}
private void InstanceEngine() {
Thread InstanceEngine = new Thread(new Runnable(){
@Override
public void run() {
LogUtils.d("ISA_NAVI", "InstanceEngine start!");
mHmiProxyJNI.instanceEngine();
}
});
InstanceEngine.setName("ISA_NAVI_InstanceEngine");
InstanceEngine.start();
}
// 获取后四位字符
@NonNull
private static String getLastFourChars(String mapInfo) {
if (mapInfo == null || mapInfo.isEmpty()) {
LogUtils.e(TAG, "mapInfo cannot be null or empty");
return "0000";
}
return mapInfo.length() >= LAST_CHARS_LENGTH
? mapInfo.substring(mapInfo.length() - LAST_CHARS_LENGTH)
: "0000";
}
// 生成密码
@NonNull
private String generatePassword() {
String mapInfo = getMapInfo();
String lastFourChars = getLastFourChars(mapInfo);
return PASSWORD_PREFIX + lastFourChars + PASSWORD_SUFFIX;
}
// 校验密码
public boolean validatePassword(String inputPassword) {
if (inputPassword == null) {
return false;
}
String generatedPassword = generatePassword();
LogUtils.i(TAG, "generatedPassword:" + generatedPassword);
LogUtils.i(TAG, "inputPassword:" + inputPassword);
return inputPassword.equals(generatedPassword);
}
public void setUSBPath(String USBPath) {
mHmiProxyJNI.setUSBPath(USBPath);
}
public boolean unzipFile(String zipFilePath, String destinationDirPath) {
return ZipUtils.getInstance().unzip(zipFilePath, destinationDirPath);
}
public long getFileSize(String filePath) {
File file = new File(filePath);
if(!file.isFile() || !file.exists()) {
return 0;
}
LogUtils.d(TAG, "getFileSize:" + file.length());
return file.length();
}
public long getAvailableSpace(String filePath) {
StatFs stat = new StatFs(filePath);
long availableBlocks = stat.getAvailableBlocksLong();
long blockSize = stat.getBlockSizeLong();
LogUtils.i(TAG, "[AvailableSpace]:" + availableBlocks * blockSize + "字节");
return availableBlocks * blockSize;
}
public String getFail(int i) {
return language.getInstance().getFail(i);
}
public boolean decryptFile(String encryptedFilePath, String decryptedFilePath) {
return new DecryptUtil().decryptFile(encryptedFilePath, decryptedFilePath, DfCert.getInstance().getKEY_AES_256());
}
public boolean desDecryptFile(File desDaraPath,String downLoadPath,String key){
return new DecryptUtil().desDecryptFile(desDaraPath, downLoadPath, key);
}
public boolean verifySignature(String filePath, String signFilePath) {
try {
return DecryptUtil.verifySignature(filePath, signFilePath, DfCert.getInstance().getPUBLIC_KEY());
} catch (Exception e) {
return false;
}
}
/**
* 获取高压电池电量百分比。
*
* <p>此方法通过调用`mCarManager.getHVPercent`来获取高压电池的电量百分比。如果调用成功,则返回电量百分比值;
* 如果调用失败或发生异常,则捕获异常并打印堆栈跟踪,最后返回`0`。</p>
*
* @return 一个浮点数,表示高压电池的电量百分比(0-100);如果获取失败或发生异常,则返回`0`。
*/
public float getHVPercent() {
float HVPercent = 0;
try {
HVPercent = mCarManager.getHVPercent();
if(HVPercent > 100 || HVPercent < 0) {
HVPercent = 0;
}
} catch (Exception e) {
e.printStackTrace();
}
return HVPercent;
}
boolean isMounted() {
return USBReceiver.isMounted(updateUSBpath);
}
public String getCountryDbPath() {
return mHmiProxyJNI.getCountryDbPath();
}
public String get_Global_m_data_address() {
return mHmiProxyJNI.get_Global_m_data_address();
}
public boolean dataVerification(String verifyDataPath) {
LogUtils.i(TAG, "dataVerification[verifyDataPath]:" + verifyDataPath);
String signPath = get_Global_m_data_address() + "sign/";
if(!mapControl()) {
return false;
}
//解压加密数据
LogUtils.i(TAG, "verifyDataPath[" + verifyDataPath + "] -> signPath[" + signPath + "]");
if(!unzipFile(verifyDataPath, signPath)) {
//删除解密验签中间文件
if(!deleteDirectory(signPath)) {
LogUtils.e(TAG, "无法删除解密验签中间文件!");
}
return false;
}
if(!mapControl()) {
return false;
}
//提取签名文件以及加密文件的绝对路径
String[] temp = ZipUtils.getInstance().findEncryptedZipFiles(signPath);
String aesDaraPath = temp[1];
String signDataPtah = temp[0];
String KeyDataPath = temp [2];
LogUtils.i(TAG, "aesDaraPath:" + aesDaraPath);
LogUtils.i(TAG, "signDataPtah" + signDataPtah);
LogUtils.i(TAG, "KeyDataPath" + KeyDataPath);
if(aesDaraPath == null || signDataPtah == null || KeyDataPath == null){
return false;
}
String key = readKeyFromFile(KeyDataPath);
if(!mapControl()) {
return false;
}
//验证签名
if(!verifySignature(aesDaraPath, signDataPtah)) {
LogUtils.e(TAG, "verifySignature fail!");
//删除解密验签中间文件
if(!deleteDirectory(signPath)) {
LogUtils.e(TAG, "无法删除解密验签中间文件!");
}
return false;
}
if(!mapControl()) {
return false;
}
//对称解密Zip
if(!decryptFile(aesDaraPath, verifyDataPath)) {
LogUtils.e(TAG, "decryptFile fail!");
//删除解密验签中间文件
if(!deleteDirectory(signPath)) {
LogUtils.e(TAG, "无法删除解密验签中间文件!");
}
return false;
}
//删除解密验签中间文件
if(!deleteDirectory(signPath)) {
LogUtils.e(TAG, "无法删除解密验签中间文件!");
}
//des解密
LogUtils.i(TAG, "verifyDataPath:" + verifyDataPath);
boolean isSuccess = desDecrypt(verifyDataPath,key);
if(!isSuccess){
LogUtils.i(TAG, "DES解密结果: 失败" );
return false;
}
LogUtils.i(TAG, "DES解密结果:成功 " );
LogUtils.i(TAG, "dataVerification success");
return true;
}
/**
* 删除指定目录及其所有内容
*
* @param directory 指定的目录路径
* @return 如果删除成功返回 true,否则返回 false
*/
public boolean deleteDirectory(String directory) {
File dir = new File(directory);
if (!dir.exists()) {
LogUtils.e("目录不存在: " + directory);
return false; // 目录不存在,视为删除失败
}
if (!dir.isDirectory()) {
LogUtils.e("指定路径不是一个目录: " + directory);
return false; // 路径不是目录,无法删除
}
return deleteRecursive(dir); // 递归删除目录
}
/**
* 递归删除文件或目录
*
* @param file 文件或目录
* @return 如果删除成功返回 true,否则返回 false
*/
private boolean deleteRecursive(File file) {
if (file.isDirectory()) {
// 如果是目录,递归删除其所有子文件和子目录
File[] files = file.listFiles();
if (files != null) {
for (File subFile : files) {
if (!deleteRecursive(subFile)) {
return false; // 如果某个子文件或子目录删除失败,则整体失败
}
}
}
}
// 删除当前文件或空目录
if (!file.delete()) {
LogUtils.e("无法删除文件或目录: " + file.getAbsolutePath());
return false; // 删除失败
}
LogUtils.i("已成功删除: " + file.getAbsolutePath());
return true; // 删除成功
}
public boolean mapControl() {
LogUtils.d(TAG, "mapControl JAVA start");
return mHmiProxyJNI.mapControl();
}
public boolean zipDbFiles(String sourceDir, String outputZipPath) {
return ZipUtils.getInstance().zipDbFiles(sourceDir, outputZipPath);
}
public boolean zip(String sourcePath, String outputZipPath) {
return ZipUtils.getInstance().zip(sourcePath, outputZipPath, true);
}
// Used to load the 'native-lib' library on application startup.
static {
LogUtils.d(TAG, "loadLibrary IsaEngineJni " + System.currentTimeMillis());
System.loadLibrary("IsaEngineJni");
}
}
package com.isa.navi.jni.hmi;
import android.os.Build;
import android.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.io.*;
import java.nio.charset.StandardCharsets;
import android.util.Base64;
import com.isa.navi.utils.LogUtils;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
import org.json.JSONObject;
import org.json.JSONException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.NoSuchAlgorithmException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.MessageDigest;
import java.security.Security;
import java.security.Signature;
import java.util.Objects;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* 对称解密管理类,负责提供对称解密相关方法。
*/
public class DecryptUtil {
/**
* 用于日志记录的标签。
* <p>这是一个静态常量,用于在日志输出中标识下载管理器的相关日志。</p>
*/
private static final String TAG = "DecryptUtil";
private long lastCheckTime;
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 对称解密文件
* @param encryptedFilePath 加密文件路径
* @param decryptedFilePath 解密后文件输出路径
* @param ENCRYPTION_KEY_BASE64 密钥
* @return 解密是否成功,成功返回true,失败返回false
*/
public boolean decryptFile(String encryptedFilePath, String decryptedFilePath, String ENCRYPTION_KEY_BASE64) {
boolean success = false;
try {
// 创建密钥和IV规格
SecretKeySpec keySpec = new SecretKeySpec(getKey(ENCRYPTION_KEY_BASE64), "AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(getIV(ENCRYPTION_KEY_BASE64));
LogUtils.i(TAG, "创建密钥和IV规格 success!");
LogUtils.i(TAG, "创建密钥和IV规格 success!");
// 创建Cipher实例并初始化
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
LogUtils.i(TAG, "创建Cipher实例并初始化 success!");
if(!HmiJNIImpl.getInstance().mapControl()) {
return false;
}
//读取文件进行对称解密,并将解密后的文件按照指定路径输出
try (FileInputStream fis = new FileInputStream(encryptedFilePath);
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream(decryptedFilePath);
CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {
byte[] buffer = new byte[10240]; // 使用10KB的缓冲区
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
// 优化后的检查逻辑(每秒最多1次)
long currentTime = System.currentTimeMillis();
if (currentTime - lastCheckTime > 1000) {
synchronized (this) {
if (currentTime - lastCheckTime > 1000) {
if (!HmiJNIImpl.getInstance().mapControl()) {
return false;
}
lastCheckTime = currentTime;
}
}
}
}
LogUtils.i(TAG, "解密并写入数据 success!");
success = true;
} catch (IOException e) {
LogUtils.e(TAG, "处理文件时发生错误:" + e.getMessage());
throw new IOException("文件处理失败", e);
}
} catch (Exception e) {
// 捕获所有其他异常并记录错误
LogUtils.e(TAG, "解密文件时发生错误:" + e.getMessage());
}
return success;
}
/**
* 验签
* @param filePath 加密文件路径
* @param signFilePath 签名文件路径(.sig文件)
* @param CERT_KEY 公钥
* @return 签名成功返回true,签名失败返回false
*/
public static boolean verifySignature(String filePath, String signFilePath, String CERT_KEY) throws Exception {
if(!HmiJNIImpl.getInstance().mapControl()) {
return false;
}
try{
// 获取加密文件的SHA256哈希值
byte[] fileHash = getFileHash(filePath);
// 读取base64格式签名数据
String base64Signature = readSignatureFromFile(signFilePath);
LogUtils.i("base64格式签名数据: " + base64Signature);
// 解码签名
byte[] signatureBytes = Base64.decode(base64Signature,Base64.DEFAULT);
// 加载公钥
PublicKey publicKey = loadPublicKey(CERT_KEY);
LogUtils.i("PUBLIC_KEY: " + publicKey);
if(!HmiJNIImpl.getInstance().mapControl()) {
return false;
}
// 验签
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(fileHash);
return signature.verify(signatureBytes);
} catch (Exception e){
e.printStackTrace();
return false;
}
}
public boolean desDecryptFile(File desDataPath, String downLoadPath, String key) {
final int BUFFER_SIZE = 8 * 1024 * 1024; // 8MB缓冲区
try {
// // 生成密钥(与加密时一致)
// byte[] utf8Bytes = key.getBytes(StandardCharsets.UTF_8);
//
// // 2. Base64 编码(使用 Base64.DEFAULT,无换行)
// String base64Str = Base64.encodeToString(utf8Bytes,Base64.DEFAULT);
//
// // 3. 取前16个字符
// String result = base64Str.substring(0, 16);
//
// byte[] byteStream = result.getBytes(StandardCharsets.UTF_8);
// SecretKeySpec secretKeySpec = new SecretKeySpec(byteStream, "DES");
LogUtils.i(TAG,"开始解析des秘钥");
DESKeySpec desKeySpec = new DESKeySpec(Base64.encode(key.getBytes("utf-8"), Base64.DEFAULT));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
LogUtils.i(TAG,"des秘钥解析成功");
LogUtils.i(TAG,"初始化DES秘钥");
// 初始化DES解密Cipher
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
LogUtils.i(TAG,"秘钥初始化完成");
LogUtils.i(TAG,"开始解析文件");
// 解析文件名
String name = desDataPath.getName();
int lastDotIndex = name.lastIndexOf(".");
String fileExtension = (lastDotIndex == -1) ? "" : name.substring(lastDotIndex + 1);
int firstIndex = name.indexOf("_");
String fileNameContent = (firstIndex == -1) ? name : name.substring(0, firstIndex);
// 创建输出文件
File outputFile = new File(downLoadPath, fileNameContent + "." + fileExtension);
try (InputStream inputStream = new FileInputStream(desDataPath);
BufferedInputStream bis = new BufferedInputStream(inputStream);
FileOutputStream fos = new FileOutputStream(outputFile);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Android 8.0+ 使用流式Base64解码
LogUtils.i("Android 8.0+ 使用流式Base64解码");
java.util.Base64.Decoder base64Decoder = java.util.Base64.getMimeDecoder();
try (InputStream base64DecodeStream = base64Decoder.wrap(bis);
CipherInputStream cis = new CipherInputStream(base64DecodeStream, cipher)) {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = cis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
// 优化后的检查逻辑(每秒最多1次)
long currentTime = System.currentTimeMillis();
if (currentTime - lastCheckTime > 1000) {
synchronized (this) {
if (currentTime - lastCheckTime > 1000) {
if (!HmiJNIImpl.getInstance().mapControl()) {
return false;
}
lastCheckTime = currentTime;
}
}
}
}
}
}
}
LogUtils.i("文件解密成功,保存为: " + outputFile.getAbsolutePath());
return true;
} catch (Exception e) {
// 修复5:添加详细错误日志
LogUtils.e(TAG, "解密失败: " + e.getMessage()
+ "\n文件: " + desDataPath.getAbsolutePath()
+ "\n密钥: " + key.substring(0, Math.min(4, key.length())) + "***");
return false;
}
}
// 处理Base64数据块:解码->解密->写入
private void processBase64Chunk(ByteArrayOutputStream base64Buffer, Cipher cipher, BufferedOutputStream bos)
throws Exception {
byte[] base64Data = base64Buffer.toByteArray();
base64Buffer.reset();
byte[] decodedData = Base64.decode(base64Data, Base64.DEFAULT);
byte[] decryptedData = cipher.update(decodedData);
if (decryptedData != null) {
bos.write(decryptedData);
}
}
// 从 .sig 文件读取签名
public static String readSignatureFromFile(String sigFilePath) throws IOException , JSONException{
byte[] fileBytes = new byte[(int) new File(sigFilePath).length()];
try (FileInputStream fis = new FileInputStream(sigFilePath)) {
fis.read(fileBytes);
}
String fileContent = new String(fileBytes).trim();
// 解析 JSON,提取 signature 字段
JSONObject json = new JSONObject(fileContent);
return json.getString("signature");
}
// 获取byte数组格式向量
private static byte[] getIV(String iv) {
// 1. 清理密钥
String cleanKey = iv.replaceAll("\\?+$", "");
// 2. 提取第一个!到第二个!之间的部分
String[] parts = cleanKey.split("!");
if (parts.length < 2) {
throw new IllegalArgumentException("Invalid key format");
}
String prefix = parts[1];
String reversed = new StringBuilder(prefix.substring(0, 7))
.reverse()
.append("!")
.toString();
reversed = reversed + "abcdefg!";
return reversed.getBytes(StandardCharsets.UTF_8);
}
// 获取byte数组格式秘钥
private static byte[] getKey(String password) {
String cleaned = password.replaceAll("[^!$a-zA-Z0-9]", "");
return cleaned.getBytes(StandardCharsets.UTF_8);
}
// 获取加密文件的SHA256哈希值
public static byte[] getFileHash(String filePath) throws IOException {
try{
MessageDigest digest = MessageDigest.getInstance("SHA-256");
File file = new File(filePath);
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
}
return digest.digest();
}catch (NoSuchAlgorithmException e){
e.printStackTrace();
return null;
}
}
// 加载验签公钥
public static PublicKey loadPublicKey(String base64PublicKey) throws Exception {
base64PublicKey = base64PublicKey.replaceAll("\\s+", "");
base64PublicKey = base64PublicKey.replaceAll("[^A-Za-z0-9+/=]", "");
// 1. Base64解码
byte[] certBytes = Base64.decode(base64PublicKey,Base64.DEFAULT);
// 2. 解析证书链(PKCS#7/P7B格式)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteArrayInputStream bis = new ByteArrayInputStream(certBytes);
List<X509Certificate> certChain = (List<X509Certificate>) cf.generateCertificates(bis);
// 3. 提取第一个证书的公钥(通常是终端实体证书)
if (certChain.isEmpty()) {
throw new IllegalArgumentException("证书链为空!");
}
return certChain.get(0).getPublicKey();
}
}
分析这两段代码