protected-broadcast 的一些细节

本文深入探讨了Android系统中Protected-Broadcast机制的实现原理及其应用场景,包括如何限制第三方应用发送特定类型的广播,以及如何判断一个广播行动是否受到保护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://blog.youkuaiyun.com/u013553529/article/details/78409382


protected-broadcast 的一些细节

★ 1. 引言

注:本文中提及的“广播(Broadcast)”,“广播事件”和“Action”的意思大致相同。发送广播(sendBroadcast)也是发送一个指定的action给BroadcastReceiver。在本文中不严格区分“广播”和“Action”,除非有地方特别说明。

对于 android 系统应用来说,运用protected-broadcast是基本的安全要求。 
顾名思义,protected-broadcast是保护广播事件(Action)不被滥用的。相对的,如果一个action是不受protected-broadcast保护,并且使用此action的<receiver>组件(称之为MyReceiver1)没有system或者signature权限保护的话,这时任何app都可以发送此action的广播给MyReceiver1

对于 Android 系统应用来说,用protected-broadcast保护action不被滥用,可以不用声明system或者signature权限去保护receiver。当然,从安全的角度来说,给每一个组件设置适当的system或者signature权限是更好的。

你如果看过 Android 源码,一定在frameworks\base\core\res中的AndroidManifest.xml文件中看到过这样的代码:

    <protected-broadcast android:name="android.intent.action.SCREEN_OFF" />
    <protected-broadcast android:name="android.intent.action.SCREEN_ON" />
    <protected-broadcast android:name="android.intent.action.USER_PRESENT" />
    <protected-broadcast android:name="android.intent.action.TIME_SET" />
    <protected-broadcast android:name="android.intent.action.TIME_TICK" />
    <protected-broadcast android:name="android.intent.action.TIMEZONE_CHANGED" />
    <protected-broadcast android:name="android.intent.action.DATE_CHANGED" />
    <protected-broadcast android:name="android.intent.action.PRE_BOOT_COMPLETED" />
    <protected-broadcast android:name="android.intent.action.BOOT_COMPLETED" />
    <protected-broadcast android:name="android.intent.action.PACKAGE_INSTALL" />
    <protected-broadcast android:name="android.intent.action.PACKAGE_ADDED" />
(略)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

本文就是针对protected-broadcast做一些细节方面的介绍,包括:

  • (1)protected-broadcast的适用范围:哪些应用可以使用这个特性?有系统签名的非系统app,能够使用这个特性吗?

  • (2)Android 系统是如何阻止第三方app发送被 protected-broadcast 保护的广播(Action)的?

  • (3)protected-broadcast 是对Activity(startActivity)、Receiver(sendBroadcast)、Service(startService)都有效吗?

  • (4)如何判断一个action是否是受保护的?

接下来一一说明。

★ 2. protected-broadcast的适用范围:哪些应用可以使用这个特性?有系统签名的非系统app,能够使用这个特性吗?

对于这个问题,我们可以从PackageParser.java入手,从PackageParser解析apk里的AndroidManifest.xml开始。

PackageParser.java 在 frameworks\base\core\java\android\content\pm目录中。

PackageParser.java中定义的常量TAG_PROTECTED_BROADCAST

private static final String TAG_PROTECTED_BROADCAST = "protected-broadcast";
  • 1

PackageParser的内部类Package中的变量protectedBroadcasts

public ArrayList<String> protectedBroadcasts;
  • 1

