2017-02-26 22:36:43 +00:00
|
|
|
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;
|
2018-08-09 14:15:43 +00:00
|
|
|
import android.support.annotation.NonNull;
|
2017-02-26 22:36:43 +00:00
|
|
|
import android.support.annotation.Nullable;
|
2018-08-09 14:15:43 +00:00
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
|
|
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
|
|
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
2018-08-01 15:09:24 +00:00
|
|
|
import org.thoughtcrime.securesms.logging.Log;
|
2017-02-26 22:36:43 +00:00
|
|
|
|
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
|
|
|
2019-07-24 02:30:23 +00:00
|
|
|
import network.loki.messenger.BuildConfig;
|
2017-02-26 22:36:43 +00:00
|
|
|
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 okhttp3.OkHttpClient;
|
|
|
|
import okhttp3.Request;
|
|
|
|
import okhttp3.Response;
|
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
public class UpdateApkJob extends BaseJob {
|
|
|
|
|
|
|
|
public static final String KEY = "UpdateApkJob";
|
2017-02-26 22:36:43 +00:00
|
|
|
|
|
|
|
private static final String TAG = UpdateApkJob.class.getSimpleName();
|
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
public UpdateApkJob() {
|
|
|
|
this(new Job.Parameters.Builder()
|
|
|
|
.setQueue("UpdateApkJob")
|
|
|
|
.addConstraint(NetworkConstraint.KEY)
|
|
|
|
.setMaxAttempts(2)
|
|
|
|
.build());
|
2018-08-09 14:15:43 +00:00
|
|
|
}
|
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
private UpdateApkJob(@NonNull Job.Parameters parameters) {
|
|
|
|
super(parameters);
|
2017-02-26 22:36:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-03-28 15:56:35 +00:00
|
|
|
public @NonNull Data serialize() {
|
|
|
|
return Data.EMPTY;
|
2018-08-09 14:15:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-03-28 15:56:35 +00:00
|
|
|
public @NonNull String getFactoryKey() {
|
|
|
|
return KEY;
|
2018-08-09 14:15:43 +00:00
|
|
|
}
|
2017-02-26 22:36:43 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRun() throws IOException, PackageManager.NameNotFoundException {
|
|
|
|
if (!BuildConfig.PLAY_STORE_DISABLED) return;
|
|
|
|
|
2018-08-02 13:25:33 +00:00
|
|
|
Log.i(TAG, "Checking for APK update...");
|
2017-02-26 22:36:43 +00:00
|
|
|
|
|
|
|
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());
|
|
|
|
|
2018-08-02 13:25:33 +00:00
|
|
|
Log.i(TAG, "Got descriptor: " + updateDescriptor);
|
2017-02-26 22:36:43 +00:00
|
|
|
|
|
|
|
if (updateDescriptor.getVersionCode() > getVersionCode()) {
|
|
|
|
DownloadStatus downloadStatus = getDownloadStatus(updateDescriptor.getUrl(), digest);
|
|
|
|
|
2018-08-02 13:25:33 +00:00
|
|
|
Log.i(TAG, "Download status: " + downloadStatus.getStatus());
|
2017-02-26 22:36:43 +00:00
|
|
|
|
|
|
|
if (downloadStatus.getStatus() == DownloadStatus.Status.COMPLETE) {
|
2018-08-02 13:25:33 +00:00
|
|
|
Log.i(TAG, "Download status complete, notifying...");
|
2017-02-26 22:36:43 +00:00
|
|
|
handleDownloadNotify(downloadStatus.getDownloadId());
|
|
|
|
} else if (downloadStatus.getStatus() == DownloadStatus.Status.MISSING) {
|
2018-08-02 13:25:33 +00:00
|
|
|
Log.i(TAG, "Download status missing, starting download...");
|
2017-02-26 22:36:43 +00:00
|
|
|
handleDownloadStart(updateDescriptor.getUrl(), updateDescriptor.getVersionName(), digest);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-05-22 16:51:56 +00:00
|
|
|
public boolean onShouldRetry(@NonNull Exception e) {
|
2017-02-26 22:36:43 +00:00
|
|
|
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");
|
2019-03-20 22:09:27 +00:00
|
|
|
downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
|
2017-02-26 22:36:43 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-05-22 16:51:56 +00:00
|
|
|
public @NonNull String toString() {
|
2017-02-26 22:36:43 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2019-03-28 15:56:35 +00:00
|
|
|
|
|
|
|
public static final class Factory implements Job.Factory<UpdateApkJob> {
|
|
|
|
@Override
|
|
|
|
public @NonNull UpdateApkJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
|
|
|
return new UpdateApkJob(parameters);
|
|
|
|
}
|
|
|
|
}
|
2017-02-26 22:36:43 +00:00
|
|
|
}
|