Android源码 ota升级

一、系统Setting添加A/B区,OTA升级涉及核心类

1.xml文件

路径:/packages/apps/Settings/res/xml/my_device_info.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
  Copyright (C) 2018 The Android Open Source Project

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
  -->

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:key="my_device_info_pref_screen"
    android:title="@string/about_settings">

    <com.android.settingslib.widget.LayoutPreference
        android:key="my_device_info_header"
        android:order="0"
        android:layout="@layout/settings_entity_header"
        android:selectable="false"
        settings:isPreferenceVisible="false"/>

    <!-- Device name -->
    <com.android.settings.widget.ValidatedEditTextPreference
        android:key="device_name"
        android:order="1"
        android:title="@string/my_device_info_device_name_preference_title"
        android:summary="@string/summary_placeholder"
        settings:controller="com.android.settings.deviceinfo.DeviceNamePreferenceController"
        settings:enableCopying="true"/>

    <!-- Account name -->
    <Preference
        android:key="branded_account"
        android:order="2"
        android:title="@string/my_device_info_account_preference_title"
        android:summary="@string/summary_placeholder"
        settings:controller="com.android.settings.deviceinfo.BrandedAccountPreferenceController"/>

    <!-- Phone number -->
    <Preference
        android:key="phone_number"
        android:order="3"
        android:title="@string/status_number"
        android:summary="@string/summary_placeholder"
        android:selectable="false"
        settings:controller="com.android.settings.deviceinfo.PhoneNumberPreferenceController"
        settings:enableCopying="true"/>

    <Preference
        android:key="emergency_info"
        android:order="14"
        android:title="@string/emergency_info_title"
        android:summary="@string/summary_placeholder"
        settings:controller="com.android.settings.accounts.EmergencyInfoPreferenceController"/>

    <!-- Legal information -->
    <Preference
        android:key="legal_container"
        android:order="15"
        android:title="@string/legal_information"
        android:fragment="com.android.settings.LegalSettings"
        settings:allowDividerAbove="true"
        settings:searchable="false"/>

    <!-- Regulatory labels -->
    <Preference
        android:key="regulatory_info"
        android:order="16"
        android:title="@string/regulatory_labels">
        <intent android:action="android.settings.SHOW_REGULATORY_INFO"/>
    </Preference>

    <!-- Safety & regulatory manual -->
    <Preference
        android:key="safety_info"
        android:order="17"
        android:title="@string/safety_and_regulatory_info">
        <intent android:action="android.settings.SHOW_SAFETY_AND_REGULATORY_INFO"/>
    </Preference>

    <!-- SIM status -->
    <Preference
        android:key="sim_status"
        android:order="18"
        android:title="@string/sim_status_title"
        settings:keywords="@string/keywords_sim_status"
        android:summary="@string/summary_placeholder"
        settings:allowDividerAbove="true"/>

    <!-- Model & hardware -->
    <Preference
        android:key="device_model"
        android:order="31"
        android:title="@string/hardware_info"
        settings:keywords="@string/keywords_model_and_hardware"
        android:summary="@string/summary_placeholder"
        android:fragment="com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFragment"
        settings:controller="com.android.settings.deviceinfo.HardwareInfoPreferenceController"/>

    <!-- IMEI -->
    <Preference
        android:key="imei_info"
        android:order="32"
        android:title="@string/status_imei"
        settings:keywords="@string/keywords_imei_info"
        android:summary="@string/summary_placeholder"
        settings:controller="com.android.settings.deviceinfo.imei.ImeiInfoPreferenceController"/>

    <!-- Android version -->
    <Preference
        android:key="firmware_version"
        android:order="42"
        android:title="@string/firmware_version"
        android:summary="@string/summary_placeholder"
        android:fragment="com.android.settings.deviceinfo.firmwareversion.FirmwareVersionSettings"
        settings:controller="com.android.settings.deviceinfo.firmwareversion.FirmwareVersionPreferenceController"/>

    <!--IP address -->
    <Preference
        android:key="wifi_ip_address"
        android:order="44"
        android:title="@string/wifi_ip_address"
        android:summary="@string/summary_placeholder"
        android:selectable="false"
        settings:allowDividerAbove="true"
        settings:enableCopying="true"/>

    <!-- Wi-Fi MAC address -->
    <Preference
        android:key="wifi_mac_address"
        android:order="45"
        android:title="@string/status_wifi_mac_address"
        android:summary="@string/summary_placeholder"
        android:selectable="false"
        settings:enableCopying="true"/>

    <!-- Bluetooth address -->
    <Preference
        android:key="bt_address"
        android:order="46"
        android:title="@string/status_bt_address"
        android:summary="@string/summary_placeholder"
        android:selectable="false"
        settings:enableCopying="true"/>

    <!-- Device up time -->
    <Preference
        android:key="up_time"
        android:order="47"
        android:title="@string/status_up_time"
        android:summary="@string/summary_placeholder"
        android:selectable="false"/>

    <!-- Manual -->
    <Preference
        android:key="manual"
        android:order="50"
        android:title="@string/manual">
        <intent android:action="android.settings.SHOW_MANUAL"/>
    </Preference>

    <!-- Feedback on the device -->
    <Preference
        android:key="device_feedback"
        android:order="51"
        android:title="@string/device_feedback"
        settings:keywords="@string/keywords_device_feedback"/>

    <!-- Device FCC equipment id -->
    <Preference
        android:key="fcc_equipment_id"
        android:order="52"
        android:title="@string/fcc_equipment_id"
        android:summary="@string/summary_placeholder"/>

    <!-- Build number -->
    <Preference
        android:key="build_number"
        android:order="53"
        android:title="@string/build_number"
        android:summary="@string/summary_placeholder"
        settings:allowDividerAbove="true"
        settings:enableCopying="true"
        settings:controller="com.android.settings.deviceinfo.BuildNumberPreferenceController"/>