解析 apk 中的 AndroidManifest.xml 的代码在 PackageParser.java 中的parseBaseApkCommon()

   private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
            XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
            IOException {
            //(略)
            } else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
                // 解析<protected-broadcast>标签
                // 获取 AndroidManifestProtectedBroadcast 的属性值
                // 这里 res 是 Resources res, sa 是 TypedArray sa。
                sa = res.obtainAttributes(parser,
                        com.android.internal.R.styleable.AndroidManifestProtectedBroadcast);

                // getNonResourceString()确保protected broadcast 的name是从xml中得到的,而不能是引用strings.xml中的字符串。
                // protected broadcast 的name在编译之后不能改变,除非重新编译。
                // AndroidManifestProtectedBroadcast_name是属性值的索引,其值为0。
                // name 为获取到的受保护的action名。
                String name = sa.getNonResourceString(
                        com.android.internal.R.styleable.AndroidManifestProtectedBroadcast_name);

                sa.recycle();

                if (name != null && (flags & PARSE_IS_SYSTEM) != 0) {
                    // PARSE_IS_SYSTEM表明只在解析系统app时,protected broadcast才会有效
                    // pkg 是内部类 Package 的实例
                    if (pkg.protectedBroadcasts == null) {
                        pkg.protectedBroadcasts = new ArrayList<String>();
                    }
                    if (!pkg.protectedBroadcasts.contains(name)) {
                        // 所有的受保护的action都放到protectedBroadcasts
                        pkg.protectedBroadcasts.add(name.intern());
                    }
                }

                // 此标签<protected-broadcast>解析完毕,跳到END_TAG:'/>' 或者 '</protected-broadcast>'
                XmlUtils.skipCurrentTag(parser);
            }
            略
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

(flags & PARSE_IS_SYSTEM) != 0这个判断可知,可以使用<protected-broadcast>标签的app只能是系统app如果不是系统app,那么此标签将被忽略

哪些app是系统app呢? 
有两类: 
* 一类是uid为 android.uid.systemandroid.uid.phoneandroid.uid.logandroid.uid.nfcandroid.uid.bluetoothandroid.uid.shell的app是系统app。

  • 另一类是指定目录中的app是系统app,“指定目录”包括/vendor/overlay/system/framework/system/priv-app/system/app/vendor/app/oem/app

解析这两类apk的时候,用到了PARSE_IS_SYSTEM标志位。

关于系统app的更多细节,可以参考 《Android 权限的一些细节》 。

替换系统app(replace)或者升级系统app(upgrade)时,也用到了PARSE_IS_SYSTEM标志位,如果系统app在新版本中增加了<protected-broadcast>也会被处理。

如果安装一个跟系统签名一样的app,不是替换系统app,也不是升级系统app的话,就不会用到PARSE_IS_SYSTEM标志来解析apk,所以在这种情况下,有系统签名但不是系统app,不能使用<protected-broadcast>特性

★ 3. Android 系统是如何阻止第三方app发送protected-broadcast保护的广播(Action)的?

分两个部分:

  • 第一部分:受保护的广播是如何保存到PackageManagerService(简称为PMS)的,即protected-broadcast从系统app到PMS。

  • 第二部分:什么时候检查广播(action)是不是受保护的。

♦ 3.1 受保护的广播是如何保存到PMS的

PackageManagerService.java

Android 7.1.1中大致的流程如下:

