一、系统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分区升级的功能.