diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 46daa57..9b47ac9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -30,6 +30,8 @@
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dd5ad34..aa9155d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -40,4 +40,12 @@
Resume
Delete
Install
+
+ Installing update package
+ Install error
+ Update installed
+ Finalizing package installation
+ Preparing for first boot
+
+ Reboot
diff --git a/src/org/lineageos/updater/UpdateDownload.java b/src/org/lineageos/updater/UpdateDownload.java
index 447f016..989ad8d 100644
--- a/src/org/lineageos/updater/UpdateDownload.java
+++ b/src/org/lineageos/updater/UpdateDownload.java
@@ -26,6 +26,7 @@ public class UpdateDownload extends Update {
private int mProgress;
private long mEta;
private long mSpeed;
+ private int mInstallProgress;
public UpdateStatus getStatus() {
return mStatus;
@@ -82,4 +83,12 @@ public class UpdateDownload extends Update {
public void setSpeed(long speed) {
mSpeed = speed;
}
+
+ public int getInstallProgress() {
+ return mInstallProgress;
+ }
+
+ public void setInstallProgress(int progress) {
+ mInstallProgress = progress;
+ }
}
diff --git a/src/org/lineageos/updater/UpdateStatus.java b/src/org/lineageos/updater/UpdateStatus.java
index 6ec426f..5cd1db7 100644
--- a/src/org/lineageos/updater/UpdateStatus.java
+++ b/src/org/lineageos/updater/UpdateStatus.java
@@ -27,7 +27,8 @@ public enum UpdateStatus {
VERIFIED,
VERIFICATION_FAILED,
INSTALLING,
- INSTALLED;
+ INSTALLED,
+ INSTALLATION_FAILED;
public static final class Persistent {
public static final int UNKNOWN = 0;
diff --git a/src/org/lineageos/updater/UpdaterReceiver.java b/src/org/lineageos/updater/UpdaterReceiver.java
new file mode 100644
index 0000000..7bdaa4c
--- /dev/null
+++ b/src/org/lineageos/updater/UpdaterReceiver.java
@@ -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);
+ }
+ }
+}
diff --git a/src/org/lineageos/updater/UpdatesActivity.java b/src/org/lineageos/updater/UpdatesActivity.java
index ef20b5b..55739ef 100644
--- a/src/org/lineageos/updater/UpdatesActivity.java
+++ b/src/org/lineageos/updater/UpdatesActivity.java
@@ -70,7 +70,8 @@ public class UpdatesActivity extends AppCompatActivity {
public void onReceive(Context context, Intent intent) {
if (UpdaterController.ACTION_UPDATE_STATUS.equals(intent.getAction())) {
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);
mAdapter.notifyItemChanged(downloadId);
}
@@ -88,6 +89,7 @@ public class UpdatesActivity extends AppCompatActivity {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(UpdaterController.ACTION_UPDATE_STATUS);
intentFilter.addAction(UpdaterController.ACTION_DOWNLOAD_PROGRESS);
+ intentFilter.addAction(UpdaterController.ACTION_INSTALL_PROGRESS);
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
}
diff --git a/src/org/lineageos/updater/UpdatesListAdapter.java b/src/org/lineageos/updater/UpdatesListAdapter.java
index 98d472d..8552fa1 100644
--- a/src/org/lineageos/updater/UpdatesListAdapter.java
+++ b/src/org/lineageos/updater/UpdatesListAdapter.java
@@ -99,6 +99,9 @@ public class UpdatesListAdapter extends RecyclerView.Adapter 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;
+ }
+}
diff --git a/src/org/lineageos/updater/controller/UpdaterController.java b/src/org/lineageos/updater/controller/UpdaterController.java
index 8b3ec0f..66e5990 100644
--- a/src/org/lineageos/updater/controller/UpdaterController.java
+++ b/src/org/lineageos/updater/controller/UpdaterController.java
@@ -39,6 +39,7 @@ import java.util.Set;
public class UpdaterController implements UpdaterControllerInt {
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 EXTRA_DOWNLOAD_ID = "extra_download_id";
@@ -92,20 +93,27 @@ public class UpdaterController implements UpdaterControllerInt {
private Map mDownloads = new HashMap<>();
- private void notifyUpdateChange(String downloadId) {
+ void notifyUpdateChange(String downloadId) {
Intent intent = new Intent();
intent.setAction(ACTION_UPDATE_STATUS);
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
mBroadcastManager.sendBroadcast(intent);
}
- private void notifyDownloadProgress(String downloadId) {
+ void notifyDownloadProgress(String downloadId) {
Intent intent = new Intent();
intent.setAction(ACTION_DOWNLOAD_PROGRESS);
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
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() {
if (!hasActiveDownloads()) {
mWakeLock.release();
@@ -433,4 +441,9 @@ public class UpdaterController implements UpdaterControllerInt {
public boolean isVerifyingUpdate() {
return mVerifyingUpdates > 0;
}
+
+ @Override
+ public boolean isInstallingUpdate() {
+ return ABUpdateInstaller.isInstallingUpdate();
+ }
}
diff --git a/src/org/lineageos/updater/controller/UpdaterControllerInt.java b/src/org/lineageos/updater/controller/UpdaterControllerInt.java
index 734e798..014d8af 100644
--- a/src/org/lineageos/updater/controller/UpdaterControllerInt.java
+++ b/src/org/lineageos/updater/controller/UpdaterControllerInt.java
@@ -43,4 +43,6 @@ public interface UpdaterControllerInt {
boolean hasActiveDownloads();
boolean isVerifyingUpdate();
+
+ boolean isInstallingUpdate();
}
diff --git a/src/org/lineageos/updater/controller/UpdaterService.java b/src/org/lineageos/updater/controller/UpdaterService.java
index a0444b7..eed5769 100644
--- a/src/org/lineageos/updater/controller/UpdaterService.java
+++ b/src/org/lineageos/updater/controller/UpdaterService.java
@@ -32,11 +32,13 @@ import android.util.Log;
import org.lineageos.updater.R;
import org.lineageos.updater.UpdateDownload;
+import org.lineageos.updater.UpdaterReceiver;
import org.lineageos.updater.UpdatesActivity;
import org.lineageos.updater.misc.Utils;
import java.io.IOException;
import java.text.NumberFormat;
+import java.util.zip.ZipFile;
public class UpdaterService extends Service {
@@ -60,7 +62,7 @@ public class UpdaterService extends Service {
private NotificationManager mNotificationManager;
private NotificationCompat.BigTextStyle mNotificationStyle;;
- private UpdaterControllerInt mUpdaterController;
+ private UpdaterController mUpdaterController;
@Override
public void onCreate() {
@@ -91,11 +93,16 @@ public class UpdaterService extends Service {
} else if (UpdaterController.ACTION_DOWNLOAD_PROGRESS.equals(intent.getAction())) {
UpdateDownload update = mUpdaterController.getUpdate(downloadId);
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.addAction(UpdaterController.ACTION_DOWNLOAD_PROGRESS);
+ intentFilter.addAction(UpdaterController.ACTION_INSTALL_PROGRESS);
intentFilter.addAction(UpdaterController.ACTION_UPDATE_STATUS);
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
@@ -141,9 +148,16 @@ public class UpdaterService extends Service {
} else if (ACTION_INSTALL_UPDATE.equals(intent.getAction())) {
String downloadId = intent.getStringExtra(EXTRA_DOWNLOAD_ID);
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) {
- Log.e(TAG, "Could not install update");
+ Log.e(TAG, "Could not install update", e);
// TODO: user facing message
}
}
@@ -260,6 +274,43 @@ public class UpdaterService extends Service {
tryStopSelf();
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());
}
+ 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) {
final Intent intent = new Intent(this, UpdaterService.class);
intent.setAction(ACTION_DOWNLOAD_CONTROL);
@@ -306,4 +376,12 @@ public class UpdaterService extends Service {
return PendingIntent.getService(this, 0, intent,
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);
+ }
+
}
diff --git a/src/org/lineageos/updater/misc/Utils.java b/src/org/lineageos/updater/misc/Utils.java
index 5ccb303..7970702 100644
--- a/src/org/lineageos/updater/misc/Utils.java
+++ b/src/org/lineageos/updater/misc/Utils.java
@@ -37,9 +37,12 @@ import java.io.FileReader;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
public class Utils {
@@ -168,4 +171,36 @@ public class Utils {
}
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");
+ }
}