Copy the updates for uncrypt using a Service

Same as change I488018d01c0baf74660362a384e53bfe5d85de2b
("Use a service to export the updates"). While at it, move the code
out of UpdaterService to better separate the code that deal with AB
updates and regular updates.

Change-Id: I2bc3e78e80f5e63c57303cbbcdc0353dbab0f67f
This commit is contained in:
Gabriele M
2017-11-30 23:41:00 +01:00
parent e66a8fa53a
commit e02b8a0b00
8 changed files with 181 additions and 128 deletions

View File

@@ -7,7 +7,6 @@
<uses-permission android:name="android.permission.REBOOT" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.RECOVERY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

View File

@@ -49,7 +49,6 @@
<string name="finalizing_package">Finalizing package installation</string>
<string name="preparing_ota_first_boot">Preparing for first boot</string>
<string name="dialog_prepare_zip_message">Preliminary update preparation</string>
<string name="notification_prepare_zip_error_title">Could not prepare update</string>
<string name="reboot">Reboot</string>

View File

@@ -160,7 +160,8 @@ public class UpdatesListAdapter extends RecyclerView.Adapter<UpdatesListAdapter.
viewHolder.mProgressPercentage.setText(percentage);
} else if (mUpdaterController.isInstallingUpdate(downloadId)) {
setButtonAction(viewHolder.mAction, Action.CANCEL_INSTALLATION, downloadId, true);
viewHolder.mProgressText.setText(
boolean notAB = !mUpdaterController.isInstallingABUpdate();
viewHolder.mProgressText.setText(notAB ? R.string.dialog_prepare_zip_message :
update.getFinalizing() ?
R.string.finalizing_package :
R.string.preparing_ota_first_boot);

View File

@@ -53,4 +53,6 @@ public interface Controller {
boolean isInstallingUpdate(String downloadId);
boolean isInstallingUpdate();
boolean isInstallingABUpdate();
}

View File

@@ -0,0 +1,157 @@
/*
* 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.content.Context;
import android.os.SystemClock;
import android.support.v7.preference.PreferenceManager;
import android.util.Log;
import org.lineageos.updater.misc.Constants;
import org.lineageos.updater.misc.FileUtils;
import org.lineageos.updater.misc.Utils;
import org.lineageos.updater.model.UpdateInfo;
import org.lineageos.updater.model.UpdateStatus;
import java.io.File;
import java.io.IOException;
class UpdateInstaller {
private static final String TAG = "UpdateInstaller";
private static Thread sPrepareUpdateThread;
private static boolean sCancelled;
private final Context mContext;
private final UpdaterController mUpdaterController;
UpdateInstaller(Context context, UpdaterController controller) {
mContext = context;
mUpdaterController = controller;
}
static boolean isInstalling() {
return !sCancelled && sPrepareUpdateThread != null;
}
static boolean isInstalling(String downloadId) {
return !sCancelled && sPrepareUpdateThread != null &&
downloadId.equals(sPrepareUpdateThread.getName());
}
void install(String downloadId) {
UpdateInfo update = mUpdaterController.getUpdate(downloadId);
boolean deleteUpdate = PreferenceManager.getDefaultSharedPreferences(mContext)
.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, false);
if (deleteUpdate) {
// Renaming the file is enough to have it deleted automatically
File uncrytpFile = new File(
update.getFile().getAbsolutePath() + Constants.UNCRYPT_FILE_EXT);
update.getFile().renameTo(uncrytpFile);
installPackage(uncrytpFile, downloadId);
} else if (Utils.isEncrypted(mContext, update.getFile())) {
// uncrypt rewrites the file so that it can be read without mounting
// the filesystem, so create a copy of it.
prepareForUncryptAndInstall(update);
} else {
installPackage(update.getFile(), downloadId);
}
}
private void installPackage(File update, String downloadId) {
try {
android.os.RecoverySystem.installPackage(mContext, update);
} catch (IOException e) {
// TODO: show error message
Log.e(TAG, "Could not install update", e);
}
}
private void prepareForUncryptAndInstall(UpdateInfo update) {
if (sPrepareUpdateThread != null) {
Log.e(TAG, "Already preparing an update");
return;
}
String uncryptFilePath = update.getFile().getAbsolutePath() + Constants.UNCRYPT_FILE_EXT;
File uncryptFile = new File(uncryptFilePath);
Runnable copyUpdateRunnable = new Runnable() {
private long mLastUpdate = -1;
FileUtils.ProgressCallBack mProgressCallBack = new FileUtils.ProgressCallBack() {
@Override
public void update(int progress) {
long now = SystemClock.elapsedRealtime();
if (mLastUpdate < 0 || now - mLastUpdate > 500) {
mUpdaterController.getActualUpdate(update.getDownloadId())
.setInstallProgress(progress);
mUpdaterController.notifyInstallProgress(update.getDownloadId());
mLastUpdate = now;
}
}
};
@Override
public void run() {
try {
FileUtils.copyFile(update.getFile(), uncryptFile, mProgressCallBack);
// Use INSTALLATION_CANCELLED to clear everything.
// This shouldn't really matter in case of success.
mUpdaterController.getActualUpdate(update.getDownloadId())
.setStatus(UpdateStatus.INSTALLATION_CANCELLED);
mUpdaterController.getActualUpdate(update.getDownloadId())
.setInstallProgress(0);
mUpdaterController.notifyUpdateChange(update.getDownloadId());
if (!sPrepareUpdateThread.isInterrupted()) {
installPackage(uncryptFile, update.getDownloadId());
} else {
uncryptFile.delete();
}
} catch (IOException e) {
Log.e(TAG, "Could not copy update", e);
uncryptFile.delete();
mUpdaterController.getActualUpdate(update.getDownloadId())
.setStatus(UpdateStatus.INSTALLATION_FAILED);
mUpdaterController.notifyUpdateChange(update.getDownloadId());
} finally {
sPrepareUpdateThread = null;
}
}
};
sPrepareUpdateThread = new Thread(copyUpdateRunnable);
sPrepareUpdateThread.setName(update.getDownloadId());
sPrepareUpdateThread.start();
sCancelled = false;
mUpdaterController.getActualUpdate(update.getDownloadId())
.setStatus(UpdateStatus.INSTALLING);
mUpdaterController.notifyUpdateChange(update.getDownloadId());
}
public void cancel() {
if (sCancelled || sPrepareUpdateThread == null) {
Log.d(TAG, "Nothing is being copied");
return;
}
sCancelled = true;
sPrepareUpdateThread.interrupt();
}
}

View File

@@ -534,11 +534,18 @@ public class UpdaterController implements Controller {
@Override
public boolean isInstallingUpdate() {
return ABUpdateInstaller.isInstallingUpdate(mContext);
return UpdateInstaller.isInstalling() ||
ABUpdateInstaller.isInstallingUpdate(mContext);
}
@Override
public boolean isInstallingUpdate(String downloadId) {
return ABUpdateInstaller.isInstallingUpdate(mContext, downloadId);
return UpdateInstaller.isInstalling(downloadId) ||
ABUpdateInstaller.isInstallingUpdate(mContext, downloadId);
}
@Override
public boolean isInstallingABUpdate() {
return ABUpdateInstaller.isInstallingUpdate(mContext);
}
}

View File

@@ -27,7 +27,6 @@ import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.NotificationCompat;
import android.support.v7.preference.PreferenceManager;
import android.text.format.Formatter;
import android.util.Log;
@@ -35,14 +34,11 @@ import org.lineageos.updater.R;
import org.lineageos.updater.UpdaterReceiver;
import org.lineageos.updater.UpdatesActivity;
import org.lineageos.updater.misc.BuildInfoUtils;
import org.lineageos.updater.misc.Constants;
import org.lineageos.updater.misc.FileUtils;
import org.lineageos.updater.misc.StringGenerator;
import org.lineageos.updater.misc.Utils;
import org.lineageos.updater.model.UpdateInfo;
import org.lineageos.updater.model.UpdateStatus;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.NumberFormat;
@@ -186,36 +182,18 @@ public class UpdaterService extends Service {
return START_STICKY;
}
} else {
boolean deleteUpdate = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, false);
if (deleteUpdate) {
// Renaming the file is enough to have it deleted automatically
File uncrytpFile = new File(
update.getFile().getAbsolutePath() + Constants.UNCRYPT_FILE_EXT);
update.getFile().renameTo(uncrytpFile);
installPackage(uncrytpFile);
} else if (Utils.isEncrypted(this, update.getFile())) {
// uncrypt rewrites the file so that it can be read without mounting
// the filesystem, so create a copy of it.
File uncrytpFile = new File(
update.getFile().getAbsolutePath() + Constants.UNCRYPT_FILE_EXT);
FileUtils.prepareForUncrypt(this, update.getFile(), uncrytpFile,
new Runnable() {
@Override
public void run() {
installPackage(uncrytpFile);
}
});
} else {
installPackage(update.getFile());
}
UpdateInstaller installer = new UpdateInstaller(this, mUpdaterController);
installer.install(downloadId);
}
} catch (IOException e) {
Log.e(TAG, "Could not install update", e);
// TODO: user facing message
}
} else if (ACTION_INSTALL_STOP.equals(intent.getAction())) {
if (ABUpdateInstaller.isInstallingUpdate(this)) {
if (UpdateInstaller.isInstalling()) {
UpdateInstaller installer = new UpdateInstaller(this, mUpdaterController);
installer.cancel();
} else if (ABUpdateInstaller.isInstallingUpdate(this)) {
ABUpdateInstaller installer = new ABUpdateInstaller(this, mUpdaterController);
installer.reconnect();
installer.cancel();
@@ -224,15 +202,6 @@ public class UpdaterService extends Service {
return START_NOT_STICKY;
}
private void installPackage(File update) {
try {
android.os.RecoverySystem.installPackage(this, update);
} catch (IOException e) {
// TODO: show error message
Log.e(TAG, "Could not install update", e);
}
}
public Controller getUpdaterController() {
return mUpdaterController;
}
@@ -352,7 +321,9 @@ public class UpdaterService extends Service {
mNotificationBuilder.mActions.clear();
mNotificationBuilder.setProgress(0, 0, false);
mNotificationStyle.setSummaryText(null);
String text = getString(R.string.installing_update);
String text = UpdateInstaller.isInstalling() ?
getString(R.string.dialog_prepare_zip_message) :
getString(R.string.installing_update);
mNotificationStyle.bigText(text);
mNotificationBuilder.setTicker(text);
mNotificationBuilder.setOngoing(true);
@@ -419,7 +390,8 @@ public class UpdaterService extends Service {
mNotificationBuilder.setProgress(100, progress, false);
String percent = NumberFormat.getPercentInstance().format(progress / 100.f);
mNotificationStyle.setSummaryText(percent);
mNotificationStyle.bigText(
boolean notAB = UpdateInstaller.isInstalling();
mNotificationStyle.bigText(notAB ? getString(R.string.dialog_prepare_zip_message) :
update.getFinalizing() ?
getString(R.string.finalizing_package) :
getString(R.string.preparing_ota_first_boot));

View File

@@ -15,16 +15,7 @@
*/
package org.lineageos.updater.misc;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
import android.view.WindowManager;
import org.lineageos.updater.R;
import java.io.File;
import java.io.FileInputStream;
@@ -104,79 +95,4 @@ public class FileUtils {
public static void copyFile(File sourceFile, File destFile) throws IOException {
copyFile(sourceFile, destFile, null);
}
public static void prepareForUncrypt(Context context, File updateFile, File uncryptFile,
Runnable callback) {
final int NOTIFICATION_ID = 12;
new AsyncTask<Void, String, Boolean>() {
private ProgressDialog mProgressDialog;
private boolean mCancelled;
private ProgressCallBack mProgressCallBack;
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.d(TAG, "Preparing update");
mProgressDialog = new ProgressDialog(context);
mProgressDialog.setTitle(R.string.app_name);
mProgressDialog.setMessage(context.getString(R.string.dialog_prepare_zip_message));
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
mProgressDialog.setCancelable(true);
mProgressDialog.setProgressNumberFormat(null);
mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
cancel(true);
}
});
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressCallBack = new ProgressCallBack() {
@Override
public void update(int progress) {
mProgressDialog.setProgress(progress);
}
};
mProgressDialog.show();
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
copyFile(updateFile, uncryptFile, mProgressCallBack);
} catch (IOException e) {
Log.e(TAG, "Error while copying the file", e);
}
return !mCancelled;
}
@Override
protected void onCancelled() {
mCancelled = true;
uncryptFile.delete();
}
@Override
protected void onPostExecute(Boolean success) {
if (!success || mCancelled) {
Log.e(TAG, "Could not prepare the update, cancelled=" + mCancelled);
uncryptFile.delete();
NotificationManager nm = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.ic_system_update);
builder.setContentTitle(
context.getString(R.string.notification_prepare_zip_error_title));
final String notificationTag = updateFile.getAbsolutePath();
nm.notify(notificationTag, NOTIFICATION_ID, builder.build());
} else {
callback.run();
}
mProgressDialog.dismiss();
}
}.execute();
}
}