</PreferenceScreen>

2.java代码

java代码文件路径: /packages/apps/Settings/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java

/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.deviceinfo.aboutphone;

import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserManager;
import android.provider.SearchIndexableResource;
import android.view.View;

import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.deviceinfo.BluetoothAddressPreferenceController;
import com.android.settings.deviceinfo.BuildNumberPreferenceController;
import com.android.settings.deviceinfo.DeviceNamePreferenceController;
import com.android.settings.deviceinfo.FccEquipmentIdPreferenceController;
import com.android.settings.deviceinfo.FeedbackPreferenceController;
import com.android.settings.deviceinfo.IpAddressPreferenceController;
import com.android.settings.deviceinfo.ManualPreferenceController;
import com.android.settings.deviceinfo.RegulatoryInfoPreferenceController;
import com.android.settings.deviceinfo.SafetyInfoPreferenceController;
import com.android.settings.deviceinfo.UptimePreferenceController;
import com.android.settings.deviceinfo.WifiMacAddressPreferenceController;
import com.android.settings.deviceinfo.imei.ImeiInfoPreferenceController;
import com.android.settings.deviceinfo.simstatus.SimStatusPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.LayoutPreference;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@SearchIndexable
public class MyDeviceInfoFragment extends DashboardFragment
        implements DeviceNamePreferenceController.DeviceNamePreferenceHost {

    private static final String LOG_TAG = "MyDeviceInfoFragment";
    private static final String KEY_MY_DEVICE_INFO_HEADER = "my_device_info_header";

    private BuildNumberPreferenceController mBuildNumberPreferenceController;

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.DEVICEINFO;
    }

    @Override
    public int getHelpResource() {
        return R.string.help_uri_about;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        use(ImeiInfoPreferenceController.class).setHost(this /* parent */);
        use(DeviceNamePreferenceController.class).setHost(this /* parent */);
        mBuildNumberPreferenceController = use(BuildNumberPreferenceController.class);
        mBuildNumberPreferenceController.setHost(this /* parent */);
    }

    @Override
    public void onStart() {
        super.onStart();
        initHeader();
    }

    @Override
    protected String getLogTag() {
        return LOG_TAG;
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.my_device_info;
    }

    @Override
    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        return buildPreferenceControllers(context, this /* fragment */, getSettingsLifecycle());
    }

    private static List<AbstractPreferenceController> buildPreferenceControllers(
            Context context, MyDeviceInfoFragment fragment, Lifecycle lifecycle) {
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
        controllers.add(new SimStatusPreferenceController(context, fragment));
        controllers.add(new IpAddressPreferenceController(context, lifecycle));
        controllers.add(new WifiMacAddressPreferenceController(context, lifecycle));
        controllers.add(new BluetoothAddressPreferenceController(context, lifecycle));
        controllers.add(new RegulatoryInfoPreferenceController(context));
        controllers.add(new SafetyInfoPreferenceController(context));
        controllers.add(new ManualPreferenceController(context));
        controllers.add(new FeedbackPreferenceController(fragment, context));
        controllers.add(new FccEquipmentIdPreferenceController(context));
        controllers.add(new UptimePreferenceController(context, lifecycle));
        return controllers;
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (mBuildNumberPreferenceController.onActivityResult(requestCode, resultCode, data)) {
            return;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    private void initHeader() {
        // TODO: Migrate into its own controller.
        final LayoutPreference headerPreference =
                getPreferenceScreen().findPreference(KEY_MY_DEVICE_INFO_HEADER);
        final boolean shouldDisplayHeader = getContext().getResources().getBoolean(
                R.bool.config_show_device_header_in_device_info);
        headerPreference.setVisible(shouldDisplayHeader);
        if (!shouldDisplayHeader) {
            return;
        }
        final View headerView = headerPreference.findViewById(R.id.entity_header);
        final Activity context = getActivity();
        final Bundle bundle = getArguments();
        final EntityHeaderController controller = EntityHeaderController
                .newInstance(context, this, headerView)
                .setRecyclerView(getListView(), getSettingsLifecycle())
                .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
                        EntityHeaderController.ActionType.ACTION_NONE);

        // TODO: There may be an avatar setting action we can use here.
        final int iconId = bundle.getInt("icon_id", 0);
        if (iconId == 0) {
            final UserManager userManager = (UserManager) getActivity().getSystemService(
                    Context.USER_SERVICE);
            final UserInfo info = Utils.getExistingUser(userManager,
                    android.os.Process.myUserHandle());
            controller.setLabel(info.name);
            controller.setIcon(
                    com.android.settingslib.Utils.getUserIcon(getActivity(), userManager, info));
        }

        controller.done(context, true /* rebindActions */);
    }

    @Override
    public void showDeviceNameWarningDialog(String deviceName) {
        DeviceNameWarningDialog.show(this);
    }

    public void onSetDeviceNameConfirm(boolean confirm) {
        final DeviceNamePreferenceController controller = use(DeviceNamePreferenceController.class);
        controller.updateDeviceName(confirm);
    }

    /**
     * For Search.
     */
    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
            new BaseSearchIndexProvider() {

                @Override
                public List<SearchIndexableResource> getXmlResourcesToIndex(
                        Context context, boolean enabled) {
                    final SearchIndexableResource sir = new SearchIndexableResource(context);
                    sir.xmlResId = R.xml.my_device_info;
                    return Arrays.asList(sir);
                }

                @Override
                public List<AbstractPreferenceController> createPreferenceControllers(
                        Context context) {
                    return buildPreferenceControllers(context, null /* fragment */,
                            null /* lifecycle */);
                }
            };
}

