Android11.0系统中添加OTA升级接口供应用层调用
添加OTA升级接口
本文主要描述在RK3568 Android11中通过在自已的系统App中添加OTA升级接口,上层应用开发者在自己的应用中调用此接口进行系统升级。
如何在系统源码中添加开发自己的系统App,可以查看: Android11.0系统中添加开发系统App
参考及说明
OTA系统升级主要分AB系统升级及非AB系统升级,本文实现的是非AB系统的升级,主要参考了services.devicepolicy源码的相关逻辑,路径为:/frameworks/base/services/devicepolicy。
- AB系统:主要是基于UpdateEngine及UpdateEngineCallback,路径:/frameworks/base/core/java/android/os/UpdateEngine.java /frameworks/base/core/java/android/os/UpdateEngineCallback.java
- 非AB系统,主要是调用RecoverySystem.installPackage(mContext, mCopiedUpdateFile)方法,路径为 /frameworks/base/core/java/android/os/RecoverySystem.java;
- RK3568 Android11.0中将OTA升级包adb上传到/storage/emulated/0/update.zip,重启系统,系统中的RKUpdateService服务也会自动检测到升级包,并提示固件升级。
Demo app中添加OTA功能
Demo应用通过其它应用传送的Uri获取OTA升级包文件,并保存为/storage/emulated/0/update.zip,之后调用RecoverySystem.installPackage()方法升级。
- 添加UpdateActivity,路径为 /vendor/yjz/demo/app/src/main/java/com/yjz/demo/update/UpdateActivity.java;
package com.yjz.demo.update;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.yjz.demo.util.SpHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class UpdateActivity extends Activity {
private static final String TAG = "UpdateActivity";
private String mOtaFileSavePath;
private int mOtaVersion;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
super.onCreate(savedInstanceState);
// 保存文件路径:/storage/emulated/0/update.zip
mOtaFileSavePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "update.zip";
Intent intent = getIntent();
mOtaVersion = intent.getIntExtra("ota_version", -1);
StagingAsyncAppTask mStagingTask = new StagingAsyncAppTask();
mStagingTask.execute(getIntent().getData());
}
private void startInstall() {
new Thread() {
@Override
public void run() {
UpdateManager.getInstance(UpdateActivity.this).startOTAUpdate(mOtaVersion, mOtaFileSavePath, new UpdateManager.OTAUpdateReadyListener() {
@Override
public void onReady() {
SpHelper.getInstance(UpdateActivity.this).setOTAVersion(Version.SYSTEM_OTA_VERSION);
}
});
}
}.start();
}
@SuppressLint("NewApi")
private final class StagingAsyncAppTask extends AsyncTask<Uri, Void, File> {
@Override
protected File doInBackground(Uri... params) {
Log.d(TAG, "copy file from user app start");
if (params == null || params.length <= 0) {
return null;
}
Uri packageUri = params[0];
if (null == packageUri) {
return null;
}
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
// Despite the comments in ContentResolver#openInputStream the returned stream can
// be null.
if (in == null) {
return null;
}
File mStagedFile = new File(mOtaFileSavePath);
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
// Be nice and respond to a cancellation
out.write(buffer, 0, bytesRead);
}
}
return mStagedFile;
} catch (IOException | SecurityException | IllegalStateException e) {
Log.w(TAG, "Error staging apk from content URI", e);
}
return null;
}
@Override
protected void onPostExecute(File installFile) {
if (null != installFile) {
// Now start the installation again from a file
Log.d(TAG, "copy file from user app finish");
startInstall();
Log.d(TAG, "send to install");
} else {
Log.d(TAG, "copy file from user app fail");
}
finish();
}
}
}
- UpdateManager、Version,在源码中为了便于引包,一些逻辑类实现在/frameworks/base/core/java/com/yjz/study;
注意:OTA升级包路径/storage/emulated/0/update.zip 在传入RecoverySystem.installPackage(Context context, File packageFile)方法时,需要将路径修改为/data/media/0/update.zip,否则系统重启进入recovery模式后会找不到升级包,recovery模式下路径是/data/media/0/
package com.yjz.study.core.update;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Environment;
import android.os.RecoverySystem;
import android.util.Log;
import com.yjz.study.core.Version;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
public final class UpdateManager {
private static final String TAG = "UpdateManager";
private Context mContext;
private Object mLock = new byte[0];
private boolean mHasOTAUpdate;
private static volatile UpdateManager INSTANCE;
private UpdateManager(Context context) {
mContext = context;
}
public static UpdateManager getInstance(Context context) {
if (null == INSTANCE) {
synchronized (UpdateManager.class) {
if (null == INSTANCE) {
INSTANCE = new UpdateManager(context.getApplicationContext());
}
}
}
return INSTANCE;
}
@SuppressLint("NewApi")
public void startOTAUpdate(int otaVersion, String otaPath, OTAUpdateReadyListener listener) {
Log.d(TAG, "startOTAUpdate--->" + otaPath);
if (otaVersion < 1) {
Log.e(TAG, "ota version not found");
return;
}
if (otaVersion <= Version.SYSTEM_OTA_VERSION) {
Log.e(TAG, "has new version, not need update");
return;
}
File f = new File(otaPath);
if (!f.exists()) {
Log.e(TAG, "ota file not found");
return;
}
if (!"update.zip".equals(f.getName())) {
Log.e(TAG, "ota file name error");
return;
}
if (!otaPath.contains(Environment.getExternalStorageDirectory().getAbsolutePath())) {
Log.e(TAG, "ota file path error");
return;
}
try {
RecoverySystem.verifyPackage(f, new RecoverySystem.ProgressListener() {
@Override
public void onProgress(int progress) {
Log.d(TAG, "verifyPackage progress--->" + progress);
}
}, null);
} catch (IOException e) {
e.printStackTrace();
} catch (GeneralSecurityException e) {
Log.e(TAG, "ota升级包验证失败--->");
e.printStackTrace();
return;
}
String realPath = "/data/media/0/" + f.getName();
synchronized (mLock) {
if (mHasOTAUpdate) {
Log.d(TAG, "OTA升级任务进行中");
return;
}
mHasOTAUpdate = true;
}
if (null != listener) {
listener.onReady();
}
//不是A/B系统,直接使用此方法升级
try {
RecoverySystem.installPackage(mContext, new File(realPath));
} catch (IOException e) {
Log.w(TAG, "IO error while trying to install non AB update.", e);
return;
}
if (null != f && f.exists()) {
f.delete();
}
}
public interface OTAUpdateReadyListener {
void onReady();
}
}
package com.yjz.study.core;
public interface Version {
int SYSTEM_OTA_VERSION = 1;
}
-
在AndroidManifest.xml中注册UpdateActivity,路径为 /vendor/yjz/demo/app/src/main/AndroidManifest.xml;
调用RecoverySystem.installPackage()必须要添加权限:
android.permission.RECOVERY;
android.permission.REBOOT;如果不需要用户看到OTA升级界面,想静默进行,UpdateActivity的主题设置为
android:theme=“@android:style/Theme.Translucent.NoTitleBar”
//********省略代码******
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECOVERY" />
<uses-permission android:name="android.permission.REBOOT" />
<uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<activity
android:name=".update.UpdateActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:exported="true">
<intent-filter android:priority="1">
<action android:name="android.intent.action.OTA_UPDATE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.intent.action.OTA_UPDATE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
</activity>
//********省略代码******
应用请求进行OTA升级
- 开发者在应用可以通过requestOTAUpdate()方法示例进行升级请求。
public int requestOTAUpdate(Context context, Uri uri, int otaVersion, int currentOtaVersion) {
if (otaVersion <= currentOtaVersion) {
return ERROR_CODE_NOT_UPDATE;
}
try {
PackageInfo packinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
String[] list = packinfo.requestedPermissions;
boolean hasPermission = false;
if (null != list) {
for (int i = 0; i < list.length; i++) {
if (Manifest.permission.QUERY_ALL_PACKAGES.equals(list[i])) {
hasPermission = true;
break;
}
}
}
if (!hasPermission) {
throw new RuntimeException("need permission " + Manifest.permission.QUERY_ALL_PACKAGES);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
if (!checkExistForApp(context, "com.yjz.demo")) {
return ERROR_CODE_NOT_SUPPORTED;
}
Intent intent = new Intent("android.intent.action.OTA_UPDATE");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
intent.putExtra("ota_version", otaVersion);
intent.putExtra("allowed_Background", true);
context.startActivity(intent);
return CODE_SUCCESS;
}