mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-12 07:07:42 +00:00
WIP: refactor on jobs using old job table
This commit is contained in:
@@ -78,35 +78,6 @@ public class JobManager implements ConstraintObserver.Notifier {
|
|||||||
new Chain(this, Collections.singletonList(job)).enqueue();
|
new Chain(this, Collections.singletonList(job)).enqueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Begins the creation of a job chain with a single job.
|
|
||||||
* @see Chain
|
|
||||||
*/
|
|
||||||
public Chain startChain(@NonNull Job job) {
|
|
||||||
return new Chain(this, Collections.singletonList(job));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Begins the creation of a job chain with a set of jobs that can be run in parallel.
|
|
||||||
* @see Chain
|
|
||||||
*/
|
|
||||||
public Chain startChain(@NonNull List<? extends Job> jobs) {
|
|
||||||
return new Chain(this, jobs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a string representing the state of the job queue. Intended for debugging.
|
|
||||||
*/
|
|
||||||
public @NonNull String getDebugInfo() {
|
|
||||||
Future<String> result = executor.submit(jobController::getDebugInfo);
|
|
||||||
try {
|
|
||||||
return result.get();
|
|
||||||
} catch (ExecutionException | InterruptedException e) {
|
|
||||||
Log.w(TAG, "Failed to retrieve Job info.", e);
|
|
||||||
return "Failed to retrieve Job info.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a listener to that will be notified when the job queue has been drained.
|
* Adds a listener to that will be notified when the job queue has been drained.
|
||||||
*/
|
*/
|
||||||
@@ -261,16 +232,6 @@ public class JobManager implements ConstraintObserver.Notifier {
|
|||||||
private Data.Serializer dataSerializer = new JsonDataSerializer();
|
private Data.Serializer dataSerializer = new JsonDataSerializer();
|
||||||
private JobStorage jobStorage = null;
|
private JobStorage jobStorage = null;
|
||||||
|
|
||||||
public @NonNull Builder setJobThreadCount(int jobThreadCount) {
|
|
||||||
this.jobThreadCount = jobThreadCount;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull Builder setExecutorFactory(@NonNull ExecutorFactory executorFactory) {
|
|
||||||
this.executorFactory = executorFactory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull Builder setJobFactories(@NonNull Map<String, Job.Factory> jobFactories) {
|
public @NonNull Builder setJobFactories(@NonNull Map<String, Job.Factory> jobFactories) {
|
||||||
this.jobFactories = jobFactories;
|
this.jobFactories = jobFactories;
|
||||||
return this;
|
return this;
|
||||||
|
@@ -29,9 +29,9 @@ public final class JobManagerFactories {
|
|||||||
public static Map<String, Job.Factory> getJobFactories(@NonNull Application application) {
|
public static Map<String, Job.Factory> getJobFactories(@NonNull Application application) {
|
||||||
HashMap<String, Job.Factory> factoryHashMap = new HashMap<String, Job.Factory>() {{
|
HashMap<String, Job.Factory> factoryHashMap = new HashMap<String, Job.Factory>() {{
|
||||||
put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory());
|
put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory());
|
||||||
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
|
put(LocalBackupJob.Companion.getKEY(), new LocalBackupJob.Factory());
|
||||||
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application));
|
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application));
|
||||||
put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
|
put(UpdateApkJob.Companion.getKEY(), new UpdateApkJob.Factory());
|
||||||
put(PrepareAttachmentAudioExtrasJob.KEY, new PrepareAttachmentAudioExtrasJob.Factory());
|
put(PrepareAttachmentAudioExtrasJob.KEY, new PrepareAttachmentAudioExtrasJob.Factory());
|
||||||
}};
|
}};
|
||||||
factoryKeys.addAll(factoryHashMap.keySet());
|
factoryKeys.addAll(factoryHashMap.keySet());
|
||||||
|
@@ -1,83 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.jobs;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.session.libsession.messaging.utilities.Data;
|
|
||||||
import org.session.libsignal.utilities.NoExternalStorageException;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
import org.thoughtcrime.securesms.database.BackupFileRecord;
|
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
|
||||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
|
||||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
public class LocalBackupJob extends BaseJob {
|
|
||||||
|
|
||||||
public static final String KEY = "LocalBackupJob";
|
|
||||||
|
|
||||||
private static final String TAG = LocalBackupJob.class.getSimpleName();
|
|
||||||
|
|
||||||
public LocalBackupJob() {
|
|
||||||
this(new Job.Parameters.Builder()
|
|
||||||
.setQueue("__LOCAL_BACKUP__")
|
|
||||||
.setMaxInstances(1)
|
|
||||||
.setMaxAttempts(3)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalBackupJob(@NonNull Job.Parameters parameters) {
|
|
||||||
super(parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull
|
|
||||||
Data serialize() {
|
|
||||||
return Data.EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull String getFactoryKey() {
|
|
||||||
return KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRun() throws NoExternalStorageException, IOException {
|
|
||||||
Log.i(TAG, "Executing backup job...");
|
|
||||||
|
|
||||||
GenericForegroundService.startForegroundTask(context,
|
|
||||||
context.getString(R.string.LocalBackupJob_creating_backup),
|
|
||||||
NotificationChannels.BACKUPS,
|
|
||||||
R.drawable.ic_launcher_foreground);
|
|
||||||
|
|
||||||
// TODO: Maybe create a new backup icon like ic_signal_backup?
|
|
||||||
|
|
||||||
try {
|
|
||||||
BackupFileRecord record = BackupUtil.createBackupFile(context);
|
|
||||||
BackupUtil.deleteAllBackupFiles(context, Collections.singletonList(record));
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
GenericForegroundService.stopForegroundTask(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onShouldRetry(@NonNull Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCanceled() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Factory implements Job.Factory<LocalBackupJob> {
|
|
||||||
@Override
|
|
||||||
public @NonNull LocalBackupJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
|
||||||
return new LocalBackupJob(parameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,61 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.session.libsession.messaging.jobs.Job
|
||||||
|
import org.session.libsession.messaging.jobs.JobDelegate
|
||||||
|
import org.session.libsession.messaging.utilities.Data
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||||
|
import org.thoughtcrime.securesms.service.GenericForegroundService
|
||||||
|
import org.thoughtcrime.securesms.util.BackupUtil.createBackupFile
|
||||||
|
import org.thoughtcrime.securesms.util.BackupUtil.deleteAllBackupFiles
|
||||||
|
|
||||||
|
import network.loki.messenger.R
|
||||||
|
|
||||||
|
|
||||||
|
class LocalBackupJob:Job {
|
||||||
|
override var delegate: JobDelegate? = null
|
||||||
|
override var id: String? = null
|
||||||
|
override var failureCount: Int = 0
|
||||||
|
override val maxFailureCount: Int = 0
|
||||||
|
|
||||||
|
lateinit var context: Context
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TAG = LocalBackupJob::class.simpleName
|
||||||
|
val KEY: String = "LocalBackupJob"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun execute(dispatcherName: String) {
|
||||||
|
Log.i(TAG, "Executing backup job...")
|
||||||
|
|
||||||
|
GenericForegroundService.startForegroundTask(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.LocalBackupJob_creating_backup),
|
||||||
|
NotificationChannels.BACKUPS,
|
||||||
|
R.drawable.ic_launcher_foreground
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Maybe create a new backup icon like ic_signal_backup?
|
||||||
|
try {
|
||||||
|
val record = createBackupFile(context)
|
||||||
|
deleteAllBackupFiles(context, listOf(record))
|
||||||
|
} finally {
|
||||||
|
GenericForegroundService.stopForegroundTask(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(): Data {
|
||||||
|
return Data.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFactoryKey(): String {
|
||||||
|
return KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
class Factory: Job.Factory<LocalBackupJob> {
|
||||||
|
override fun create(data: Data): LocalBackupJob {
|
||||||
|
return LocalBackupJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,271 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.jobs;
|
|
||||||
|
|
||||||
|
|
||||||
import android.app.DownloadManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
import org.session.libsession.messaging.utilities.Data;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
import org.thoughtcrime.securesms.service.UpdateApkReadyListener;
|
|
||||||
import org.session.libsession.utilities.FileUtils;
|
|
||||||
import org.session.libsignal.utilities.Hex;
|
|
||||||
import org.session.libsignal.utilities.JsonUtil;
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
|
|
||||||
import network.loki.messenger.BuildConfig;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public class UpdateApkJob extends BaseJob {
|
|
||||||
|
|
||||||
public static final String KEY = "UpdateApkJob";
|
|
||||||
|
|
||||||
private static final String TAG = UpdateApkJob.class.getSimpleName();
|
|
||||||
|
|
||||||
public UpdateApkJob() {
|
|
||||||
this(new Job.Parameters.Builder()
|
|
||||||
.setQueue("UpdateApkJob")
|
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
|
||||||
.setMaxAttempts(3)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private UpdateApkJob(@NonNull Job.Parameters parameters) {
|
|
||||||
super(parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull
|
|
||||||
Data serialize() {
|
|
||||||
return Data.EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull String getFactoryKey() {
|
|
||||||
return KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRun() throws IOException, PackageManager.NameNotFoundException {
|
|
||||||
if (!BuildConfig.PLAY_STORE_DISABLED) return;
|
|
||||||
|
|
||||||
Log.i(TAG, "Checking for APK update...");
|
|
||||||
|
|
||||||
OkHttpClient client = new OkHttpClient();
|
|
||||||
Request request = new Request.Builder().url(String.format("%s/latest.json", BuildConfig.NOPLAY_UPDATE_URL)).build();
|
|
||||||
|
|
||||||
Response response = client.newCall(request).execute();
|
|
||||||
|
|
||||||
if (!response.isSuccessful()) {
|
|
||||||
throw new IOException("Bad response: " + response.message());
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateDescriptor updateDescriptor = JsonUtil.fromJson(response.body().string(), UpdateDescriptor.class);
|
|
||||||
byte[] digest = Hex.fromStringCondensed(updateDescriptor.getDigest());
|
|
||||||
|
|
||||||
Log.i(TAG, "Got descriptor: " + updateDescriptor);
|
|
||||||
|
|
||||||
if (updateDescriptor.getVersionCode() > getVersionCode()) {
|
|
||||||
DownloadStatus downloadStatus = getDownloadStatus(updateDescriptor.getUrl(), digest);
|
|
||||||
|
|
||||||
Log.i(TAG, "Download status: " + downloadStatus.getStatus());
|
|
||||||
|
|
||||||
if (downloadStatus.getStatus() == DownloadStatus.Status.COMPLETE) {
|
|
||||||
Log.i(TAG, "Download status complete, notifying...");
|
|
||||||
handleDownloadNotify(downloadStatus.getDownloadId());
|
|
||||||
} else if (downloadStatus.getStatus() == DownloadStatus.Status.MISSING) {
|
|
||||||
Log.i(TAG, "Download status missing, starting download...");
|
|
||||||
handleDownloadStart(updateDescriptor.getUrl(), updateDescriptor.getVersionName(), digest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onShouldRetry(@NonNull Exception e) {
|
|
||||||
return e instanceof IOException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCanceled() {
|
|
||||||
Log.w(TAG, "Update check failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getVersionCode() throws PackageManager.NameNotFoundException {
|
|
||||||
PackageManager packageManager = context.getPackageManager();
|
|
||||||
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
|
|
||||||
|
|
||||||
return packageInfo.versionCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DownloadStatus getDownloadStatus(String uri, byte[] theirDigest) {
|
|
||||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
DownloadManager.Query query = new DownloadManager.Query();
|
|
||||||
|
|
||||||
query.setFilterByStatus(DownloadManager.STATUS_PAUSED | DownloadManager.STATUS_PENDING | DownloadManager.STATUS_RUNNING | DownloadManager.STATUS_SUCCESSFUL);
|
|
||||||
|
|
||||||
long pendingDownloadId = TextSecurePreferences.getUpdateApkDownloadId(context);
|
|
||||||
byte[] pendingDigest = getPendingDigest(context);
|
|
||||||
Cursor cursor = downloadManager.query(query);
|
|
||||||
|
|
||||||
try {
|
|
||||||
DownloadStatus status = new DownloadStatus(DownloadStatus.Status.MISSING, -1);
|
|
||||||
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
int jobStatus = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
|
|
||||||
String jobRemoteUri = cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_URI));
|
|
||||||
long downloadId = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID));
|
|
||||||
byte[] digest = getDigestForDownloadId(downloadId);
|
|
||||||
|
|
||||||
if (jobRemoteUri != null && jobRemoteUri.equals(uri) && downloadId == pendingDownloadId) {
|
|
||||||
|
|
||||||
if (jobStatus == DownloadManager.STATUS_SUCCESSFUL &&
|
|
||||||
digest != null && pendingDigest != null &&
|
|
||||||
MessageDigest.isEqual(pendingDigest, theirDigest) &&
|
|
||||||
MessageDigest.isEqual(digest, theirDigest))
|
|
||||||
{
|
|
||||||
return new DownloadStatus(DownloadStatus.Status.COMPLETE, downloadId);
|
|
||||||
} else if (jobStatus != DownloadManager.STATUS_SUCCESSFUL) {
|
|
||||||
status = new DownloadStatus(DownloadStatus.Status.PENDING, downloadId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDownloadStart(String uri, String versionName, byte[] digest) {
|
|
||||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(uri));
|
|
||||||
|
|
||||||
downloadRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
|
|
||||||
downloadRequest.setTitle("Downloading Signal update");
|
|
||||||
downloadRequest.setDescription("Downloading Signal " + versionName);
|
|
||||||
downloadRequest.setVisibleInDownloadsUi(false);
|
|
||||||
downloadRequest.setDestinationInExternalFilesDir(context, null, "signal-update.apk");
|
|
||||||
downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
|
|
||||||
|
|
||||||
long downloadId = downloadManager.enqueue(downloadRequest);
|
|
||||||
TextSecurePreferences.setUpdateApkDownloadId(context, downloadId);
|
|
||||||
TextSecurePreferences.setUpdateApkDigest(context, Hex.toStringCondensed(digest));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDownloadNotify(long downloadId) {
|
|
||||||
Intent intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
|
|
||||||
intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);
|
|
||||||
|
|
||||||
new UpdateApkReadyListener().onReceive(context, intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable byte[] getDigestForDownloadId(long downloadId) {
|
|
||||||
try {
|
|
||||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
FileInputStream fin = new FileInputStream(downloadManager.openDownloadedFile(downloadId).getFileDescriptor());
|
|
||||||
byte[] digest = FileUtils.getFileDigest(fin);
|
|
||||||
|
|
||||||
fin.close();
|
|
||||||
|
|
||||||
return digest;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable byte[] getPendingDigest(Context context) {
|
|
||||||
try {
|
|
||||||
String encodedDigest = TextSecurePreferences.getUpdateApkDigest(context);
|
|
||||||
|
|
||||||
if (encodedDigest == null) return null;
|
|
||||||
|
|
||||||
return Hex.fromStringCondensed(encodedDigest);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class UpdateDescriptor {
|
|
||||||
@JsonProperty
|
|
||||||
private int versionCode;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String versionName;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String url;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String sha256sum;
|
|
||||||
|
|
||||||
|
|
||||||
public int getVersionCode() {
|
|
||||||
return versionCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVersionName() {
|
|
||||||
return versionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull String toString() {
|
|
||||||
return "[" + versionCode + ", " + versionName + ", " + url + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDigest() {
|
|
||||||
return sha256sum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DownloadStatus {
|
|
||||||
enum Status {
|
|
||||||
PENDING,
|
|
||||||
COMPLETE,
|
|
||||||
MISSING
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Status status;
|
|
||||||
private final long downloadId;
|
|
||||||
|
|
||||||
DownloadStatus(Status status, long downloadId) {
|
|
||||||
this.status = status;
|
|
||||||
this.downloadId = downloadId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getDownloadId() {
|
|
||||||
return downloadId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Factory implements Job.Factory<UpdateApkJob> {
|
|
||||||
@Override
|
|
||||||
public @NonNull UpdateApkJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
|
||||||
return new UpdateApkJob(parameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,200 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable
|
||||||
|
|
||||||
|
import android.app.DownloadManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import network.loki.messenger.BuildConfig
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.session.libsession.messaging.jobs.Job
|
||||||
|
import org.session.libsession.messaging.jobs.JobDelegate
|
||||||
|
import org.session.libsession.messaging.utilities.Data
|
||||||
|
import org.session.libsession.utilities.FileUtils
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.getUpdateApkDigest
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.getUpdateApkDownloadId
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.setUpdateApkDigest
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.setUpdateApkDownloadId
|
||||||
|
import org.session.libsignal.utilities.Hex
|
||||||
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.thoughtcrime.securesms.service.UpdateApkReadyListener
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
class UpdateApkJob: Job {
|
||||||
|
override var delegate: JobDelegate? = null
|
||||||
|
override var id: String? = null
|
||||||
|
override var failureCount: Int = 0
|
||||||
|
override val maxFailureCount: Int = 0
|
||||||
|
|
||||||
|
lateinit var context: Context
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TAG = UpdateApkJob::class.simpleName
|
||||||
|
val KEY: String = "UpdateApkJob"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun execute(dispatcherName: String) {
|
||||||
|
if (!BuildConfig.PLAY_STORE_DISABLED) return
|
||||||
|
|
||||||
|
Log.i(TAG, "Checking for APK update...")
|
||||||
|
|
||||||
|
val client = OkHttpClient()
|
||||||
|
val request = Request.Builder().url(String.format("%s/latest.json", BuildConfig.NOPLAY_UPDATE_URL)).build()
|
||||||
|
val response = client.newCall(request).execute()
|
||||||
|
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
throw IOException("Bad response: " + response.message())
|
||||||
|
}
|
||||||
|
|
||||||
|
val updateDescriptor: UpdateDescriptor = JsonUtil.fromJson(
|
||||||
|
response.body()!!.string(),
|
||||||
|
UpdateDescriptor::class.java
|
||||||
|
)
|
||||||
|
val digest = Hex.fromStringCondensed(updateDescriptor.digest)
|
||||||
|
|
||||||
|
Log.i(
|
||||||
|
TAG,
|
||||||
|
"Got descriptor: $updateDescriptor"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (updateDescriptor.versionCode > getVersionCode()) {
|
||||||
|
val downloadStatus: DownloadStatus = getDownloadStatus(updateDescriptor.url, digest)
|
||||||
|
Log.i(TAG, "Download status: " + downloadStatus.status)
|
||||||
|
if (downloadStatus.status == DownloadStatus.Status.COMPLETE) {
|
||||||
|
Log.i(TAG, "Download status complete, notifying...")
|
||||||
|
handleDownloadNotify(downloadStatus.downloadId)
|
||||||
|
} else if (downloadStatus.status == DownloadStatus.Status.MISSING) {
|
||||||
|
Log.i(TAG, "Download status missing, starting download...")
|
||||||
|
handleDownloadStart(
|
||||||
|
updateDescriptor.url,
|
||||||
|
updateDescriptor.versionName,
|
||||||
|
digest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
|
private fun getVersionCode(): Int {
|
||||||
|
val packageManager: PackageManager = context.getPackageManager()
|
||||||
|
val packageInfo: PackageInfo = packageManager.getPackageInfo(context.getPackageName(), 0)
|
||||||
|
return packageInfo.versionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDownloadStatus(uri: String, theirDigest: ByteArray): DownloadStatus {
|
||||||
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
val query = DownloadManager.Query()
|
||||||
|
query.setFilterByStatus(DownloadManager.STATUS_PAUSED or DownloadManager.STATUS_PENDING or DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_SUCCESSFUL)
|
||||||
|
val pendingDownloadId = getUpdateApkDownloadId(context)
|
||||||
|
val pendingDigest = getPendingDigest(context)
|
||||||
|
val cursor = downloadManager.query(query)
|
||||||
|
return try {
|
||||||
|
var status = DownloadStatus(DownloadStatus.Status.MISSING, -1)
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
val jobStatus =
|
||||||
|
cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
|
||||||
|
val jobRemoteUri =
|
||||||
|
cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_URI))
|
||||||
|
val downloadId =
|
||||||
|
cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID))
|
||||||
|
val digest = getDigestForDownloadId(downloadId)
|
||||||
|
if (jobRemoteUri != null && jobRemoteUri == uri && downloadId == pendingDownloadId) {
|
||||||
|
if (jobStatus == DownloadManager.STATUS_SUCCESSFUL && digest != null && pendingDigest != null &&
|
||||||
|
MessageDigest.isEqual(pendingDigest, theirDigest) &&
|
||||||
|
MessageDigest.isEqual(digest, theirDigest)
|
||||||
|
) {
|
||||||
|
return DownloadStatus(DownloadStatus.Status.COMPLETE, downloadId)
|
||||||
|
} else if (jobStatus != DownloadManager.STATUS_SUCCESSFUL) {
|
||||||
|
status = DownloadStatus(DownloadStatus.Status.PENDING, downloadId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDownloadStart(uri: String, versionName: String, digest: ByteArray) {
|
||||||
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
val downloadRequest = DownloadManager.Request(Uri.parse(uri))
|
||||||
|
downloadRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI)
|
||||||
|
downloadRequest.setTitle("Downloading Signal update")
|
||||||
|
downloadRequest.setDescription("Downloading Signal $versionName")
|
||||||
|
downloadRequest.setVisibleInDownloadsUi(false)
|
||||||
|
downloadRequest.setDestinationInExternalFilesDir(context, null, "signal-update.apk")
|
||||||
|
downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)
|
||||||
|
val downloadId = downloadManager.enqueue(downloadRequest)
|
||||||
|
setUpdateApkDownloadId(context, downloadId)
|
||||||
|
setUpdateApkDigest(context, Hex.toStringCondensed(digest))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDownloadNotify(downloadId: Long) {
|
||||||
|
val intent = Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||||
|
intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId)
|
||||||
|
UpdateApkReadyListener().onReceive(context, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDigestForDownloadId(downloadId: Long): ByteArray? {
|
||||||
|
return try {
|
||||||
|
val downloadManager =
|
||||||
|
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
val fin = FileInputStream(downloadManager.openDownloadedFile(downloadId).fileDescriptor)
|
||||||
|
val digest = FileUtils.getFileDigest(fin)
|
||||||
|
fin.close()
|
||||||
|
digest
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPendingDigest(context: Context): ByteArray? {
|
||||||
|
return try {
|
||||||
|
val encodedDigest = getUpdateApkDigest(context) ?: return null
|
||||||
|
Hex.fromStringCondensed(encodedDigest)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(): Data {
|
||||||
|
return Data.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFactoryKey(): String {
|
||||||
|
return KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UpdateDescriptor(
|
||||||
|
@JsonProperty("versionCode") @Nullable val versionCode: Int,
|
||||||
|
@JsonProperty("versionName") @Nullable val versionName: String,
|
||||||
|
@JsonProperty("url") @Nullable val url: String,
|
||||||
|
@JsonProperty("sha256sum") @Nullable val digest: String)
|
||||||
|
{
|
||||||
|
override fun toString(): String {
|
||||||
|
return "[$versionCode, $versionName, $url]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DownloadStatus(val status: Status, val downloadId: Long) {
|
||||||
|
enum class Status {
|
||||||
|
PENDING, COMPLETE, MISSING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Factory: Job.Factory<UpdateApkJob> {
|
||||||
|
override fun create(data: Data): UpdateApkJob {
|
||||||
|
return UpdateApkJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.service;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.jobs.JobQueue;
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
|
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.sskenvironment
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
import org.session.libsession.utilities.SSKEnvironment
|
import org.session.libsession.utilities.SSKEnvironment
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
Reference in New Issue
Block a user