三、制作OTA包

1.制作OTA 包,整包升级方式

执行命令

 make otapackage

OTA输出路径

out/target/product/(device)/(device)ota-eng.xss.zip

编译出整包,优点是只需保留最新版本就可以,缺点就是在升级过程中,需要下载的包太大,用户耗时耗力.

2.制作OTA差分包

差分包顾名思义,就是通过特定命令编译出,两个版本的差异部分,执行以下命令:

./build/make/tools/releasetools/ota_from_target_files.py -v -p ./out/host/linux-x86/ -i old-target-files.zip new-target-files.zip  fota.zip
old-target-files.zip

new-target-files.zip 这两个target 包,整编后位置在out/disk 下面 或者out/target/product/(device)/obj/PACKAGING/target_files_intermediates,差分包升级缺点是每次都得备份target 包,优点是差分包比较小,节约流量;

四、接下来在系统Settings里面添加ab分区升级的入口

1.my_device_info.xml在关于设备中新增system update来进行ab分区升级

在系统Settings中,关于设备的xml文件就是my_device_info.xml,接下来看下在这个xml中新增system update来进行ab分区升级功能实现

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:key="my_device_info_pref_screen"
    android:title="@string/about_settings">

    <com.android.settingslib.widget.LayoutPreference
        android:key="my_device_info_header"
        android:order="0"
        android:layout="@layout/settings_entity_header"
        android:selectable="false"
        settings:isPreferenceVisible="false"/>
    <!-- Phone number -->

