diff options
Diffstat (limited to 'app/src/fatweb/java/se.leap.bitmaskclient/appUpdate')
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); + } + +} |