summaryrefslogtreecommitdiff
path: root/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/fatweb/java/se.leap.bitmaskclient/appUpdate')
-rw-r--r--app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadBroadcastReceiver.java102
-rw-r--r--app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadConnector.java120
-rw-r--r--app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadNotificationManager.java145
-rw-r--r--app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadService.java82
-rw-r--r--app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadServiceCommand.java81
-rw-r--r--app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/FileProviderUtil.java37
-rw-r--r--app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/InstallActivity.java83
-rw-r--r--app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/UpdateDownloadManager.java211
8 files changed, 861 insertions, 0 deletions
diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadBroadcastReceiver.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadBroadcastReceiver.java
new file mode 100644
index 00000000..a4acc2aa
--- /dev/null
+++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadBroadcastReceiver.java
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2020 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.appUpdate;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import se.leap.bitmaskclient.Constants;
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.utils.PreferenceHelper;
+
+import static android.app.Activity.RESULT_CANCELED;
+import static se.leap.bitmaskclient.Constants.BROADCAST_DOWNLOAD_SERVICE_EVENT;
+import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.DOWNLOAD_PROGRESS;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.NO_NEW_VERISON;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.PROGRESS_VALUE;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_DOWNLOADED;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_DOWNLOAD_FAILED;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_FOUND;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_NOT_FOUND;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.VERIFICATION_ERROR;
+import static se.leap.bitmaskclient.appUpdate.DownloadServiceCommand.DOWNLOAD_UPDATE;
+
+public class DownloadBroadcastReceiver extends BroadcastReceiver {
+
+ public static final String ACTION_DOWNLOAD = "se.leap.bitmaskclient.appUpdate.ACTION_DOWNLOAD";
+ private static final String TAG = DownloadBroadcastReceiver.class.getSimpleName();
+
+ private DownloadNotificationManager notificationManager;
+
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ if (notificationManager == null) {
+ notificationManager = new DownloadNotificationManager(context.getApplicationContext());
+ }
+
+ int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED);
+ Bundle resultData = intent.getParcelableExtra(Constants.BROADCAST_RESULT_KEY);
+
+ switch (action) {
+ case BROADCAST_DOWNLOAD_SERVICE_EVENT:
+ switch (resultCode) {
+ case UPDATE_FOUND:
+ notificationManager.buildDownloadFoundNotification();
+ break;
+ case UPDATE_NOT_FOUND:
+ if (resultData.getBoolean(NO_NEW_VERISON, false)) {
+ PreferenceHelper.setLastAppUpdateCheck(context.getApplicationContext());
+ }
+ break;
+ case UPDATE_DOWNLOADED:
+ notificationManager.buildDownloadSuccessfulNotification();
+ break;
+ case UPDATE_DOWNLOAD_FAILED:
+ if (resultData.getBoolean(VERIFICATION_ERROR, false)) {
+ Toast.makeText(context.getApplicationContext(), context.getString(R.string.version_update_error_pgp_verification), Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(context.getApplicationContext(), context.getString(R.string.version_update_error), Toast.LENGTH_LONG).show();
+ }
+ notificationManager.cancelNotifications();
+ break;
+ case DOWNLOAD_PROGRESS:
+ int progress = resultData.getInt(PROGRESS_VALUE, 0);
+ notificationManager.buildDownloadUpdateProgress(progress);
+ break;
+ }
+ break;
+
+ case ACTION_DOWNLOAD:
+ DownloadServiceCommand.execute(context.getApplicationContext(), DOWNLOAD_UPDATE);
+ break;
+
+ default:
+ break;
+ }
+
+ }
+}
diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadConnector.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadConnector.java
new file mode 100644
index 00000000..8723f515
--- /dev/null
+++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadConnector.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2020 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.appUpdate;
+
+
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Scanner;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.Buffer;
+import okio.BufferedSink;
+import okio.BufferedSource;
+import okio.Okio;
+
+
+/**
+ * This class encapsulates HTTP requests so that the results can be mocked
+ * and it's owning UpdateDownloadManager class logic can be unit tested properly
+ *
+ */
+public class DownloadConnector {
+
+ private static final String TAG = DownloadConnector.class.getSimpleName();
+ public final static String APP_TYPE = "application/vnd.android.package-archive";
+ public final static String TEXT_FILE_TYPE = "application/text";
+
+ public interface DownloadProgress {
+ void onUpdate(int progress);
+ }
+
+ static String requestTextFileFromServer(@NonNull String url, @NonNull OkHttpClient okHttpClient) {
+ try {
+ Request request = new Request.Builder()
+ .url(url)
+ .addHeader("Content-Type", TEXT_FILE_TYPE)
+ .build();
+
+ Response response = okHttpClient.newCall(request).execute();
+ if (!response.isSuccessful()) {
+ return null;
+ }
+ InputStream inputStream = response.body().byteStream();
+ Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
+ if (scanner.hasNext()) {
+ return scanner.next();
+ }
+ return null;
+
+ } catch (Exception e) {
+ Log.d(TAG, "Text file download failed");
+ }
+
+ return null;
+ }
+
+ static File requestFileFromServer(@NonNull String url, @NonNull OkHttpClient okHttpClient, File destFile, DownloadProgress callback) {
+ BufferedSink sink;
+ BufferedSource source;
+ try {
+ Request.Builder requestBuilder = new Request.Builder()
+ .url(url)
+ .addHeader("Content-Type", APP_TYPE);
+ Request request = requestBuilder.build();
+
+ Response response = okHttpClient.newCall(request).execute();
+ ResponseBody body = response.body();
+ long contentLength = body.contentLength();
+ source = body.source();
+ sink = Okio.buffer(Okio.sink(destFile));
+ Buffer sinkBuffer = sink.buffer();
+ long totalBytesRead = 0;
+ int bufferSize = 8 * 1024;
+ long bytesRead;
+ int lastProgress = 0;
+ while ((bytesRead = source.read(sinkBuffer, bufferSize)) != -1) {
+ sink.emit();
+ totalBytesRead += bytesRead;
+ int progress = (int) ((totalBytesRead * 100) / contentLength);
+ // debouncing callbacks
+ if (lastProgress < progress) {
+ lastProgress = progress;
+ callback.onUpdate(progress);
+ }
+ }
+ sink.flush();
+
+ return destFile;
+
+ } catch (Exception e) {
+ Log.d(TAG, "File download failed");
+ }
+
+ return null;
+ }
+
+}
diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadNotificationManager.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadNotificationManager.java
new file mode 100644
index 00000000..aaf487aa
--- /dev/null
+++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadNotificationManager.java
@@ -0,0 +1,145 @@
+/**
+ * Copyright (c) 2020 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.appUpdate;
+
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+
+import se.leap.bitmaskclient.R;
+
+import static android.content.Intent.CATEGORY_DEFAULT;
+import static se.leap.bitmaskclient.appUpdate.DownloadBroadcastReceiver.ACTION_DOWNLOAD;
+
+public class DownloadNotificationManager {
+ private Context context;
+ private final static int DOWNLOAD_NOTIFICATION_ID = 1;
+
+ public DownloadNotificationManager(@NonNull Context context) {
+ this.context = context;
+ }
+
+ public void buildDownloadFoundNotification() {
+ NotificationManager notificationManager = initNotificationManager();
+ if (notificationManager == null) {
+ return;
+ }
+ NotificationCompat.Builder notificationBuilder = initNotificationBuilderDefaults();
+ notificationBuilder
+ .setSmallIcon(R.drawable.ic_about_36)
+ .setWhen(System.currentTimeMillis())
+ .setTicker(context.getString(R.string.version_update_title, context.getString(R.string.app_name)))
+ .setContentTitle(context.getString(R.string.version_update_title, context.getString(R.string.app_name)))
+ .setContentText(context.getString(R.string.version_update_found))
+ .setContentIntent(getDownloadIntent());
+ notificationManager.notify(DOWNLOAD_NOTIFICATION_ID, notificationBuilder.build());
+ }
+
+ public void buildDownloadSuccessfulNotification() {
+ NotificationManager notificationManager = initNotificationManager();
+ if (notificationManager == null) {
+ return;
+ }
+ NotificationCompat.Builder notificationBuilder = initNotificationBuilderDefaults();
+ notificationBuilder
+ .setSmallIcon(android.R.drawable.stat_sys_download_done)
+ .setWhen(System.currentTimeMillis())
+ .setTicker(context.getString(R.string.version_update_title, context.getString(R.string.app_name)))
+ .setContentTitle(context.getString(R.string.version_update_download_title, context.getString(R.string.app_name)))
+ .setContentText(context.getString(R.string.version_update_download_description))
+ .setContentIntent(getInstallIntent());
+ notificationManager.notify(DOWNLOAD_NOTIFICATION_ID, notificationBuilder.build());
+ }
+
+ public void buildDownloadUpdateProgress(int progress) {
+ NotificationManager notificationManager = initNotificationManager();
+ if (notificationManager == null) {
+ return;
+ }
+
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this.context, DownloadService.NOTIFICATION_CHANNEL_NEWSTATUS_ID);
+ notificationBuilder
+ .setDefaults(Notification.DEFAULT_ALL)
+ .setAutoCancel(false)
+ .setOngoing(true)
+ .setSmallIcon(android.R.drawable.stat_sys_download)
+ .setContentTitle(context.getString(R.string.version_update_apk_description, context.getString(R.string.app_name)))
+ .setProgress(100, progress, false)
+ .setContentIntent(getDownloadIntent());
+ notificationManager.notify(DOWNLOAD_NOTIFICATION_ID, notificationBuilder.build());
+ }
+
+ private NotificationManager initNotificationManager() {
+ NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager == null) {
+ return null;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ createNotificationChannel(notificationManager);
+ }
+ return notificationManager;
+ }
+
+ @TargetApi(26)
+ private void createNotificationChannel(NotificationManager notificationManager) {
+ CharSequence name = "Bitmask Updates";
+ String description = "Informs about available updates";
+ NotificationChannel channel = new NotificationChannel(DownloadService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ name,
+ NotificationManager.IMPORTANCE_LOW);
+ channel.setSound(null, null);
+ channel.setDescription(description);
+ // Register the channel with the system; you can't change the importance
+ // or other notification behaviors after this
+ notificationManager.createNotificationChannel(channel);
+ }
+
+ private NotificationCompat.Builder initNotificationBuilderDefaults() {
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this.context, DownloadService.NOTIFICATION_CHANNEL_NEWSTATUS_ID);
+ notificationBuilder.
+ setDefaults(Notification.DEFAULT_ALL).
+ setAutoCancel(true);
+ return notificationBuilder;
+ }
+
+ private PendingIntent getDownloadIntent() {
+ Intent downloadIntent = new Intent(context, DownloadBroadcastReceiver.class);
+ downloadIntent.setAction(ACTION_DOWNLOAD);
+ return PendingIntent.getBroadcast(context, 0, downloadIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ private PendingIntent getInstallIntent() {
+ Intent installIntent = new Intent(context, InstallActivity.class);
+ return PendingIntent.getActivity(context, 0, installIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ public void cancelNotifications() {
+ NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager == null) {
+ return;
+ }
+ notificationManager.cancel(DOWNLOAD_NOTIFICATION_ID);
+ }
+}
diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadService.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadService.java
new file mode 100644
index 00000000..bc9adfc1
--- /dev/null
+++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadService.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2020 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.appUpdate;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.JobIntentService;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import se.leap.bitmaskclient.OkHttpClientGenerator;
+
+public class DownloadService extends JobIntentService implements UpdateDownloadManager.DownloadServiceCallback {
+
+ static final int JOB_ID = 161376;
+ static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "bitmask_download_service_news";
+
+ final public static String TAG = DownloadService.class.getSimpleName(),
+ PROGRESS_VALUE = "progressValue",
+ NO_NEW_VERISON = "noNewVersion",
+ DOWNLOAD_FAILED = "downloadFailed",
+ NO_PUB_KEY = "noPubKey",
+ VERIFICATION_ERROR = "verificationError";
+
+ final public static int
+ UPDATE_DOWNLOADED = 1,
+ UPDATE_DOWNLOAD_FAILED = 2,
+ UPDATE_FOUND = 3,
+ UPDATE_NOT_FOUND = 4,
+ DOWNLOAD_PROGRESS = 6;
+
+
+ private UpdateDownloadManager updateDownloadManager;
+
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ updateDownloadManager = initDownloadManager();
+ }
+
+ @Override
+ protected void onHandleWork(@NonNull Intent intent) {
+ updateDownloadManager.handleIntent(intent);
+ }
+
+ /**
+ * Convenience method for enqueuing work in to this service.
+ */
+ static void enqueueWork(Context context, Intent work) {
+ try {
+ DownloadService.enqueueWork(context, DownloadService.class, JOB_ID, work);
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private UpdateDownloadManager initDownloadManager() {
+ OkHttpClientGenerator clientGenerator = new OkHttpClientGenerator(null);
+ return new UpdateDownloadManager(this, clientGenerator, this);
+ }
+
+ @Override
+ public void broadcastEvent(Intent intent) {
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+ }
+}
diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadServiceCommand.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadServiceCommand.java
new file mode 100644
index 00000000..c4e809f2
--- /dev/null
+++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadServiceCommand.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2020 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.appUpdate;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.ResultReceiver;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import se.leap.bitmaskclient.ProviderAPI;
+
+public class DownloadServiceCommand {
+
+ public final static String
+ CHECK_VERSION_FILE = "checkVersionFile",
+ DOWNLOAD_UPDATE = "downloadUpdate";
+
+ private Context context;
+ private String action;
+ private ResultReceiver resultReceiver;
+
+ private DownloadServiceCommand(@NotNull Context context, @NotNull String action) {
+ this(context.getApplicationContext(), action, null);
+ }
+
+ private DownloadServiceCommand(@NotNull Context context, @NotNull String action, @Nullable ResultReceiver resultReceiver) {
+ super();
+ this.context = context;
+ this.action = action;
+ this.resultReceiver = resultReceiver;
+ }
+
+
+ private Intent setUpIntent() {
+ Intent command = new Intent(context, ProviderAPI.class);
+ command.setAction(action);
+ if (resultReceiver != null) {
+ command.putExtra(ProviderAPI.RECEIVER_KEY, resultReceiver);
+ }
+ return command;
+ }
+
+ private boolean isInitialized() {
+ return context != null;
+ }
+
+
+ private void execute() {
+ if (isInitialized()) {
+ Intent intent = setUpIntent();
+ DownloadService.enqueueWork(context, intent);
+ }
+ }
+
+ public static void execute(Context context, String action) {
+ DownloadServiceCommand command = new DownloadServiceCommand(context, action);
+ command.execute();
+ }
+
+ public static void execute(Context context, String action, ResultReceiver resultReceiver) {
+ DownloadServiceCommand command = new DownloadServiceCommand(context, action, resultReceiver);
+ command.execute();
+ }
+
+}
diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/FileProviderUtil.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/FileProviderUtil.java
new file mode 100644
index 00000000..4966a863
--- /dev/null
+++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/FileProviderUtil.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2020 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.appUpdate;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import androidx.annotation.NonNull;
+import androidx.core.content.FileProvider;
+
+import java.io.File;
+
+import se.leap.bitmaskclient.BuildConfig;
+
+public class FileProviderUtil {
+
+ private static final String AUTHORITY = BuildConfig.APPLICATION_ID +".fileprovider";
+
+ public static Uri getUriFor(@NonNull Context context, @NonNull File file) {
+ if (Build.VERSION.SDK_INT >= 24) return FileProvider.getUriForFile(context, AUTHORITY, file);
+ else return Uri.fromFile(file);
+ }
+}
diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/InstallActivity.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/InstallActivity.java
new file mode 100644
index 00000000..6629425c
--- /dev/null
+++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/InstallActivity.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2020 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.appUpdate;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import java.io.File;
+
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.utils.PreferenceHelper;
+
+import static se.leap.bitmaskclient.Constants.REQUEST_CODE_REQUEST_UPDATE;
+import static se.leap.bitmaskclient.appUpdate.DownloadConnector.APP_TYPE;
+import static se.leap.bitmaskclient.appUpdate.FileProviderUtil.getUriFor;
+
+public class InstallActivity extends Activity {
+
+ private static final String TAG = InstallActivity.class.getSimpleName();
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestPermissionAndInstall();
+ }
+
+ private void requestPermissionAndInstall() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !this.getPackageManager().canRequestPackageInstalls()) {
+ startActivityForResult(new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:"+getPackageName())),
+ REQUEST_CODE_REQUEST_UPDATE);
+ } else {
+ installUpdate();
+ }
+ }
+
+ protected void installUpdate() {
+ PreferenceHelper.restartOnUpdate(this.getApplicationContext(), true);
+
+ Intent installIntent = new Intent(Intent.ACTION_VIEW);
+ File update = UpdateDownloadManager.getUpdateFile(this.getApplicationContext());
+ if (update.exists()) {
+ installIntent.setDataAndType(getUriFor(this.getApplicationContext(), update), APP_TYPE);
+ installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ this.startActivity(installIntent);
+ finish();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_CODE_REQUEST_UPDATE) {
+ if (resultCode == RESULT_OK) {
+ installUpdate();
+ } else {
+ Toast.makeText(this, getString(R.string.version_update_error_permissions), Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+ }
+}
diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/UpdateDownloadManager.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/UpdateDownloadManager.java
new file mode 100644
index 00000000..b79c2a91
--- /dev/null
+++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/UpdateDownloadManager.java
@@ -0,0 +1,211 @@
+/**
+ * Copyright (c) 2020 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.appUpdate;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.io.File;
+
+import okhttp3.OkHttpClient;
+import pgpverify.Logger;
+import pgpverify.PgpVerifier;
+import se.leap.bitmaskclient.BuildConfig;
+import se.leap.bitmaskclient.OkHttpClientGenerator;
+import se.leap.bitmaskclient.R;
+
+import static android.text.TextUtils.isEmpty;
+import static se.leap.bitmaskclient.Constants.BROADCAST_DOWNLOAD_SERVICE_EVENT;
+import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE;
+import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;
+import static se.leap.bitmaskclient.ProviderAPI.RECEIVER_KEY;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.DOWNLOAD_FAILED;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.DOWNLOAD_PROGRESS;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.NO_NEW_VERISON;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.NO_PUB_KEY;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.PROGRESS_VALUE;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_DOWNLOADED;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_DOWNLOAD_FAILED;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_FOUND;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.UPDATE_NOT_FOUND;
+import static se.leap.bitmaskclient.appUpdate.DownloadService.VERIFICATION_ERROR;
+import static se.leap.bitmaskclient.appUpdate.DownloadServiceCommand.CHECK_VERSION_FILE;
+import static se.leap.bitmaskclient.appUpdate.DownloadServiceCommand.DOWNLOAD_UPDATE;
+import static se.leap.bitmaskclient.utils.FileHelper.readPublicKey;
+
+public class UpdateDownloadManager implements Logger, DownloadConnector.DownloadProgress {
+
+
+ private static final String TAG = UpdateDownloadManager.class.getSimpleName();
+
+ public interface DownloadServiceCallback {
+ void broadcastEvent(Intent intent);
+ }
+
+ private Context context;
+
+ private PgpVerifier pgpVerifier;
+ private DownloadServiceCallback serviceCallback;
+ OkHttpClientGenerator clientGenerator;
+
+
+ public UpdateDownloadManager(Context context, OkHttpClientGenerator clientGenerator, DownloadServiceCallback callback) {
+ this.context = context;
+ this.clientGenerator = clientGenerator;
+ pgpVerifier = new PgpVerifier();
+ pgpVerifier.setLogger(this);
+ serviceCallback = callback;
+ }
+
+ //pgpverify Logger interface
+ @Override
+ public void log(String s) {
+
+ }
+
+ @Override
+ public void onUpdate(int progress) {
+ Bundle resultData = new Bundle();
+ resultData.putInt(PROGRESS_VALUE, progress);
+ broadcastEvent(DOWNLOAD_PROGRESS, resultData);
+ }
+
+ public void handleIntent(Intent command) {
+ ResultReceiver receiver = null;
+ if (command.getParcelableExtra(RECEIVER_KEY) != null) {
+ receiver = command.getParcelableExtra(RECEIVER_KEY);
+ }
+ String action = command.getAction();
+
+ Bundle result = new Bundle();
+ switch (action) {
+ case CHECK_VERSION_FILE:
+ result = checkVersionFile(result);
+ if (result.getBoolean(BROADCAST_RESULT_KEY)) {
+ sendToReceiverOrBroadcast(receiver, UPDATE_FOUND, result);
+ } else {
+ sendToReceiverOrBroadcast(receiver, UPDATE_NOT_FOUND, result);
+ }
+ break;
+ case DOWNLOAD_UPDATE:
+ result = downloadUpdate(result);
+ if (result.getBoolean(BROADCAST_RESULT_KEY)) {
+ sendToReceiverOrBroadcast(receiver, UPDATE_DOWNLOADED, result);
+ } else {
+ sendToReceiverOrBroadcast(receiver, UPDATE_DOWNLOAD_FAILED, result);
+ }
+ break;
+ }
+ }
+
+ public static File getUpdateFile(Context context) {
+ return new File(context.getExternalFilesDir(null) + "/" + context.getString(R.string.app_name) + "_update.apk");
+ }
+
+ private Bundle downloadUpdate(Bundle task) {
+
+ String publicKey = readPublicKey(context);
+ if (isEmpty(publicKey)) {
+ task.putBoolean(BROADCAST_RESULT_KEY, false);
+ task.putBoolean(NO_PUB_KEY, true);
+ return task;
+ }
+
+ OkHttpClient client = clientGenerator.init();
+ String signature = DownloadConnector.requestTextFileFromServer(BuildConfig.signature_url, client);
+ if (signature == null) {
+ task.putBoolean(BROADCAST_RESULT_KEY, false);
+ task.putBoolean(DOWNLOAD_FAILED, true);
+ return task;
+ }
+
+ File destinationFile = getUpdateFile(context);
+ if (destinationFile.exists()) {
+ destinationFile.delete();
+ }
+
+ destinationFile = DownloadConnector.requestFileFromServer(BuildConfig.update_apk_url, client, destinationFile, this);
+
+ if (destinationFile == null) {
+ task.putBoolean(BROADCAST_RESULT_KEY, false);
+ task.putBoolean(DOWNLOAD_FAILED, true);
+ return task;
+ }
+
+ boolean successfulVerified = pgpVerifier.verify(signature, publicKey, destinationFile.getAbsolutePath());
+ if (!successfulVerified) {
+ destinationFile.delete();
+ task.putBoolean(BROADCAST_RESULT_KEY, false);
+ task.putBoolean(VERIFICATION_ERROR, true);
+ return task;
+ }
+
+ task.putBoolean(BROADCAST_RESULT_KEY, true);
+ return task;
+ }
+
+ private Bundle checkVersionFile(Bundle task) {
+ OkHttpClient client = clientGenerator.init();
+ String versionString = DownloadConnector.requestTextFileFromServer(BuildConfig.version_file_url, client);
+
+ if (versionString != null) {
+ versionString = versionString.replace("\n", "").trim();
+ }
+
+ int version = -1;
+ try {
+ version = Integer.parseInt(versionString);
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ Log.e(TAG, "could not parse version code: " + versionString);
+ }
+
+ if (version == -1) {
+ task.putBoolean(BROADCAST_RESULT_KEY, false);
+ task.putBoolean(DOWNLOAD_FAILED, true);
+ } else if (BuildConfig.VERSION_CODE >= version) {
+ task.putBoolean(BROADCAST_RESULT_KEY, false);
+ task.putBoolean(NO_NEW_VERISON, true);
+ } else {
+ task.putBoolean(BROADCAST_RESULT_KEY, true);
+ }
+ return task;
+ }
+
+ private void sendToReceiverOrBroadcast(ResultReceiver receiver, int resultCode, Bundle resultData) {
+ if (resultData == null || resultData == Bundle.EMPTY) {
+ resultData = new Bundle();
+ }
+ if (receiver != null) {
+ receiver.send(resultCode, resultData);
+ } else {
+ broadcastEvent(resultCode, resultData);
+ }
+ }
+
+ private void broadcastEvent(int resultCode , Bundle resultData) {
+ Intent intentUpdate = new Intent(BROADCAST_DOWNLOAD_SERVICE_EVENT);
+ intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
+ intentUpdate.putExtra(BROADCAST_RESULT_CODE, resultCode);
+ intentUpdate.putExtra(BROADCAST_RESULT_KEY, resultData);
+ serviceCallback.broadcastEvent(intentUpdate);
+ }
+
+}