<!--add core start-->
    <Preference
        android:key="local_system_update"
        android:order="0"
        android:title="System Update"
        android:summary="System Update"
        android:selectable="false"
        settings:controller="com.android.settings.deviceinfo.SystemUpdatePreferenceController"
        settings:enableCopying="true"/>
<!--add core end-->

    <!-- Device name -->
    <com.android.settings.widget.ValidatedEditTextPreference
        android:key="device_name"
        android:order="1"
        android:title="@string/my_device_info_device_name_preference_title"
        android:summary="@string/summary_placeholder"
        settings:controller="com.android.settings.deviceinfo.DeviceNamePreferenceController"
        settings:enableCopying="true"/>

    <!-- Account name -->
    <Preference
        android:key="branded_account"
        android:order="2"
        android:title="@string/my_device_info_account_preference_title"
        android:summary="@string/summary_placeholder"
        settings:controller="com.android.settings.deviceinfo.BrandedAccountPreferenceController"/>

    <!-- Phone number -->
    <Preference
        android:key="phone_number"
        android:order="3"
        android:title="@string/status_number"
        android:summary="@string/summary_placeholder"
        android:selectable="false"
        settings:controller="com.android.settings.deviceinfo.PhoneNumberPreferenceController"
        settings:enableCopying="true"/>

    <Preference
        android:key="emergency_info"
        android:order="14"
        android:title="@string/emergency_info_title"
        android:summary="@string/summary_placeholder"
        settings:controller="com.android.settings.accounts.EmergencyInfoPreferenceController"/>

    <!-- Legal information -->
    <Preference
        android:key="legal_container"
        android:order="15"
        android:title="@string/legal_information"
        android:fragment="com.android.settings.LegalSettings"
        settings:allowDividerAbove="true"/>

在实现系统settings添加ab分区ota升级功能中,通过上述在my_device_info.xml中,新增

android:key="local_system_update"的Preference来作为ab分区升级的控件,接下来看下

怎么在MyDeviceInfoFragment.java中新增相关的升级功能

2.MyDeviceInfoFragment.java中新增ota功能

@SearchIndexable
public class MyDeviceInfoFragment extends DashboardFragment
        implements DeviceNamePreferenceController.DeviceNamePreferenceHost {

    private static final String LOG_TAG = "MyDeviceInfoFragment";
    private static final String KEY_MY_DEVICE_INFO_HEADER = "my_device_info_header";

    private BuildNumberPreferenceController mBuildNumberPreferenceController;

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.DEVICEINFO;
    }

    @Override
    public int getHelpResource() {
        return R.string.help_uri_about;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        use(ImeiInfoPreferenceController.class).setHost(this /* parent */);
        use(DeviceNamePreferenceController.class).setHost(this /* parent */);
+        use(SystemUpdatePreferenceController.class).setActivity(getActivity());
        mBuildNumberPreferenceController = use(BuildNumberPreferenceController.class);
        mBuildNumberPreferenceController.setHost(this /* parent */);
    }

在实现系统settings添加ab分区ota升级功能中,通过上述在MyDeviceInfoFragment.java中,

由于需要在SystemUpdatePreferenceController中传递一个activity对象方便Dialog弹窗的实现,

所以就需要给新增的setActivity(getActivity());赋值来实现功能

五、相关update_engine的selinux授权

