Add support for A/B (Seamless) System Updates
Loosely based on:
0465cb691d
This commit is contained in:
@@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
<service android:name=".controller.UpdaterService" />
|
<service android:name=".controller.UpdaterService" />
|
||||||
|
|
||||||
|
<receiver android:name=".UpdaterReceiver" exported="false" />
|
||||||
|
|
||||||
<receiver android:name=".UpdatesCheckReceiver">
|
<receiver android:name=".UpdatesCheckReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
@@ -40,4 +40,12 @@
|
|||||||
<string name="resume_button">Resume</string>
|
<string name="resume_button">Resume</string>
|
||||||
<string name="cancel_button">Delete</string>
|
<string name="cancel_button">Delete</string>
|
||||||
<string name="install_button">Install</string>
|
<string name="install_button">Install</string>
|
||||||
|
|
||||||
|
<string name="installing_update">Installing update package</string>
|
||||||
|
<string name="installing_update_error">Install error</string>
|
||||||
|
<string name="installing_update_finished">Update installed</string>
|
||||||
|
<string name="finalizing_package">Finalizing package installation</string>
|
||||||
|
<string name="preparing_ota_first_boot">Preparing for first boot</string>
|
||||||
|
|
||||||
|
<string name="reboot">Reboot</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -26,6 +26,7 @@ public class UpdateDownload extends Update {
|
|||||||
private int mProgress;
|
private int mProgress;
|
||||||
private long mEta;
|
private long mEta;
|
||||||
private long mSpeed;
|
private long mSpeed;
|
||||||
|
private int mInstallProgress;
|
||||||
|
|
||||||
public UpdateStatus getStatus() {
|
public UpdateStatus getStatus() {
|
||||||
return mStatus;
|
return mStatus;
|
||||||
@@ -82,4 +83,12 @@ public class UpdateDownload extends Update {
|
|||||||
public void setSpeed(long speed) {
|
public void setSpeed(long speed) {
|
||||||
mSpeed = speed;
|
mSpeed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getInstallProgress() {
|
||||||
|
return mInstallProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstallProgress(int progress) {
|
||||||
|
mInstallProgress = progress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,8 @@ public enum UpdateStatus {
|
|||||||
VERIFIED,
|
VERIFIED,
|
||||||
VERIFICATION_FAILED,
|
VERIFICATION_FAILED,
|
||||||
INSTALLING,
|
INSTALLING,
|
||||||
INSTALLED;
|
INSTALLED,
|
||||||
|
INSTALLATION_FAILED;
|
||||||
|
|
||||||
public static final class Persistent {
|
public static final class Persistent {
|
||||||
public static final int UNKNOWN = 0;
|
public static final int UNKNOWN = 0;
|
||||||
|
35
src/org/lineageos/updater/UpdaterReceiver.java
Normal file
35
src/org/lineageos/updater/UpdaterReceiver.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS 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 org.lineageos.updater;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
|
||||||
|
public class UpdaterReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
public static final String ACTION_INSTALL_REBOOT =
|
||||||
|
"org.lineageos.updater.action.INSTALL_REBOOT";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (ACTION_INSTALL_REBOOT.equals(intent.getAction())) {
|
||||||
|
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
pm.reboot(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -70,7 +70,8 @@ public class UpdatesActivity extends AppCompatActivity {
|
|||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (UpdaterController.ACTION_UPDATE_STATUS.equals(intent.getAction())) {
|
if (UpdaterController.ACTION_UPDATE_STATUS.equals(intent.getAction())) {
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
} else if (UpdaterController.ACTION_DOWNLOAD_PROGRESS.equals(intent.getAction())) {
|
} else if (UpdaterController.ACTION_DOWNLOAD_PROGRESS.equals(intent.getAction()) ||
|
||||||
|
UpdaterController.ACTION_INSTALL_PROGRESS.equals(intent.getAction())) {
|
||||||
String downloadId = intent.getStringExtra(UpdaterController.EXTRA_DOWNLOAD_ID);
|
String downloadId = intent.getStringExtra(UpdaterController.EXTRA_DOWNLOAD_ID);
|
||||||
mAdapter.notifyItemChanged(downloadId);
|
mAdapter.notifyItemChanged(downloadId);
|
||||||
}
|
}
|
||||||
@@ -88,6 +89,7 @@ public class UpdatesActivity extends AppCompatActivity {
|
|||||||
IntentFilter intentFilter = new IntentFilter();
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
intentFilter.addAction(UpdaterController.ACTION_UPDATE_STATUS);
|
intentFilter.addAction(UpdaterController.ACTION_UPDATE_STATUS);
|
||||||
intentFilter.addAction(UpdaterController.ACTION_DOWNLOAD_PROGRESS);
|
intentFilter.addAction(UpdaterController.ACTION_DOWNLOAD_PROGRESS);
|
||||||
|
intentFilter.addAction(UpdaterController.ACTION_INSTALL_PROGRESS);
|
||||||
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
|
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -99,6 +99,9 @@ public class UpdatesListAdapter extends RecyclerView.Adapter<UpdatesListAdapter.
|
|||||||
if (mUpdaterController.isDownloading(downloadId)) {
|
if (mUpdaterController.isDownloading(downloadId)) {
|
||||||
setButtonAction(viewHolder.mButton1, Action.PAUSE, downloadId, true);
|
setButtonAction(viewHolder.mButton1, Action.PAUSE, downloadId, true);
|
||||||
viewHolder.mButton2.setEnabled(false);
|
viewHolder.mButton2.setEnabled(false);
|
||||||
|
} else if (mUpdaterController.isInstallingUpdate()) {
|
||||||
|
viewHolder.mButton1.setEnabled(false);
|
||||||
|
viewHolder.mButton2.setEnabled(false);
|
||||||
} else {
|
} else {
|
||||||
// Allow one active download
|
// Allow one active download
|
||||||
boolean enabled = !mUpdaterController.hasActiveDownloads() &&
|
boolean enabled = !mUpdaterController.hasActiveDownloads() &&
|
||||||
|
159
src/org/lineageos/updater/controller/ABUpdateInstaller.java
Normal file
159
src/org/lineageos/updater/controller/ABUpdateInstaller.java
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS 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 org.lineageos.updater.controller;
|
||||||
|
|
||||||
|
import android.os.UpdateEngine;
|
||||||
|
import android.os.UpdateEngineCallback;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.lineageos.updater.UpdateDownload;
|
||||||
|
import org.lineageos.updater.UpdateStatus;
|
||||||
|
import org.lineageos.updater.misc.Utils;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
class ABUpdateInstaller {
|
||||||
|
|
||||||
|
private static final String TAG = "ABUpdateInstaller";
|
||||||
|
|
||||||
|
private static boolean sIsInstallingUpdate;
|
||||||
|
|
||||||
|
private static final String PAYLOAD_BIN_PATH = "payload.bin";
|
||||||
|
private static final String PAYLOAD_PROPERTIES_PATH = "payload_properties.txt";
|
||||||
|
|
||||||
|
private final UpdaterController mUpdaterController;
|
||||||
|
private final String mDownloadId;
|
||||||
|
|
||||||
|
private final UpdateEngineCallback mUpdateEngineCallback = new UpdateEngineCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusUpdate(int status, float percent) {
|
||||||
|
switch (status) {
|
||||||
|
case UpdateEngine.UpdateStatusConstants.DOWNLOADING:
|
||||||
|
case UpdateEngine.UpdateStatusConstants.FINALIZING: {
|
||||||
|
int progress = Math.round(percent * 100);
|
||||||
|
mUpdaterController.getUpdate(mDownloadId).setInstallProgress(progress);
|
||||||
|
mUpdaterController.notifyInstallProgress(mDownloadId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UpdateEngine.UpdateStatusConstants.REPORTING_ERROR_EVENT: {
|
||||||
|
UpdateDownload update = mUpdaterController.getUpdate(mDownloadId);
|
||||||
|
update.setInstallProgress(0);
|
||||||
|
update.setStatus(UpdateStatus.INSTALLATION_FAILED);
|
||||||
|
mUpdaterController.notifyUpdateChange(mDownloadId);;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPayloadApplicationComplete(int errorCode) {
|
||||||
|
sIsInstallingUpdate = false;
|
||||||
|
switch (errorCode) {
|
||||||
|
case UpdateEngine.ErrorCodeConstants.SUCCESS: {
|
||||||
|
UpdateDownload update = mUpdaterController.getUpdate(mDownloadId);
|
||||||
|
update.setInstallProgress(0);
|
||||||
|
update.setStatus(UpdateStatus.INSTALLED);
|
||||||
|
mUpdaterController.notifyUpdateChange(mDownloadId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
UpdateDownload update = mUpdaterController.getUpdate(mDownloadId);
|
||||||
|
update.setInstallProgress(0);
|
||||||
|
update.setStatus(UpdateStatus.INSTALLATION_FAILED);
|
||||||
|
mUpdaterController.notifyUpdateChange(mDownloadId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static synchronized boolean start(UpdaterController updaterController,
|
||||||
|
String downloadId) {
|
||||||
|
if (sIsInstallingUpdate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ABUpdateInstaller installer = new ABUpdateInstaller(updaterController, downloadId);
|
||||||
|
sIsInstallingUpdate = installer.startUpdate();
|
||||||
|
return sIsInstallingUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static synchronized boolean isInstallingUpdate() {
|
||||||
|
return sIsInstallingUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ABUpdateInstaller(UpdaterController updaterController, String downloadId) {
|
||||||
|
mUpdaterController = updaterController;
|
||||||
|
mDownloadId = downloadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean startUpdate() {
|
||||||
|
File file = mUpdaterController.getUpdate(mDownloadId).getFile();
|
||||||
|
if (!file.exists()) {
|
||||||
|
Log.e(TAG, "The given update doesn't exist");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mUpdaterController.getUpdate(mDownloadId).setStatus(UpdateStatus.INSTALLING);
|
||||||
|
mUpdaterController.notifyUpdateChange(mDownloadId);
|
||||||
|
|
||||||
|
long offset;
|
||||||
|
String[] headerKeyValuePairs;
|
||||||
|
try {
|
||||||
|
ZipFile zipFile = new ZipFile(file);
|
||||||
|
offset = Utils.getZipEntryOffset(zipFile, PAYLOAD_BIN_PATH);
|
||||||
|
ZipEntry payloadPropEntry = zipFile.getEntry(PAYLOAD_PROPERTIES_PATH);
|
||||||
|
try (InputStream is = zipFile.getInputStream(payloadPropEntry);
|
||||||
|
InputStreamReader isr = new InputStreamReader(is);
|
||||||
|
BufferedReader br = new BufferedReader(isr)) {
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
for (String line; (line = br.readLine()) != null;) {
|
||||||
|
lines.add(line);
|
||||||
|
}
|
||||||
|
headerKeyValuePairs = new String[lines.size()];
|
||||||
|
headerKeyValuePairs = lines.toArray(headerKeyValuePairs);
|
||||||
|
}
|
||||||
|
zipFile.close();
|
||||||
|
} catch (IOException | IllegalArgumentException e) {
|
||||||
|
Log.e(TAG, "Could not prepare " + file, e);
|
||||||
|
mUpdaterController.getUpdate(mDownloadId).setStatus(UpdateStatus.INSTALLATION_FAILED);
|
||||||
|
mUpdaterController.notifyUpdateChange(mDownloadId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateEngine updateEngine = new UpdateEngine();
|
||||||
|
updateEngine.bind(mUpdateEngineCallback);
|
||||||
|
String zipFileUri = "file://" + file.getAbsolutePath();
|
||||||
|
updateEngine.applyPayload(zipFileUri, offset, 0, headerKeyValuePairs);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isABUpdate(ZipFile zipFile) {
|
||||||
|
return zipFile.getEntry(PAYLOAD_BIN_PATH) != null &&
|
||||||
|
zipFile.getEntry(PAYLOAD_PROPERTIES_PATH) != null;
|
||||||
|
}
|
||||||
|
}
|
@@ -39,6 +39,7 @@ import java.util.Set;
|
|||||||
public class UpdaterController implements UpdaterControllerInt {
|
public class UpdaterController implements UpdaterControllerInt {
|
||||||
|
|
||||||
public static final String ACTION_DOWNLOAD_PROGRESS = "action_download_progress";
|
public static final String ACTION_DOWNLOAD_PROGRESS = "action_download_progress";
|
||||||
|
public static final String ACTION_INSTALL_PROGRESS = "action_install_progress";
|
||||||
public static final String ACTION_UPDATE_STATUS = "action_update_status_change";
|
public static final String ACTION_UPDATE_STATUS = "action_update_status_change";
|
||||||
public static final String EXTRA_DOWNLOAD_ID = "extra_download_id";
|
public static final String EXTRA_DOWNLOAD_ID = "extra_download_id";
|
||||||
|
|
||||||
@@ -92,20 +93,27 @@ public class UpdaterController implements UpdaterControllerInt {
|
|||||||
|
|
||||||
private Map<String, DownloadEntry> mDownloads = new HashMap<>();
|
private Map<String, DownloadEntry> mDownloads = new HashMap<>();
|
||||||
|
|
||||||
private void notifyUpdateChange(String downloadId) {
|
void notifyUpdateChange(String downloadId) {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.setAction(ACTION_UPDATE_STATUS);
|
intent.setAction(ACTION_UPDATE_STATUS);
|
||||||
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
|
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
|
||||||
mBroadcastManager.sendBroadcast(intent);
|
mBroadcastManager.sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyDownloadProgress(String downloadId) {
|
void notifyDownloadProgress(String downloadId) {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.setAction(ACTION_DOWNLOAD_PROGRESS);
|
intent.setAction(ACTION_DOWNLOAD_PROGRESS);
|
||||||
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
|
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
|
||||||
mBroadcastManager.sendBroadcast(intent);
|
mBroadcastManager.sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void notifyInstallProgress(String downloadId) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(ACTION_INSTALL_PROGRESS);
|
||||||
|
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
|
||||||
|
mBroadcastManager.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
private void tryReleaseWakelock() {
|
private void tryReleaseWakelock() {
|
||||||
if (!hasActiveDownloads()) {
|
if (!hasActiveDownloads()) {
|
||||||
mWakeLock.release();
|
mWakeLock.release();
|
||||||
@@ -433,4 +441,9 @@ public class UpdaterController implements UpdaterControllerInt {
|
|||||||
public boolean isVerifyingUpdate() {
|
public boolean isVerifyingUpdate() {
|
||||||
return mVerifyingUpdates > 0;
|
return mVerifyingUpdates > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInstallingUpdate() {
|
||||||
|
return ABUpdateInstaller.isInstallingUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -43,4 +43,6 @@ public interface UpdaterControllerInt {
|
|||||||
boolean hasActiveDownloads();
|
boolean hasActiveDownloads();
|
||||||
|
|
||||||
boolean isVerifyingUpdate();
|
boolean isVerifyingUpdate();
|
||||||
|
|
||||||
|
boolean isInstallingUpdate();
|
||||||
}
|
}
|
||||||
|
@@ -32,11 +32,13 @@ import android.util.Log;
|
|||||||
|
|
||||||
import org.lineageos.updater.R;
|
import org.lineageos.updater.R;
|
||||||
import org.lineageos.updater.UpdateDownload;
|
import org.lineageos.updater.UpdateDownload;
|
||||||
|
import org.lineageos.updater.UpdaterReceiver;
|
||||||
import org.lineageos.updater.UpdatesActivity;
|
import org.lineageos.updater.UpdatesActivity;
|
||||||
import org.lineageos.updater.misc.Utils;
|
import org.lineageos.updater.misc.Utils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
public class UpdaterService extends Service {
|
public class UpdaterService extends Service {
|
||||||
|
|
||||||
@@ -60,7 +62,7 @@ public class UpdaterService extends Service {
|
|||||||
private NotificationManager mNotificationManager;
|
private NotificationManager mNotificationManager;
|
||||||
private NotificationCompat.BigTextStyle mNotificationStyle;;
|
private NotificationCompat.BigTextStyle mNotificationStyle;;
|
||||||
|
|
||||||
private UpdaterControllerInt mUpdaterController;
|
private UpdaterController mUpdaterController;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
@@ -91,11 +93,16 @@ public class UpdaterService extends Service {
|
|||||||
} else if (UpdaterController.ACTION_DOWNLOAD_PROGRESS.equals(intent.getAction())) {
|
} else if (UpdaterController.ACTION_DOWNLOAD_PROGRESS.equals(intent.getAction())) {
|
||||||
UpdateDownload update = mUpdaterController.getUpdate(downloadId);
|
UpdateDownload update = mUpdaterController.getUpdate(downloadId);
|
||||||
handleDownloadProgressChange(update);
|
handleDownloadProgressChange(update);
|
||||||
|
} else if (UpdaterController.ACTION_INSTALL_PROGRESS.equals(intent.getAction())) {
|
||||||
|
UpdateDownload update = mUpdaterController.getUpdate(downloadId);
|
||||||
|
mNotificationBuilder.setContentTitle(update.getName());
|
||||||
|
handleInstallProgress(update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
IntentFilter intentFilter = new IntentFilter();
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
intentFilter.addAction(UpdaterController.ACTION_DOWNLOAD_PROGRESS);
|
intentFilter.addAction(UpdaterController.ACTION_DOWNLOAD_PROGRESS);
|
||||||
|
intentFilter.addAction(UpdaterController.ACTION_INSTALL_PROGRESS);
|
||||||
intentFilter.addAction(UpdaterController.ACTION_UPDATE_STATUS);
|
intentFilter.addAction(UpdaterController.ACTION_UPDATE_STATUS);
|
||||||
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
|
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
|
||||||
|
|
||||||
@@ -141,9 +148,16 @@ public class UpdaterService extends Service {
|
|||||||
} else if (ACTION_INSTALL_UPDATE.equals(intent.getAction())) {
|
} else if (ACTION_INSTALL_UPDATE.equals(intent.getAction())) {
|
||||||
String downloadId = intent.getStringExtra(EXTRA_DOWNLOAD_ID);
|
String downloadId = intent.getStringExtra(EXTRA_DOWNLOAD_ID);
|
||||||
try {
|
try {
|
||||||
Utils.triggerUpdate(this, mUpdaterController.getUpdate(downloadId));
|
ZipFile zipFile = new ZipFile(mUpdaterController.getUpdate(downloadId).getFile());
|
||||||
|
boolean isABUpdate = ABUpdateInstaller.isABUpdate(zipFile);
|
||||||
|
zipFile.close();
|
||||||
|
if (isABUpdate) {
|
||||||
|
ABUpdateInstaller.start(mUpdaterController, downloadId);
|
||||||
|
} else {
|
||||||
|
Utils.triggerUpdate(this, mUpdaterController.getUpdate(downloadId));
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Could not install update");
|
Log.e(TAG, "Could not install update", e);
|
||||||
// TODO: user facing message
|
// TODO: user facing message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,6 +274,43 @@ public class UpdaterService extends Service {
|
|||||||
tryStopSelf();
|
tryStopSelf();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case INSTALLING: {
|
||||||
|
mNotificationBuilder.mActions.clear();
|
||||||
|
mNotificationBuilder.setProgress(0, 0, true);
|
||||||
|
mNotificationStyle.setSummaryText(null);
|
||||||
|
String text = getString(R.string.installing_update);
|
||||||
|
mNotificationStyle.bigText(text);
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationBuilder.setOngoing(true);
|
||||||
|
startForeground(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INSTALLED: {
|
||||||
|
stopForeground(STOP_FOREGROUND_DETACH);
|
||||||
|
mNotificationBuilder.setProgress(100, 100, false);
|
||||||
|
String text = getString(R.string.installing_update_finished);
|
||||||
|
mNotificationStyle.bigText(text);
|
||||||
|
mNotificationBuilder.addAction(R.drawable.ic_tab_install,
|
||||||
|
getString(R.string.reboot),
|
||||||
|
getRebootPendingIntent());
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationBuilder.setOngoing(false);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
tryStopSelf();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INSTALLATION_FAILED: {
|
||||||
|
stopForeground(STOP_FOREGROUND_DETACH);
|
||||||
|
mNotificationBuilder.setProgress(0, 0, false);
|
||||||
|
String text = getString(R.string.installing_update_error);
|
||||||
|
mNotificationStyle.bigText(text);
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationBuilder.setOngoing(false);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
tryStopSelf();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,6 +332,25 @@ public class UpdaterService extends Service {
|
|||||||
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleInstallProgress(UpdateDownload update) {
|
||||||
|
int progress = update.getInstallProgress();
|
||||||
|
mNotificationBuilder.setProgress(100, progress, false);
|
||||||
|
|
||||||
|
mNotificationStyle.setBigContentTitle(update.getName());
|
||||||
|
mNotificationBuilder.setContentTitle(update.getName());
|
||||||
|
|
||||||
|
if (progress == 0) {
|
||||||
|
mNotificationStyle.bigText(getString(R.string.finalizing_package));
|
||||||
|
mNotificationBuilder.setProgress(0, 0, true);
|
||||||
|
} else {
|
||||||
|
String percent = NumberFormat.getPercentInstance().format(progress / 100.f);
|
||||||
|
mNotificationStyle.setSummaryText(percent);
|
||||||
|
mNotificationStyle.bigText(getString(R.string.preparing_ota_first_boot));
|
||||||
|
}
|
||||||
|
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
private PendingIntent getResumePendingIntent(String downloadId) {
|
private PendingIntent getResumePendingIntent(String downloadId) {
|
||||||
final Intent intent = new Intent(this, UpdaterService.class);
|
final Intent intent = new Intent(this, UpdaterService.class);
|
||||||
intent.setAction(ACTION_DOWNLOAD_CONTROL);
|
intent.setAction(ACTION_DOWNLOAD_CONTROL);
|
||||||
@@ -306,4 +376,12 @@ public class UpdaterService extends Service {
|
|||||||
return PendingIntent.getService(this, 0, intent,
|
return PendingIntent.getService(this, 0, intent,
|
||||||
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PendingIntent getRebootPendingIntent() {
|
||||||
|
final Intent intent = new Intent(this, UpdaterReceiver.class);
|
||||||
|
intent.setAction(UpdaterReceiver.ACTION_INSTALL_REBOOT);
|
||||||
|
return PendingIntent.getBroadcast(this, 0, intent,
|
||||||
|
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -37,9 +37,12 @@ import java.io.FileReader;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
@@ -168,4 +171,36 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the offset to the compressed data of a file inside the given zip
|
||||||
|
*
|
||||||
|
* @param zipFile input zip file
|
||||||
|
* @param entryPath full path of the entry
|
||||||
|
* @return the offset of the compressed, or -1 if not found
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalArgumentException if the given entry is not found
|
||||||
|
*/
|
||||||
|
public static long getZipEntryOffset(ZipFile zipFile, String entryPath)
|
||||||
|
throws IOException {
|
||||||
|
// Each entry has an header of (30 + n + m) bytes
|
||||||
|
// 'n' is the length of the file name
|
||||||
|
// 'm' is the length of the extra field
|
||||||
|
final int FIXED_HEADER_SIZE = 30;
|
||||||
|
Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
|
||||||
|
long offset = 0;
|
||||||
|
while (zipEntries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = zipEntries.nextElement();
|
||||||
|
int n = entry.getName().length();
|
||||||
|
int m = entry.getExtra() == null ? 0 : entry.getExtra().length;
|
||||||
|
int headerSize = FIXED_HEADER_SIZE + n + m;
|
||||||
|
offset += headerSize;
|
||||||
|
if (entry.getName().equals(entryPath)) {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
offset += entry.getCompressedSize();
|
||||||
|
}
|
||||||
|
Log.e(TAG, "Entry " + entryPath + " not found");
|
||||||
|
throw new IllegalArgumentException("The given entry was not found");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user