package me.devsaki.hentoid.updater;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.text.format.Formatter;
import android.widget.RemoteViews;
import com.thin.downloadmanager.DownloadManager;
import com.thin.downloadmanager.DownloadRequest;
import com.thin.downloadmanager.DownloadStatusListenerV1;
import com.thin.downloadmanager.ThinDownloadManager;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import me.devsaki.hentoid.BuildConfig;
import me.devsaki.hentoid.HentoidApp;
import me.devsaki.hentoid.R;
import me.devsaki.hentoid.util.Helper;
import me.devsaki.hentoid.util.JsonHelper;
import me.devsaki.hentoid.util.LogHelper;
import me.devsaki.hentoid.util.NetworkStatus;
/**
* Created by avluis on 8/21/15.
* Takes care of notifying and download of app updates.
*/
public class UpdateCheck {
static final String ACTION_DOWNLOAD_UPDATE =
"me.devsaki.hentoid.updater.DOWNLOAD_UPDATE";
static final String ACTION_NOTIFICATION_REMOVED =
"me.devsaki.hentoid.updater.NOTIFICATION_REMOVED";
static final String ACTION_DOWNLOAD_CANCELLED =
"me.devsaki.hentoid.updater.DOWNLOAD_CANCELLED";
static final String ACTION_INSTALL_UPDATE =
"me.devsaki.hentoid.updater.INSTALL_UPDATE";
private static final String TAG = LogHelper.makeLogTag(UpdateCheck.class);
private static final String KEY_VERSION_CODE = "versionCode";
private static final String KEY_UPDATED_URL = "updateURL";
@SuppressLint("StaticFieldLeak")
private static UpdateCheck instance;
private final int NOTIFICATION_ID = 4368643;
private final UpdateNotificationRunnable updateNotificationRunnable =
new UpdateNotificationRunnable();
private NotificationCompat.Builder builder;
private NotificationManager notifManager;
private RemoteViews notif;
private int downloadID = -1;
private DownloadManager downloadManager;
private String updateDownloadPath;
private Context cxt;
private UpdateCheckCallback updateCheckResult;
private Handler mHandler;
private String downloadURL;
private int progressBar;
private long total;
private long done;
private boolean showToast;
private int retryCount = 0;
public static UpdateCheck getInstance() {
if (instance == null) {
instance = new UpdateCheck();
}
return instance;
}
public void checkForUpdate(@NonNull Context context, final boolean onlyWifi,
final boolean showToast, final UpdateCheckCallback callback) {
this.cxt = context;
this.updateCheckResult = callback;
mHandler = new Handler(context.getMainLooper());
if ((onlyWifi && NetworkStatus.isWifi(context)) ||
(!onlyWifi && NetworkStatus.isOnline(context))) {
checkNetworkConnectivity();
} else {
LogHelper.w(TAG, "Network is not connected!");
}
this.showToast = showToast;
}
private void checkNetworkConnectivity() {
AsyncTask.execute(() -> {
boolean connected = NetworkStatus.hasInternetAccess(HentoidApp.getAppContext());
if (connected) {
runAsyncTask(retryCount != 0);
}
});
}
private void runAsyncTask(boolean retry) {
String updateURL = BuildConfig.UPDATE_URL;
if (retry) {
retryCount++;
LogHelper.d(TAG, "Retrying! Count: " + retryCount);
}
LogHelper.d(TAG, "Update URL: " + updateURL);
if (Helper.isAtLeastAPI(Build.VERSION_CODES.HONEYCOMB)) {
new UpdateCheckTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, updateURL);
} else {
new UpdateCheckTask().execute(updateURL);
}
}
private void updateAvailableNotification(String updateURL) {
downloadURL = updateURL;
Intent installUpdate = new Intent(ACTION_DOWNLOAD_UPDATE);
installUpdate.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent updateIntent = PendingIntent.getBroadcast(cxt, 0, installUpdate, 0);
notif = new RemoteViews(cxt.getPackageName(),
R.layout.notification_update_available);
notifManager = (NotificationManager)
cxt.getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat
.Builder(cxt)
.setSmallIcon(R.drawable.ic_stat_hentoid)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVibrate(new long[]{1, 1, 1})
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setTicker(cxt.getString(R.string.update_available))
.setContent(notif);
notif.setTextViewText(R.id.tv_update_summary,
cxt.getString(R.string.download_update));
notif.setOnClickPendingIntent(R.id.rl_notify_root, updateIntent);
notifManager.notify(NOTIFICATION_ID, builder.build());
if (downloadManager != null) {
try {
mHandler.post(updateNotificationRunnable);
} catch (Exception e) {
e.printStackTrace();
}
}
}
void downloadingUpdateNotification() {
Intent stopIntent = new Intent(ACTION_DOWNLOAD_CANCELLED);
stopIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent cancelIntent = PendingIntent.getBroadcast(cxt, 0, stopIntent, 0);
Intent clearIntent = new Intent(ACTION_NOTIFICATION_REMOVED);
clearIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent removeIntent = PendingIntent.getBroadcast(cxt, 0, clearIntent, 0);
notif = new RemoteViews(cxt.getPackageName(),
R.layout.notification_update);
notifManager = (NotificationManager)
cxt.getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat
.Builder(cxt)
.setSmallIcon(R.drawable.ic_stat_hentoid)
.setTicker(cxt.getString(R.string.downloading_update))
.setAutoCancel(false)
.setOngoing(true)
.setContent(notif)
.setDeleteIntent(removeIntent);
notif.setProgressBar(R.id.pb_notification, 100, 0, true);
notif.setTextViewText(R.id.tv_update_title, cxt.getString(R.string.downloading_update));
notif.setTextViewText(R.id.tv_update_summary, "");
notif.setOnClickPendingIntent(R.id.bt_cancel, cancelIntent);
notifManager.notify(NOTIFICATION_ID, builder.build());
}
private void updateDownloadedNotification() {
Intent installUpdate = new Intent(ACTION_INSTALL_UPDATE);
installUpdate.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent installIntent = PendingIntent.getBroadcast(cxt, 0, installUpdate, 0);
Intent clearIntent = new Intent(ACTION_NOTIFICATION_REMOVED);
clearIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent removeIntent = PendingIntent.getBroadcast(cxt, 0, clearIntent, 0);
notif = new RemoteViews(cxt.getPackageName(),
R.layout.notification_update_available);
notifManager = (NotificationManager)
cxt.getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat
.Builder(cxt)
.setSmallIcon(R.drawable.ic_stat_hentoid)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVibrate(new long[]{1, 1, 1})
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setDeleteIntent(removeIntent)
.setTicker(cxt.getString(R.string.install_update))
.setContent(notif);
notif.setTextViewText(R.id.tv_update_summary, cxt.getString(R.string.install_update));
notif.setOnClickPendingIntent(R.id.rl_notify_root, installIntent);
notifManager.notify(NOTIFICATION_ID, builder.build());
}
void installUpdate() {
Intent intent;
if (Helper.isAtLeastAPI(Build.VERSION_CODES.JELLY_BEAN)) {
intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
} else {
intent = new Intent(Intent.ACTION_VIEW);
}
intent.setDataAndType(Uri.parse("file://" + updateDownloadPath),
"application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
cxt.startActivity(intent);
}
void downloadUpdate() {
if (downloadURL != null) {
if (downloadID != -1) {
cancelDownload();
}
Uri downloadUri = Uri.parse(downloadURL);
Uri destinationUri = Uri.parse(updateDownloadPath = cxt.getExternalCacheDir() +
"/hentoid_update.apk");
DownloadRequest downloadRequest = new DownloadRequest(downloadUri)
.setDestinationURI(destinationUri)
.setPriority(DownloadRequest.Priority.HIGH)
.setDeleteDestinationFileOnFailure(false)
.setStatusListener(new DownloadStatusListenerV1() {
private boolean posted;
@Override
public void onDownloadComplete(DownloadRequest request) {
cancelRunnable();
updateDownloadedNotification();
}
@Override
public void onDownloadFailed(DownloadRequest request, int errorCode,
String errorMessage) {
cancelNotificationAndUpdateRunnable();
if (errorCode == DownloadManager.ERROR_UNHANDLED_HTTP_CODE) {
if ("Unhandled HTTP response:404 message:Not Found"
.equals(errorMessage)) {
try {
notif.setProgressBar(R.id.pb_notification, 100, 0, true);
notif.setTextViewText(R.id.tv_update_title,
cxt.getString(R.string.error_network_summary));
notif.setTextViewText(R.id.tv_update_summary,
cxt.getString(R.string.error_file));
notifManager.notify(NOTIFICATION_ID, builder.build());
} catch (Exception e) {
LogHelper.e(TAG, e, "Error");
}
} else {
try {
notif.setProgressBar(R.id.pb_notification, 100, 0, true);
notif.setTextViewText(R.id.tv_update_title,
cxt.getString(R.string.error_network_summary));
notif.setTextViewText(R.id.tv_update_summary,
cxt.getString(R.string.error_file));
notifManager.notify(NOTIFICATION_ID, builder.build());
} catch (Exception e) {
LogHelper.e(TAG, e, "Error");
}
}
} else {
LogHelper.d(TAG, "Error Code: " + errorCode + ". Error Message: " +
errorMessage);
}
downloadManager = null;
}
@Override
public void onProgress(DownloadRequest request, long totalBytes,
long downloadedBytes, int progress) {
progressBar = progress;
total = totalBytes;
done = downloadedBytes;
if (!posted) {
mHandler.post(updateNotificationRunnable);
posted = true;
}
}
});
downloadManager = new ThinDownloadManager();
downloadID = downloadManager.add(downloadRequest);
LogHelper.d(TAG, "DownloadID: " + downloadID);
} else {
this.cancelNotification();
}
}
void cancelNotificationAndUpdateRunnable() {
cancelNotification();
try {
mHandler.removeCallbacks(updateNotificationRunnable);
} catch (Exception e) {
e.printStackTrace();
}
}
private void cancelRunnable() {
try {
mHandler.removeCallbacks(updateNotificationRunnable);
} catch (Exception e) {
e.printStackTrace();
}
}
private void cancelNotification() {
try {
notifManager.cancel(NOTIFICATION_ID);
} catch (Exception e) {
e.printStackTrace();
}
}
void cancelDownload() {
if (downloadManager != null) {
try {
downloadManager.cancel(downloadID);
downloadManager.release();
} catch (Exception e) {
LogHelper.e(TAG, e, "Error while cancelling download");
}
}
cancelNotificationAndUpdateRunnable();
}
public interface UpdateCheckCallback {
void noUpdateAvailable();
void onUpdateAvailable();
}
private class UpdateCheckTask extends AsyncTask<String, Void, Void> {
@Override
protected Void doInBackground(String... params) {
try {
JSONObject jsonObject = new JsonHelper().jsonReader(params[0]);
if (jsonObject != null) {
int updateVersionCode = jsonObject.getInt(KEY_VERSION_CODE);
if (Helper.getAppVersionCode(cxt) < updateVersionCode) {
if (updateCheckResult != null) {
updateCheckResult.onUpdateAvailable();
}
downloadURL = jsonObject.getString(KEY_UPDATED_URL);
updateAvailableNotification(downloadURL);
} else {
if (updateCheckResult != null) {
updateCheckResult.noUpdateAvailable();
if (showToast) {
mHandler.post(() -> Helper.toast(cxt,
R.string.update_check_no_update));
}
}
}
} else {
if (showToast) {
mHandler.post(() -> Helper.toast(cxt, R.string.error_dependency));
}
}
} catch (IOException e) {
if (retryCount == 0) {
runAsyncTask(true);
} else {
LogHelper.e(TAG, e, "IO ERROR");
mHandler.post(() -> Helper.toast(cxt, R.string.error_dependency));
}
} catch (JSONException e) {
LogHelper.e(TAG, e, "Error with JSON File");
mHandler.post(() -> Helper.toast(cxt, R.string.error_dependency));
} catch (PackageManager.NameNotFoundException e) {
LogHelper.e(TAG, e, "Package Name NOT Found");
}
return null;
}
}
private class UpdateNotificationRunnable implements Runnable {
@Override
public void run() {
notif.setProgressBar(R.id.pb_notification, 100, progressBar, false);
notif.setTextViewText(R.id.tv_update_summary,
"(" + Formatter.formatShortFileSize(cxt, done) + "/"
+ Formatter.formatShortFileSize(cxt, total) + ") "
+ progressBar
+ "%");
notifManager.notify(NOTIFICATION_ID, builder.build());
mHandler.postDelayed(this, 1000);
}
}
}