--- a/system/sepolicy/private/domain.te
+++ b/system/sepolicy/private/domain.te
@@ -311,6 +311,7 @@ define(`dac_override_allowed', `{
   vold
   vold_prepare_subdirs
   zygote
+  update_engine
 }')
 neverallow ~dac_override_allowed self:global_capability_class_set dac_override;
 # Since the kernel checks dac_read_search before dac_override, domains that
@@ -323,6 +324,7 @@ neverallow ~{
   iorap_prefetcherd
   traced_perf
   traced_probes
+  update_engine
   userdebug_or_eng(`heapprofd')
 } self:global_capability_class_set dac_read_search;

--- a/system/sepolicy/prebuilts/api/30.0/private/domain.te
+++ b/system/sepolicy/prebuilts/api/30.0/private/domain.te
@@ -311,6 +311,7 @@ define(`dac_override_allowed', `{
   vold
   vold_prepare_subdirs
   zygote
+  update_engine
 }')
 neverallow ~dac_override_allowed self:global_capability_class_set dac_override;
 # Since the kernel checks dac_read_search before dac_override, domains that
@@ -323,6 +324,7 @@ neverallow ~{
   iorap_prefetcherd
   traced_perf
   traced_probes
+  update_engine
   userdebug_or_eng(`heapprofd')
 } self:global_capability_class_set dac_read_search;

--- a/device/mediatek/sepolicy/basic/non_plat/update_engine.te
+++ b/device/mediatek/sepolicy/basic/non_plat/update_engine.te
@@ -32,7 +32,7 @@ allow update_engine postinstall_mnt_dir:dir { search getattr open read write sea
 # Add for AVB20
 allow update_engine tmpfs:lnk_file read;
 
-allow update_engine metadata_file:dir { getattr mounton };
+allow update_engine metadata_file:dir { getattr mounton search };
 allow update_engine devpts:chr_file rw_file_perms;
 allow update_engine kmsg_device:chr_file w_file_perms;
 
@@ -51,4 +51,9 @@ recovery_only(`
 # Purpose: Add permission for pl path utilities
 allow update_engine sysfs_mm:dir search;
 allow update_engine postinstall_block_device:dir w_dir_perms;
-allow update_engine postinstall_block_device:lnk_file create_file_perms;
\ No newline at end of file
+allow update_engine postinstall_block_device:lnk_file create_file_perms;
+allow update_engine storage_file:lnk_file {read write};
+allow update_engine mnt_user_file:dir {search};
+allow update_engine sysfs_boot_type:file { read open getattr write unlink};
+allow update_engine update_engine:capability dac_read_search;
+allow update_engine update_engine:capability dac_override;

六、系统settings添加ab分区ota升级功能实现二的核心类

packages\apps\Settings\src\com\android\settings\deviceinfo\UpdateParser.java
packages\apps\Settings\src\com\android\settings\deviceinfo\SystemUpdatePreferenceController.java

七、系统settings添加ab分区ota升级功能实现二的核心功能分析和实现

A/B 系统更新(也称为无缝更新)的目标是确保在无线下载 (OTA) 更新期间在磁盘上保留一个可正常启动和使用的系统。采用这种方式可以降低更新之后设备无法启动的可能性在 Android 4.4 和 Android 10 上沿用之前的 RecoverySystem.installPackage(mContext, OTA_PACKAGE); 方式是没出问题的,但是在 Android 12 貌似不支持这种升级方式了,于是开启检索之路

接下来就来看下关于ota ab分区功能实现google 已经为我们集成了升级方式,AB 升级和Recovery 升级,相对来说recovery 会进入系统recovery mode ,影响用户使用,而AB 升级是无感升级,两个slot 重启切换,不影响用户使用,体验更好.目前android 11 默认是开启 virtural AB,对于上层来说其实没啥区别,都是调用updata_engine就可以了。

我们主要关注几个状态码

case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT:

升级成功,提示您是否重启

public void onPayloadApplicationComplete(int errorCode)

中的

case UpdateEngine.ErrorCodeConstants.SUCCESS:

提示升级完成,

升级失败的,主要监听这里面的,这个是最终状态.

OTA 包

整包升级

make otapackage

out/target/product/(device)/(device)ota-eng.xss.zip

编译出整包,优点是只保留最新版本就可以了,缺点是下载包太大.

差分包

差分包顾名思义,就是两个版本的差异,通过以下命令.

./build/make/tools/releasetools/ota_from_target_files.py -v -p ./out/host/linux-x86/ -i old-target-files.zip new-target-files.zip  fota.zip

old-target-files.zip

new-target-files.zip 这两个target 包,整编后位置在out/disk 下面

或者out/target/product/(device)/obj/PACKAGING/target_files_intermediates

差分包升级缺点是每次都得备份target 包,优点是差分包比较小,节约流量;

UpdateEngine类(@SystemApi)主要提供bind和applyPayload接口给应用

bind(final UpdateEngineCallback callback, final Handler handler) 主要接受UpdateEngineCallback对象,同步代码块中会实现callback的两个接口,获取升级服务的状态码和结果错误码

applyPayload传递升级包路径大小等信息,并会传递到服务端进行实际逻辑的操作

7.1新增解析UpdateEngine的ota升级包的工具类

package com.android.settings.deviceinfo;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;

import com.android.internal.util.Preconditions;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/** Parse an A/B update zip file. */
class UpdateParser {

    private static final String TAG = "UpdateLayoutFragment";
    private static final String PAYLOAD_BIN_FILE = "payload.bin";
    private static final String PAYLOAD_PROPERTIES = "payload_properties.txt";
    private static final String FILE_URL_PREFIX = "file://";
    private static final int ZIP_FILE_HEADER = 30;

    private UpdateParser() {
    }

    /**
     * Parse a zip file containing a system update and return a non null ParsedUpdate.
     */
    @Nullable
    static ParsedUpdate parse(@NonNull File file) throws IOException {
        Preconditions.checkNotNull(file);

        long payloadOffset = 0;
        long payloadSize = 0;
        boolean payloadFound = false;
        String[] props = null;

        try (ZipFile zipFile = new ZipFile(file)) {
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                long fileSize = entry.getCompressedSize();
                if (!payloadFound) {
                    payloadOffset += ZIP_FILE_HEADER + entry.getName().length();
                    if (entry.getExtra() != null) {
                        payloadOffset += entry.getExtra().length;
                    }
                }

                if (entry.isDirectory()) {
                    continue;
                } else if (entry.getName().equals(PAYLOAD_BIN_FILE)) {
                    payloadSize = fileSize;
                    payloadFound = true;
                } else if (entry.getName().equals(PAYLOAD_PROPERTIES)) {
                    try (BufferedReader buffer = new BufferedReader(
                            new InputStreamReader(zipFile.getInputStream(entry)))) {
                        props = buffer.lines().toArray(String[]::new);
                    }
                }
                if (!payloadFound) {
                    payloadOffset += fileSize;
                }

                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, String.format("Entry %s", entry.getName()));
                }
            }
        }
        return new ParsedUpdate(file, payloadOffset, payloadSize, props);
    }

    /** Information parsed from an update file. */
    static class ParsedUpdate {
        final String mUrl;
        final long mOffset;
        final long mSize;
        final String[] mProps;

        ParsedUpdate(File file, long offset, long size, String[] props) {
            mUrl = FILE_URL_PREFIX + file.getAbsolutePath();
            mOffset = offset;
            mSize = size;
            mProps = props;
        }

        /** Verify the update information is correct. */
        boolean isValid() {
            return mOffset >= 0 && mSize > 0 && mProps != null;
        }

        @Override
        public String toString() {
            return String.format(Locale.getDefault(),
                    "ParsedUpdate: URL=%s, offset=%d, size=%s, props=%s",
                    mUrl, mOffset, mSize, Arrays.toString(mProps));
        }
    }
}

通过在新增的UpdateParser类中,来解析ota升级的zip包 判断是否合法,zip包长度 地址和

大小等等信息,接下来看下具体实现ota升级的核心类SystemUpdatePreferenceController.java

7.2 SystemUpdatePreferenceController.java实现ab分区升级

package com.android.settings.deviceinfo;

import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.UpdateEngine;
import android.os.UpdateEngineCallback;
import android.os.AsyncTask;
import android.os.SystemProperties;
import android.os.RecoverySystem;
import android.os.Handler;
import android.widget.Toast;
import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.app.Activity;
import android.os.PowerManager;
import android.view.WindowManager;
import android.view.Gravity;
import android.content.DialogInterface;
import android.util.Log;
import android.provider.Settings;
import com.android.settings.core.BasePreferenceController;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import java.io.File;

public class SystemUpdatePreferenceController extends BasePreferenceController {
	private String TAG = "SystemUpdatePreferenceController";
    private static final String KEY_PENDING_DEVICE_NAME = "local_system_update";
    private PowerManager.WakeLock mWakeLock;
    private PowerManager mPowerManager;
	private Context mContext;
	private UpdateEngine mUpdateEngine;
	private OtaUpdateEngineCallback mOtaUpdateEngineCallback;
	private Activity mActivity;
    public SystemUpdatePreferenceController(Context context, String key) {
        super(context, key);
		mContext = context;
		mUpdateEngine = new UpdateEngine();
		mOtaUpdateEngineCallback = new OtaUpdateEngineCallback();
        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        // 获取WakeLock对象
        mWakeLock = mPowerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP|PowerManager.SCREEN_BRIGHT_WAKE_LOCK, TAG);
        mWakeLock.setReferenceCounted(false);
        mWakeLock.acquire(600000L);
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        //mPreference = screen.findPreference(KEY_PENDING_DEVICE_NAME);
    }
    @Override
    public int getAvailabilityStatus() {
        return AVAILABLE;
    }
    @Override
    public boolean handlePreferenceTreeClick(Preference preference) {
		checkUpdatePackage();
        return super.handlePreferenceTreeClick(preference);
    }
    public void setActivity(Activity activity){
		this.mActivity = activity;
	}
    public void checkUpdatePackage() {
        Log.d(TAG, "mIsVirtualAbSupported:" + mIsVirtualAbSupported);
        //checkInteralStorage();
         // Add for bug1413281, support Non-A/B feature
        if (mIsVirtualAbSupported) {
            //checkOtaPackage();
			File file = new File("/sdcard/update.zip");
			startVirtualAbUpdateProgress(file);
        }
        /*if (SD_UDPATE_SUPPORTED) {
            checkExternalStorage();
        }*/
    }

    private final boolean mIsVirtualAbSupported = SystemProperties.getBoolean("ro.build.ab_update", false);
    private boolean mInstallationInProgress = false;
//开始A/B升级
    public void startVirtualAbUpdateProgress(File file) {
        if (mInstallationInProgress) {
            showInstallationInProgress();
        } else {
            try {
//执行异步任务解析
                new UpdateVerifier().execute(file);
            } catch (Exception ex) {
                Log.e(TAG, ex.getMessage());
                Toast.makeText(mContext, "update failed", Toast.LENGTH_SHORT).show();
            }
        }
    }
 
 
    //验证ota升级包的有效性
    private class UpdateVerifier extends AsyncTask<File, Void, UpdateParser.ParsedUpdate> {
        @Override
        protected UpdateParser.ParsedUpdate doInBackground(File... files) {
            //Preconditions.checkArgument(files.length > 0, "No file specified");
            File file = files[0];
            try {
                return UpdateParser.parse(file);
            } catch (Exception e) {
                Log.e(TAG, String.format("For file %s", file), e);
                return null;
            }
        }
 
        @Override
        protected void onPostExecute(UpdateParser.ParsedUpdate result) {
            if (result == null) {
                Toast.makeText(mContext, "update failed", Toast.LENGTH_SHORT).show();
                Log.e(TAG, String.format("Failed verification %s", result));
                return;
            }
            if (!result.isValid()) {
                Toast.makeText(mContext, "update failed", Toast.LENGTH_SHORT).show();
                Log.e(TAG, String.format("Failed verification %s", result));
                return;
            }
            Log.d(TAG, "package verifier success");
            /*if (isLowBatteryLevel()) {
//低电量
                Toast.makeText(mContext, R.string.recovery_update_level, Toast.LENGTH_LONG).show();
            } else {*/
//成功,弹出dialog
                showConfirmInstallDialog(result);
            //}
        }
	}	

//弹出确认是否升级的对话框
    public void showConfirmInstallDialog(final UpdateParser.ParsedUpdate parsedUpdate) {
		Log.d(TAG,"mActivity:"+mActivity);
        AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
        builder.setTitle("update package"/*mContext.getResources().getString(R.string.recovery_update_package_install_title)*/);
        builder.setMessage("update_install_ready"/*mContext.getResources().getString(R.string.recovery_update_install_ready)*/);
        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
//show安装进度
                showInstallationInProgress();
                Settings.Global.putInt(mContext.getContentResolver(), "install_progress", 1);
//安装更新
                installUpdate(parsedUpdate);
            }
        });
        builder.setNegativeButton(android.R.string.cancel, null);
        AlertDialog dialog = builder.create();
        dialog.setCancelable(false);
		dialog.getWindow().setGravity(Gravity.CENTER_HORIZONTAL);
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        dialog.show();
        //UNISOC: ADD for bug1648653, change the title and message color.
        //((TextView)dialog.findViewById(android.R.id.message)).setTextColor(Color.BLUE);
        //((TextView)dialog.findViewById(R.id.alertTitle)).setTextColor(Color.BLUE);
    }
 
 
