diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f76ab96a..cea3a868 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -33,6 +33,7 @@
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2d33ebef..ac154651 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -100,11 +100,10 @@
Download URL
URL Copied
- Could not export download
Exporting update
- Exporting update as %1$s into the external storage.
Update exported
Export error
+ Already exporting an update
- 1 second
diff --git a/src/org/lineageos/updater/ExportUpdateService.java b/src/org/lineageos/updater/ExportUpdateService.java
new file mode 100644
index 00000000..6705de68
--- /dev/null
+++ b/src/org/lineageos/updater/ExportUpdateService.java
@@ -0,0 +1,212 @@
+/*
+ * 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.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+import android.widget.Toast;
+
+import org.lineageos.updater.misc.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.NumberFormat;
+
+public class ExportUpdateService extends Service {
+
+ private static final String TAG = "ExportUpdateService";
+
+ private static final int NOTIFICATION_ID = 16;
+
+ public static final String ACTION_START_EXPORTING = "start_exporting";
+ public static final String ACTION_STOP_EXPORTING = "stop_exporting";
+
+ public static final String EXTRA_SOURCE_FILE = "source_file";
+ public static final String EXTRA_DEST_FILE = "dest_file";
+
+ private volatile boolean mIsExporting = false;
+
+ private Thread mExportThread;
+ private ExportRunnable mExportRunnable;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (ACTION_START_EXPORTING.equals(intent.getAction())) {
+ if (mIsExporting) {
+ Log.e(TAG, "Already exporting an update");
+ Toast.makeText(this, R.string.toast_already_exporting, Toast.LENGTH_SHORT).show();
+ return START_NOT_STICKY;
+ }
+ mIsExporting = true;
+ File source = (File) intent.getSerializableExtra(EXTRA_SOURCE_FILE);
+ File destination = (File) intent.getSerializableExtra(EXTRA_DEST_FILE);
+ startExporting(source, destination);
+ } else if (ACTION_STOP_EXPORTING.equals(intent.getAction())) {
+ if (mIsExporting) {
+ mExportThread.interrupt();
+ stopForeground(true);
+ try {
+ mExportThread.join();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Error while waiting for thread");
+ }
+ mExportRunnable.cleanUp();
+ mIsExporting = false;
+ }
+ } else {
+ Log.e(TAG, "No action specified");
+ }
+
+ if (!mIsExporting) {
+ stopSelf();
+ }
+
+ return START_NOT_STICKY;
+ }
+
+ private class ExportRunnable implements Runnable {
+ private File mSource;
+ private File mDestination;
+ private FileUtils.ProgressCallBack mProgressCallBack;
+ private Runnable mRunnableComplete;
+ private Runnable mRunnableFailed;
+
+ private ExportRunnable(File source, File destination,
+ FileUtils.ProgressCallBack progressCallBack,
+ Runnable runnableComplete, Runnable runnableFailed) {
+ mSource = source;
+ mDestination = destination;
+ mProgressCallBack = progressCallBack;
+ mRunnableComplete = runnableComplete;
+ mRunnableFailed = runnableFailed;
+ }
+
+ @Override
+ public void run() {
+ try {
+ FileUtils.copyFile(mSource, mDestination, mProgressCallBack);
+ mIsExporting = false;
+ if (!mExportThread.isInterrupted()) {
+ Log.d(TAG, "Completed");
+ mRunnableComplete.run();
+ } else {
+ Log.d(TAG, "Aborted");
+ }
+ } catch (IOException e) {
+ mIsExporting = false;
+ Log.e(TAG, "Could not copy file", e);
+ mRunnableFailed.run();
+ } finally {
+ stopSelf();
+ }
+ }
+
+ private void cleanUp() {
+ mDestination.delete();
+ }
+ }
+
+ private void startExporting(File source, File destination) {
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this);
+ NotificationCompat.BigTextStyle notificationStyle = new NotificationCompat.BigTextStyle();
+ notificationBuilder.setContentTitle(getString(R.string.dialog_export_title));
+ notificationStyle.setBigContentTitle(getString(R.string.dialog_export_title));
+ notificationStyle.bigText(destination.getName());
+ notificationBuilder.setStyle(notificationStyle);
+ notificationBuilder.setSmallIcon(R.drawable.ic_system_update);
+ notificationBuilder.addAction(R.drawable.ic_pause,
+ getString(android.R.string.cancel),
+ getStopPendingIntent());
+
+ FileUtils.ProgressCallBack progressCallBack = new FileUtils.ProgressCallBack() {
+ private long mLastUpdate = -1;
+
+ @Override
+ public void update(int progress) {
+ long now = SystemClock.elapsedRealtime();
+ if (mLastUpdate < 0 || now - mLastUpdate > 500) {
+ String percent = NumberFormat.getPercentInstance().format(progress / 100.f);
+ notificationStyle.setSummaryText(percent);
+ notificationBuilder.setProgress(100, progress, false);
+ notificationManager.notify(NOTIFICATION_ID,
+ notificationBuilder.build());
+ mLastUpdate = now;
+ }
+ }
+ };
+
+ startForeground(NOTIFICATION_ID, notificationBuilder.build());
+ notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
+
+ Runnable runnableComplete = new Runnable() {
+ @Override
+ public void run() {
+ notificationStyle.setSummaryText(null);
+ notificationStyle.setBigContentTitle(
+ getString(R.string.notification_export_success));
+ notificationBuilder.setContentTitle(
+ getString(R.string.notification_export_success));
+ notificationBuilder.setProgress(0, 0, false);
+ notificationBuilder.setContentText(destination.getName());
+ notificationBuilder.mActions.clear();
+ notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
+ stopForeground(STOP_FOREGROUND_DETACH);
+ }
+ };
+
+ Runnable runnableFailed = new Runnable() {
+ @Override
+ public void run() {
+ notificationStyle.setSummaryText(null);
+ notificationStyle.setBigContentTitle(
+ getString(R.string.notification_export_fail));
+ notificationBuilder.setContentTitle(
+ getString(R.string.notification_export_fail));
+ notificationBuilder.setProgress(0, 0, false);
+ notificationBuilder.setContentText(null);
+ notificationBuilder.mActions.clear();
+ notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
+ stopForeground(STOP_FOREGROUND_DETACH);
+ }
+ };
+
+ mExportRunnable = new ExportRunnable(source, destination, progressCallBack,
+ runnableComplete, runnableFailed);
+ mExportThread = new Thread(mExportRunnable);
+ mExportThread.start();
+ }
+
+ private PendingIntent getStopPendingIntent() {
+ final Intent intent = new Intent(this, ExportUpdateService.class);
+ intent.setAction(ACTION_STOP_EXPORTING);
+ return PendingIntent.getService(this, 0, intent,
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+}
diff --git a/src/org/lineageos/updater/UpdatesListAdapter.java b/src/org/lineageos/updater/UpdatesListAdapter.java
index ef877438..39a6c598 100644
--- a/src/org/lineageos/updater/UpdatesListAdapter.java
+++ b/src/org/lineageos/updater/UpdatesListAdapter.java
@@ -16,6 +16,7 @@
package org.lineageos.updater;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.design.widget.Snackbar;
@@ -42,7 +43,6 @@ import android.widget.TextView;
import org.lineageos.updater.controller.Controller;
import org.lineageos.updater.misc.BuildInfoUtils;
import org.lineageos.updater.misc.Constants;
-import org.lineageos.updater.misc.FileUtils;
import org.lineageos.updater.misc.PermissionsUtils;
import org.lineageos.updater.misc.StringGenerator;
import org.lineageos.updater.misc.Utils;
@@ -563,17 +563,15 @@ public class UpdatesListAdapter extends RecyclerView.Adapter() {
-
- private ProgressDialog mProgressDialog;
- private boolean mCancelled;
- private ProgressCallBack mProgressCallBack;
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- mProgressDialog = new ProgressDialog(context);
- mProgressDialog.setTitle(context.getString(R.string.dialog_export_title));
- mProgressDialog.setMessage(
- context.getString(R.string.dialog_export_message, destFile.getName()));
- mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- 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(sourceFile, destFile, mProgressCallBack);
- return true;
- } catch (IOException e) {
- return false;
- }
- }
-
- @Override
- protected void onCancelled() {
- mCancelled = true;
- destFile.delete();
- }
-
- @Override
- protected void onPostExecute(Boolean success) {
- mProgressDialog.dismiss();
- if (mCancelled) {
- destFile.delete();
- } else {
- NotificationManager nm = (NotificationManager) context.getSystemService(
- Context.NOTIFICATION_SERVICE);
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
- builder.setSmallIcon(R.drawable.ic_system_update);
- builder.setContentTitle(
- success ? context.getString(R.string.notification_export_success)
- : context.getString(R.string.notification_export_fail));
- builder.setContentText(destFile.getName());
- final String notificationTag = destFile.getAbsolutePath();
- nm.notify(notificationTag, NOTIFICATION_ID, builder.build());
- }
- }
- }.execute();
- }
-
public static void prepareForUncrypt(Context context, File updateFile, File uncryptFile,
Runnable callback) {