package com.winsontan520.wversionmanager.library; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.PowerManager; import android.preference.PreferenceManager; import android.support.v4.content.FileProvider; import android.util.Log; import android.widget.Toast; import com.ov3rk1ll.kinocast.BuildConfig; import com.ov3rk1ll.kinocast.R; import org.json.JSONObject; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Calendar; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class WVersionManager { private static final String TAG = "WVersionManager"; private static final int MODE_CHECK_VERSION = 100; private static final int MODE_ASK_FOR_RATE = 200; private CustomTagHandler customTagHandler; private String PREF_IGNORE_VERSION_CODE = "w.ignore.version.code"; private String PREF_REMINDER_TIME = "w.reminder.time"; private Activity activity; private Drawable icon; private String title; private String message; private String updateNowLabel; private String remindMeLaterLabel; private String ignoreThisVersionLabel; private String updateUrl; private String versionContentUrl; private int reminderTimer; private int versionCode; private AlertDialogButtonListener listener; private boolean mDialogCancelable = true; private boolean mIsAskForRate = false; private String mAskForRatePositiveLabel; private String mAskForRateNegativeLabel; private int mMode = 100; // default mode ProgressDialog mProgressDialog; private boolean mToastIfUpToDate = false; public WVersionManager(Activity act){ this.activity = act; this.listener = new AlertDialogButtonListener(); this.customTagHandler = new CustomTagHandler(); } private Drawable getDefaultAppIcon() { Drawable d = activity.getApplicationInfo().loadIcon(activity.getPackageManager()); return d; } public void setShowToastIfUpToDate(boolean toastIfUpToDate){ this.mToastIfUpToDate = toastIfUpToDate; } public void checkVersion() { checkVersion(false); } public void checkVersion(boolean forceCheck) { mMode = MODE_CHECK_VERSION; String versionContentUrl = getVersionContentUrl(); if(versionContentUrl == null){ Log.e(TAG, "Please set versionContentUrl first"); return; } Calendar c = Calendar.getInstance(); long currentTimeStamp = c.getTimeInMillis(); long reminderTimeStamp = getReminderTime(); if(BuildConfig.DEBUG){ Log.v(TAG, "currentTimeStamp="+currentTimeStamp); Log.v(TAG, "reminderTimeStamp="+reminderTimeStamp); } if(currentTimeStamp > reminderTimeStamp || forceCheck){ // fire request to get update version content if(BuildConfig.DEBUG){ Log.v(TAG, "getting update content..."); } VersionContentRequest request = new VersionContentRequest(activity, mToastIfUpToDate); request.execute(getVersionContentUrl()); } } private void showDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setIcon(getIcon()); builder.setTitle(getTitle()); builder.setMessage(getMessage()); switch (mMode) { case MODE_CHECK_VERSION: builder.setPositiveButton(getUpdateNowLabel(), listener); builder.setNeutralButton(getRemindMeLaterLabel(), listener); //builder.setNegativeButton(getIgnoreThisVersionLabel(), listener); break; case MODE_ASK_FOR_RATE: builder.setPositiveButton(getAskForRatePositiveLabel(), listener); builder.setNegativeButton(getAskForRateNegativeLabel(), listener); break; default: return; } builder.setCancelable(isDialogCancelable()); AlertDialog dialog = builder.create(); if(activity != null && !activity.isFinishing()){ dialog.show(); } } public String getUpdateNowLabel() { return updateNowLabel != null? updateNowLabel : "Update now"; } public void setUpdateNowLabel(String updateNowLabel) { this.updateNowLabel = updateNowLabel; } public String getRemindMeLaterLabel() { return remindMeLaterLabel != null? remindMeLaterLabel : "Maybe later"; } public void setRemindMeLaterLabel(String remindMeLaterLabel) { this.remindMeLaterLabel = remindMeLaterLabel; } public String getIgnoreThisVersionLabel() { return ignoreThisVersionLabel != null? ignoreThisVersionLabel : "Ignore this version"; } public void setIgnoreThisVersionLabel(String ignoreThisVersionLabel) { this.ignoreThisVersionLabel = ignoreThisVersionLabel; } public void setIcon(Drawable icon) { this.icon = icon; } public void setTitle(String title) { this.title = title; } public void setMessage(String message) { this.message = message; } public String getMessage() { String defaultMessage = null; switch(mMode){ case MODE_CHECK_VERSION: defaultMessage = "What's new in this version"; break; case MODE_ASK_FOR_RATE: defaultMessage = "Please rate us!"; break; } return message != null? message : defaultMessage; } public String getTitle() { String defaultTitle = null; switch(mMode){ case MODE_CHECK_VERSION: defaultTitle = "New Update Available"; break; case MODE_ASK_FOR_RATE: defaultTitle = "Rate this app"; break; } return title != null? title : defaultTitle; } public Drawable getIcon() { return icon != null? icon : getDefaultAppIcon(); } public String getUpdateUrl() { return updateUrl != null ? updateUrl : getGooglePlayStoreUrl(); } public void setUpdateUrl(String updateUrl) { this.updateUrl = updateUrl; } public String getVersionContentUrl() { return versionContentUrl; } public void setVersionContentUrl(String versionContentUrl) { this.versionContentUrl = versionContentUrl; } public int getReminderTimer() { return reminderTimer > 0 ? reminderTimer : (1 * 60); // default value 60 minutes } public void setReminderTimer(int minutes){ if(minutes > 0){ reminderTimer = minutes; } } //TODO Download Dialog and open when ready private void updateNow(String url) { if(url != null){ try{ mProgressDialog = new ProgressDialog(this.activity); mProgressDialog.setMessage("Download Update"); mProgressDialog.setIndeterminate(true); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mProgressDialog.setCancelable(true); // execute this when the downloader must be fired final DownloadTask downloadTask = new DownloadTask(this.activity); downloadTask.execute(url); mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { downloadTask.cancel(true); } }); /*Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); activity.startActivity(intent);*/ }catch(Exception e){ Log.e(TAG, "is update url correct?" + e ); } } } private void remindMeLater(int reminderTimer) { Calendar c = Calendar.getInstance(); long currentTimeStamp = c.getTimeInMillis(); c.add(Calendar.MINUTE, reminderTimer); long reminderTimeStamp = c.getTimeInMillis(); if(BuildConfig.DEBUG){ Log.v(TAG, "currentTimeStamp="+currentTimeStamp); Log.v(TAG, "reminderTimeStamp="+reminderTimeStamp); } setReminderTime(reminderTimeStamp); } private void setReminderTime(long reminderTimeStamp) { PreferenceManager.getDefaultSharedPreferences(activity).edit().putLong(PREF_REMINDER_TIME, reminderTimeStamp).commit(); } private long getReminderTime(){ return PreferenceManager.getDefaultSharedPreferences(activity).getLong(PREF_REMINDER_TIME, 0); } private void ignoreThisVersion() { PreferenceManager.getDefaultSharedPreferences(activity).edit().putInt(PREF_IGNORE_VERSION_CODE, versionCode).commit(); } private String getGooglePlayStoreUrl(){ String id = activity.getApplicationInfo().packageName; // current google play is using package name as id return "market://details?id=" + id; } private class AlertDialogButtonListener implements DialogInterface.OnClickListener{ @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case AlertDialog.BUTTON_POSITIVE: updateNow(getUpdateUrl()); break; case AlertDialog.BUTTON_NEUTRAL: remindMeLater(getReminderTimer()); break; case AlertDialog.BUTTON_NEGATIVE: ignoreThisVersion(); break; } } } class VersionInformation{ private int versionCode; private String name; private String body; private String downloadUrl; public VersionInformation(int versionCode, String name, String body, String downloadUrl) { this.versionCode = versionCode; this.name = name; this.body = body; this.downloadUrl = downloadUrl; } } class VersionContentRequest extends AsyncTask<String, Void, VersionInformation>{ Context context; int statusCode; boolean toastIfUpToDate; public VersionContentRequest(Context context, boolean toastIfUpToDate) { this.context = context; this.toastIfUpToDate = toastIfUpToDate; } @Override protected VersionInformation doInBackground(String... uri) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder().url(uri[0]).build(); Log.d(TAG, "GET " + uri[0]); try { Response response = client.newCall(request).execute(); JSONObject json = new JSONObject(response.body().string()); int currentVersionCode = getCurrentVersionCode(); int versionCode = json.optInt("version_code", 0); if(currentVersionCode < versionCode){ request = new Request.Builder().url(json.getString("gitlab")).build(); response = client.newCall(request).execute(); JSONObject gitlab = new JSONObject(response.body().string()); return new VersionInformation(versionCode, gitlab.getString("name"), gitlab.getString("body"), gitlab.getJSONArray("assets").getJSONObject(0).getString("browser_download_url") ); } }catch (Exception e) { Log.e(TAG, e.toString()); } return null; } @Override protected void onPostExecute(VersionInformation result) { if(result == null){ if(toastIfUpToDate){ Toast.makeText(activity, R.string.update_uptodate, Toast.LENGTH_SHORT).show(); } Log.e(TAG, "Response invalid"); }else{ setUpdateUrl(result.downloadUrl); setMessage(result.body); setTitle("Update " + result.name); showDialog(); } } } class DownloadTask extends AsyncTask<String, Integer, String> { private Context context; private PowerManager.WakeLock mWakeLock; private File localFile; public DownloadTask(Context context) { this.context = context; } @Override protected String doInBackground(String... sUrl) { InputStream input = null; OutputStream output = null; HttpURLConnection connection = null; try { URL url = new URL(sUrl[0]); connection = (HttpURLConnection) url.openConnection(); connection.connect(); // expect HTTP 200 OK, so we don't mistakenly save error report // instead of the file if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { return "Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage(); } // this will be useful to display download percentage // might be -1: server did not report the length int fileLength = connection.getContentLength(); // download the file context.getExternalFilesDir(null).mkdirs(); String file = url.getFile(); localFile = new File(context.getExternalFilesDir(null), file.substring(file.lastIndexOf("/") + 1)); if(localFile.exists()) localFile.delete(); input = connection.getInputStream(); output = new FileOutputStream(localFile); byte data[] = new byte[4096]; long total = 0; int count; while ((count = input.read(data)) != -1) { // allow canceling with back button if (isCancelled()) { input.close(); return null; } total += count; // publishing the progress.... if (fileLength > 0) // only if total length is known publishProgress((int) (total * 100 / fileLength)); output.write(data, 0, count); } } catch (Exception e) { e.printStackTrace(); return e.toString(); } finally { try { if (output != null) output.close(); if (input != null) input.close(); } catch (IOException ignored) { } if (connection != null) connection.disconnect(); } return null; } @Override protected void onPreExecute() { super.onPreExecute(); // take CPU lock to prevent CPU from going off if the user // presses the power button during download PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); mWakeLock.acquire(); mProgressDialog.show(); } @Override protected void onProgressUpdate(Integer... progress) { super.onProgressUpdate(progress); // if we get here, length is known, now set indeterminate to false mProgressDialog.setIndeterminate(false); mProgressDialog.setMax(100); mProgressDialog.setProgress(progress[0]); } @Override protected void onPostExecute(String result) { mWakeLock.release(); mProgressDialog.dismiss(); if (result != null) Toast.makeText(context, "Download error: " + result, Toast.LENGTH_LONG).show(); else Toast.makeText(context, "File downloaded", Toast.LENGTH_SHORT).show(); //Open file Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", localFile); Log.i(TAG, "onPostExecute: open " + uri); // Uri.fromFile(localFile) intent.setDataAndType(uri, "application/vnd.android.package-archive"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); context.startActivity(intent); } } public int getCurrentVersionCode() { return BuildConfig.VERSION_CODE; } public int getIgnoreVersionCode() { return PreferenceManager.getDefaultSharedPreferences(activity).getInt(PREF_IGNORE_VERSION_CODE, 1); } public CustomTagHandler getCustomTagHandler() { return customTagHandler; } public void setCustomTagHandler(CustomTagHandler customTagHandler) { this.customTagHandler = customTagHandler; } public boolean isDialogCancelable() { return mDialogCancelable; } public void setDialogCancelable(boolean dialogCancelable) { mDialogCancelable = dialogCancelable; } public void askForRate(){ mMode = MODE_ASK_FOR_RATE; showDialog(); } public String getAskForRatePositiveLabel() { return mAskForRatePositiveLabel == null? "OK":mAskForRatePositiveLabel; } public void setAskForRatePositiveLabel(String askForRatePositiveLabel) { mAskForRatePositiveLabel = askForRatePositiveLabel; } public String getAskForRateNegativeLabel() { return mAskForRateNegativeLabel == null? "Not now":mAskForRateNegativeLabel; } public void setAskForRateNegativeLabel(String askForRateNegativeLabel) { mAskForRateNegativeLabel = askForRateNegativeLabel; } }