//show安装进度
    private void showInstallationInProgress() {
        mInstallationInProgress = true;
        //showStatus(R.string.recovery_update_package_install_title, R.string.recovery_update_package_download_in_progress);
//bind方法很重要,便于自定义操作及查看进度
        mUpdateEngine.bind(mOtaUpdateEngineCallback, new Handler(mContext.getMainLooper()));
    }
 
 
//将ota包数据发送到updateEngine进行升级
    private void installUpdate(UpdateParser.ParsedUpdate parsedUpdate) {
        Log.d(TAG, "mUrl:" + parsedUpdate.mUrl + ",mOffset:" + parsedUpdate.mOffset
                + ",mSize:" + parsedUpdate.mSize + ",mProps:" + parsedUpdate.mProps);
 
        try {
            if(mContext.getSystemService(KeyguardManager.class).isDeviceSecure()){
                Log.d(TAG, "prepareForUnattendedUpdate");
                RecoverySystem.prepareForUnattendedUpdate(mContext, TAG, null);
            }
            if (mWakeLock != null && !mWakeLock.isHeld()) mWakeLock.acquire();
//主要执行这行命令,applyPayload
            mUpdateEngine.applyPayload(
                    parsedUpdate.mUrl, parsedUpdate.mOffset, parsedUpdate.mSize, parsedUpdate.mProps);
        } catch (Exception ex) {
            mInstallationInProgress = false;
            if (mWakeLock != null && mWakeLock.isHeld()) mWakeLock.release();
            Settings.Global.putInt(mContext.getContentResolver(), "install_progress", 0);
            //Modify for bug1420894, no need to show toast if update succeed
            String message = ex.getMessage();
            Log.e(TAG, message);
            if (!"reboot".equals(message)) {
                Toast.makeText(mContext, "update package"/*R.string.recovery_update_package_install_failed*/, Toast.LENGTH_SHORT).show();
            }
        }
    }
