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" />
|
||||
|
||||
<receiver android:name=".UpdaterReceiver" exported="false" />
|
||||
|
||||
<receiver android:name=".UpdatesCheckReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
|
@@ -40,4 +40,12 @@
|
||||
<string name="resume_button">Resume</string>
|
||||
<string name="cancel_button">Delete</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>
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@@ -99,6 +99,9 @@ public class UpdatesListAdapter extends RecyclerView.Adapter<UpdatesListAdapter.
|
||||
if (mUpdaterController.isDownloading(downloadId)) {
|
||||
setButtonAction(viewHolder.mButton1, Action.PAUSE, downloadId, true);
|
||||
viewHolder.mButton2.setEnabled(false);
|
||||
} else if (mUpdaterController.isInstallingUpdate()) {
|
||||
viewHolder.mButton1.setEnabled(false);
|
||||
viewHolder.mButton2.setEnabled(false);
|
||||
} else {
|
||||
// Allow one active download
|
||||
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 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<String, DownloadEntry> 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();
|
||||
}
|
||||
}
|
||||
|
@@ -43,4 +43,6 @@ public interface UpdaterControllerInt {
|
||||
boolean hasActiveDownloads();
|
||||
|
||||
boolean isVerifyingUpdate();
|
||||
|
||||
boolean isInstallingUpdate();
|
||||
}
|
||||
|
@@ -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 {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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");
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user