mirror of
https://github.com/oxen-io/session-android.git
synced 2023-12-14 02:53:01 +01:00
42f1baaf61
We have to make some changes, and it's gotten to the point where maintaining it as a separate library is more hassle than it's worth, especially with Google releasing WorkManager as the preferred job scheduling library.
254 lines
8.5 KiB
Java
254 lines
8.5 KiB
Java
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 android.os.Build;
|
|
import android.support.annotation.Nullable;
|
|
import android.util.Log;
|
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
|
|
import org.thoughtcrime.securesms.BuildConfig;
|
|
import org.thoughtcrime.securesms.jobmanager.JobParameters;
|
|
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
|
|
import org.thoughtcrime.securesms.service.UpdateApkReadyListener;
|
|
import org.thoughtcrime.securesms.util.FileUtils;
|
|
import org.thoughtcrime.securesms.util.Hex;
|
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.security.MessageDigest;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import okhttp3.OkHttpClient;
|
|
import okhttp3.Request;
|
|
import okhttp3.Response;
|
|
|
|
public class UpdateApkJob extends ContextJob {
|
|
|
|
private static final String TAG = UpdateApkJob.class.getSimpleName();
|
|
|
|
public UpdateApkJob(Context context) {
|
|
super(context, JobParameters.newBuilder()
|
|
.withGroupId(UpdateApkJob.class.getSimpleName())
|
|
.withRequirement(new NetworkRequirement(context))
|
|
.withWakeLock(true, 30, TimeUnit.SECONDS)
|
|
.withRetryCount(2)
|
|
.create());
|
|
}
|
|
|
|
@Override
|
|
public void onAdded() {}
|
|
|
|
@Override
|
|
public void onRun() throws IOException, PackageManager.NameNotFoundException {
|
|
if (!BuildConfig.PLAY_STORE_DISABLED) return;
|
|
|
|
Log.w(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 = JsonUtils.fromJson(response.body().string(), UpdateDescriptor.class);
|
|
byte[] digest = Hex.fromStringCondensed(updateDescriptor.getDigest());
|
|
|
|
Log.w(TAG, "Got descriptor: " + updateDescriptor);
|
|
|
|
if (updateDescriptor.getVersionCode() > getVersionCode()) {
|
|
DownloadStatus downloadStatus = getDownloadStatus(updateDescriptor.getUrl(), digest);
|
|
|
|
Log.w(TAG, "Download status: " + downloadStatus.getStatus());
|
|
|
|
if (downloadStatus.getStatus() == DownloadStatus.Status.COMPLETE) {
|
|
Log.w(TAG, "Download status complete, notifying...");
|
|
handleDownloadNotify(downloadStatus.getDownloadId());
|
|
} else if (downloadStatus.getStatus() == DownloadStatus.Status.MISSING) {
|
|
Log.w(TAG, "Download status missing, starting download...");
|
|
handleDownloadStart(updateDescriptor.getUrl(), updateDescriptor.getVersionName(), digest);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onShouldRetry(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");
|
|
|
|
if (Build.VERSION.SDK_INT >= 11) {
|
|
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 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;
|
|
}
|
|
}
|
|
}
|