//处理来自UpdateEngine的事件
    public class OtaUpdateEngineCallback extends UpdateEngineCallback {
        @Override
        public void onStatusUpdate(int status, float percent) {
            switch (status) {
                case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT:
				    Log.d(TAG, "install successful");
//升级基本完成,只需重启即可,自定义逻辑
                    mPowerManager.reboot("");
                    break;
                case UpdateEngine.UpdateStatusConstants.DOWNLOADING:
//downloading
                    Log.d(TAG, "downloading progress:" + ((int) (percent * 100) + "%"));
                    break;
                default:
                    // do nothing
            }
        }
 
        @Override
        public void onPayloadApplicationComplete(int errorCode) {
            Log.w(TAG, String.format("onPayloadApplicationComplete %d", errorCode));
            String message = (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS) ? "install_success" : "install_failed";
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
//升级完成(不论成功或是失败)
        }
    }	
}

在点击system update这个ab分区升级的动作后,首选调用checkUpdatePackage()来检测

是否存在sdcard下的zip升级包,然后调用升级的核心功能,接下来确定

升级后调用new UpdateVerifier().execute(file);来解析这个zip升级包,

在protected void onPostExecute(UpdateParser.ParsedUpdate result) 中,对升级包进行校验

后,然后弹窗Dialog让用户确实进行ota升级功能,确定升级后,在调用installUpdate(UpdateParser.ParsedUpdate parsedUpdate)

来安装升级包,最终调用UpdateEngine.applyPayload来执行ab分区升级,在UpdateEngine的回调事件中,

可以通过UpdateEngine.UpdateStatusConstants.DOWNLOADING看到安装进度,而

在 UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT安装完成后,

就需要重启然后完成ab分区升级的功能.

相关推荐
Dnelic-1 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen3 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年11 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿13 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神14 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛14 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法15 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter16 小时前
Android吸顶效果,并有着ViewPager左右切换
android
Stara051116 小时前
Git推送+拉去+uwsgi+Nginx服务器部署项目
git·python·mysql·nginx·gitee·github·uwsgi
坐公交也用券16 小时前
使用Python3实现Gitee码云自动化发布
运维·gitee·自动化