-> PackageManagerService()构造方法
-> scanDirTracedLI()
-> scanDirLI(File dir, ...) {// 为了与Android 8.0代码做对比,这里列出一些细节
    final File[] files = dir.listFiles();
    for (File file : files) {
        scanPackageTracedLI(); // 这里是串行处理,目录dir中的apk是一个接一个解析的
    }
}
-> scanPackageTracedLI((File scanFile, ...)
-> scanPackageLI(File scanFile, ...)
    -> pp.parsePackage(scanFile, parseFlags);
-> scanPackageLI(PackageParser.Package pkg, File scanFile, ...)
-> scanPackageInternalLI()
-> scanPackageLI(PackageParser.Package pkg, final int policyFlags, ...)
-> scanPackageDirtyLI() {
   if (pkg.protectedBroadcasts != null) {
        N = pkg.protectedBroadcasts.size();
        for (i=0; i<N; i++) {
            mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Android 8.0中大致的流程如下:

-> PackageManagerService()构造方法
-> scanDirTracedLI()
-> scanDirLI(File dir, ...) {// 这里采用了并行处理,7.1.1中是串行处理的
    final File[] files = dir.listFiles();
    ParallelPackageParser parallelPackageParser = new ParallelPackageParser()
    for (File file : files) {
        // 将解析apk的任务交给多个线程来处理
        parallelPackageParser.submit(file, parseFlags);
        fileCount++;
    }
    for (; fileCount > 0; fileCount--) {
        // take()取出解析apk的结果,如果没有解析完,则等待
        // 哪个apk先解析完,就先处理哪个apk(scanPackageLI())
        ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
        Throwable throwable = parseResult.throwable;
        if (throwable == null) {// 解析apk无异常
            scanPackageLI(parseResult.pkg, parseResult.scanFile, ...);
        }
    }
    parallelPackageParser.close();
}
-> scanPackageLI(PackageParser.Package pkg, File scanFile, ...)
-> scanPackageInternalLI()
-> scanPackageLI(File scanFile, int parseFlags, ...)
    -> parsePackage() // 这又解析了一次?不知道为什么,没有用之前解析好的(useCaches为false)
-> scanPackageLI(PackageParser.Package pkg, final int policyFlags, ...)
-> scanPackageDirtyLI()
-> commitPackageSettings() {
    if (pkg.protectedBroadcasts != null) {
        N = pkg.protectedBroadcasts.size();
        for (i=0; i<N; i++) {
            mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

<protected-broadcast>保存到PMS后,PMS提供了一个接口isProtectedBroadcast()供其他应用调用,目前只是AMS在调用。

从下面的代码中可以看到,mProtectedBroadcasts中的action是受保护的,除此之外,某些action名字是受保护的或者说是被禁止的,第三方app不能乱发,例如,以android.net.netmon.lingerExpired开头的action。

    @Override
    public boolean isProtectedBroadcast(String actionName) {
        synchronized (mPackages) {
            if (mProtectedBroadcasts.contains(actionName)) {
                return true;
            } else if (actionName != null) {
                if (actionName.startsWith("android.net.netmon.lingerExpired")
                        || actionName.startsWith("com.android.server.sip.SipWakeupTimer")
                        || actionName.startsWith("com.android.internal.telephony.data-reconnect")
                        || actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) {
                    return true;
                }
            }
        }
        return false;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

调用PMS的isProtectedBroadcast()是通过IPackageManager接口。

IPackageManager.aidl

    boolean isProtectedBroadcast(String actionName);
  • 1
  • 2
  • 3

♦ 3.2 检查广播(action)是不是受保护的

这是在 ActivityManagerService (简称AMS)中执行的,最终调用的是PMS的接口isProtectedBroadcast()

ActivityManagerService.java 
大致的调用过程如下:

-> App中调用sendBroadcast()
-> sendBroadcast() @ ContextImpl
-> broadcastIntent() @ ActivityManagerService
-> broadcastIntentLocked() {
    final String action = intent.getAction();
    isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
    if (!isCallerSystem) {// 如果调用者不是系统app,即调用sendBroadcast()的不是系统app
        if (isProtectedBroadcast) {//action是被保护的广播,那么将抛出异常
            String msg = "Permission Denial: not allowed to send broadcast "
                    + action + " from pid="
                    + callingPid + ", uid=" + callingUid;
            throw new SecurityException(msg);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

由上面可知,如果调用sendBroadcast()的不是系统app,并且广播action是受保护的,那么将抛出SecurityException异常。

★ 4. protected-broadcast是对Activity(startActivity)、Receiver(sendBroadcast)、Service(startService)都有效吗?

从上面的分析来看,不是都有效。 
而且从名字protected-broadcast直观的来看(猜),也是只保护广播action的,不保护 startActivity 和 startService 的action。 
都是action,待遇怎么差那么多呢?

★ 5. 如何判断一个action是否是受保护的?

如果对apk进行渗透测试,经常会遇到Receiver组件暴露的问题,但是Receiver接收的action是否受保护呢?这里提供一种方法来判断action是不是受保护的。代码如下:

package com.galian.mylib;

import android.content.pm.IPackageManager;
import android.os.RemoteException;
import android.util.Log;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Utils {

    private static final String TAG = "Utils";
    public static boolean isProtectedBroatcast(String action) {
        Class<?> cls = null;
        Method method = null;
        boolean ret = false;

        try {
            cls = Class.forName("android.app.ActivityThread");
            method = cls.getMethod("getPackageManager", null);
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
            IPackageManager iPackageManager = (IPackageManager) method.invoke(cls, null);
            ret = iPackageManager.isProtectedBroadcast(action);
            Log.d(TAG, "action: " + action + " is " + (ret?"protected.":" not protected."));
        } catch ...
        //(catch 代码块略)
        return ret;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

要想使上面的代码编译通过,还需要framework生成的jar包framework-classes-full-debug.jar。 
在 build.gradle中引用 framework-classes-full-debug.jar 包:

dependencies {
    //compile fileTree(dir: 'libs', include: ['*.jar']) // 一定要注释掉,否则会发生method数超过65536的问题
    provided files('libs/framework-classes-full-debug.jar')
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5

我用Android 8.0 代码编译生成的 framework-classes-full-debug.jar,放到了github,地址为:https://github.com/galian123/Samples/blob/master/mylib/libs/framework-classes-full-debug.jar。 
整个工程也传到了github,地址:https://github.com/galian123/Samples


以代码为基础,告诉我这段代码涉及哪些知识点,想要读懂理解最终编写这种代码的学习步骤:package com.isa.navi.jni.hmi; import android.content.Context; import android.os.Message; import android.os.StatFs; import android.util.Log; import androidx.annotation.NonNull; import com.isa.navi.jni.hmi.bean.UpdateList; 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.IOException; import java.time.LocalDate; import java.time.Month; import java.util.ArrayList; import java.util.Collections; 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); } }); // 初始化C++模块 mHmiProxyJNI.InitializeCpp(); InstanceEngine(); //----------------TEST-START-zwx UpdateTest(); } public void UpdateTest() { Thread UpdateTest = new Thread(new Runnable(){ @Override public void run() { LogUtils.i(TAG, "UpdateTest start!"); setUSBPath("/data/maptest/KVM_EU_202502_T1.0_S31N.zip"); updateMap(); } }); UpdateTest.setName("ISA_NAVI_UpdateTest"); UpdateTest.start(); } //----------------TEST-END-zwx 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,其他状态直接放行update 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; for(File file : listFiles) { //判断U盘内是否含有升级文件 if(!file.isDirectory() && mFormat.isUpgradeFileNamingFormat(file.getName())) { isHaveUSBData = true; if(newfile == null) { newfile = file; } else { if(Format.getInstance().isNewerVersion(file.getName().replaceAll("\\.\\w+$", ""), newfile.getName().replaceAll("\\.\\w+$", ""))) { newfile = file; } } } } //未检测到U盘指定路径下是否有数据,触发提示弹窗 if(!isHaveUSBData || newfile == null) { LogUtils.e(TAG, "The USB drive does not contain an upgrade package!"); bean.setUpdateStatus(3); handleDownloadStatusChange(bean); return; }else{ // 从文件名中提取版本号 String localDataPath = Format.getInstance().extractVersion(getCountryDbPath()); LogUtils.i(TAG, "file name:" + newfile.getName()); LogUtils.i(TAG, "localDataPath:" + localDataPath); if(localDataPath.isEmpty()) { String mapinfo = getMapInfo(); if(!mapinfo.isEmpty()) { String[] newVersionParts = new String[2]; Format.getInstance().extractVersion(newfile.getName().replaceAll("\\.\\w+$", ""), newVersionParts); String newUpdateVersion = newVersionParts[0]; String newReleasesVersion = newVersionParts[1]; 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(newfile.getName().replaceAll("\\.\\w+$", ""), 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 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 boolean dataVerification(String verifyDataPath) { LogUtils.i(TAG, "dataVerification[verifyDataPath]:" + verifyDataPath); String signPath = "/data/data/com.isa.navi/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]; LogUtils.i(TAG, "aesDaraPath:" + aesDaraPath); LogUtils.i(TAG, "signDataPtah" + signDataPtah); 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, "无法删除解密验签中间文件!"); } 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"); } }
最新发布
07-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值