mirror of https://github.com/kurisufriend/Clover
The update checker loads a json file from the github site and checks if there are newer versions available. If there are, allow the user to download and install the update with just a few clicks.multisite^2
parent
4b63aacf98
commit
5fbce3e31d
@ -0,0 +1,162 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.net; |
||||
|
||||
|
||||
import android.util.JsonReader; |
||||
|
||||
import com.android.volley.Response; |
||||
|
||||
import org.floens.chan.BuildConfig; |
||||
|
||||
import java.io.IOException; |
||||
import java.text.DateFormat; |
||||
import java.text.ParseException; |
||||
import java.text.SimpleDateFormat; |
||||
import java.util.ArrayList; |
||||
import java.util.Date; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
|
||||
import okhttp3.HttpUrl; |
||||
|
||||
public class UpdateApiRequest extends JsonReaderRequest<UpdateApiRequest.UpdateApiResponse> { |
||||
public static final String TYPE_UPDATE = "update"; |
||||
|
||||
private static final int API_VERSION = 1; |
||||
|
||||
private String forFlavor; |
||||
|
||||
public UpdateApiRequest(Response.Listener<UpdateApiResponse> listener, Response.ErrorListener errorListener) { |
||||
super(BuildConfig.UPDATE_API_ENDPOINT, listener, errorListener); |
||||
forFlavor = BuildConfig.FLAVOR; |
||||
} |
||||
|
||||
@Override |
||||
public UpdateApiResponse readJson(JsonReader reader) throws Exception { |
||||
reader.beginObject(); |
||||
|
||||
UpdateApiResponse response = new UpdateApiResponse(); |
||||
|
||||
int apiVersion; |
||||
out: |
||||
while (reader.hasNext()) { |
||||
switch (reader.nextName()) { |
||||
case "api_version": |
||||
apiVersion = reader.nextInt(); |
||||
|
||||
if (apiVersion > API_VERSION) { |
||||
response.newerApiVersion = true; |
||||
|
||||
while (reader.hasNext()) reader.skipValue(); |
||||
|
||||
break out; |
||||
} |
||||
|
||||
break; |
||||
case "messages": |
||||
reader.beginArray(); |
||||
while (reader.hasNext()) { |
||||
response.messages.add(readMessage(reader)); |
||||
} |
||||
reader.endArray(); |
||||
break; |
||||
case "check_interval": |
||||
response.checkIntervalMs = reader.nextLong(); |
||||
break; |
||||
default: |
||||
reader.skipValue(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
reader.endObject(); |
||||
|
||||
return response; |
||||
} |
||||
|
||||
private UpdateApiMessage readMessage(JsonReader reader) throws IOException { |
||||
reader.beginObject(); |
||||
|
||||
UpdateApiMessage message = new UpdateApiMessage(); |
||||
|
||||
while (reader.hasNext()) { |
||||
switch (reader.nextName()) { |
||||
case "type": |
||||
message.type = reader.nextString(); |
||||
break; |
||||
case "code": |
||||
message.code = reader.nextInt(); |
||||
break; |
||||
case "date": |
||||
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); |
||||
try { |
||||
message.date = format.parse(reader.nextString()); |
||||
} catch (ParseException ignore) { |
||||
} |
||||
break; |
||||
case "message_html": |
||||
message.messageHtml = reader.nextString(); |
||||
break; |
||||
case "apk": |
||||
reader.beginObject(); |
||||
while (reader.hasNext()) { |
||||
if (reader.nextName().equals(forFlavor)) { |
||||
reader.beginObject(); |
||||
while (reader.hasNext()) { |
||||
switch (reader.nextName()) { |
||||
case "url": |
||||
message.apkUrl = HttpUrl.parse(reader.nextString()); |
||||
break; |
||||
default: |
||||
reader.skipValue(); |
||||
break; |
||||
} |
||||
} |
||||
reader.endObject(); |
||||
} else { |
||||
reader.skipValue(); |
||||
} |
||||
} |
||||
reader.endObject(); |
||||
break; |
||||
default: |
||||
reader.skipValue(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
reader.endObject(); |
||||
|
||||
return message; |
||||
} |
||||
|
||||
public static class UpdateApiResponse { |
||||
public boolean newerApiVersion; |
||||
public List<UpdateApiMessage> messages = new ArrayList<>(); |
||||
public long checkIntervalMs; |
||||
} |
||||
|
||||
public static class UpdateApiMessage { |
||||
public String type; |
||||
public int code; |
||||
public Date date; |
||||
public String messageHtml; |
||||
public HttpUrl apkUrl; |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.settings; |
||||
|
||||
import android.content.SharedPreferences; |
||||
|
||||
public class LongSetting extends Setting<Long> { |
||||
private boolean hasCached = false; |
||||
private Long cached; |
||||
|
||||
public LongSetting(SharedPreferences sharedPreferences, String key, Long def) { |
||||
super(sharedPreferences, key, def); |
||||
} |
||||
|
||||
@Override |
||||
public Long get() { |
||||
if (hasCached) { |
||||
return cached; |
||||
} else { |
||||
cached = sharedPreferences.getLong(key, def); |
||||
hasCached = true; |
||||
return cached; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void set(Long value) { |
||||
if (!value.equals(get())) { |
||||
sharedPreferences.edit().putLong(key, value).apply(); |
||||
cached = value; |
||||
onValueChanged(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,238 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.update; |
||||
|
||||
|
||||
import android.content.Intent; |
||||
import android.net.Uri; |
||||
import android.os.Environment; |
||||
import android.os.StrictMode; |
||||
import android.text.TextUtils; |
||||
|
||||
import com.android.volley.Response; |
||||
import com.android.volley.VolleyError; |
||||
|
||||
import org.floens.chan.BuildConfig; |
||||
import org.floens.chan.Chan; |
||||
import org.floens.chan.core.cache.FileCache; |
||||
import org.floens.chan.core.net.UpdateApiRequest; |
||||
import org.floens.chan.core.settings.ChanSettings; |
||||
import org.floens.chan.utils.IOUtils; |
||||
import org.floens.chan.utils.Logger; |
||||
import org.floens.chan.utils.Time; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
|
||||
import okhttp3.HttpUrl; |
||||
|
||||
/** |
||||
* Calls the update API and downloads and requests installs of APK files. |
||||
* <p>The APK files are downloaded to the public Download directory, and the default APK install |
||||
* screen is launched after downloading. |
||||
*/ |
||||
public class UpdateManager { |
||||
public static final long DEFAULT_UPDATE_CHECK_INTERVAL_MS = 1000 * 60 * 60 * 24 * 5; // 5 days
|
||||
|
||||
private static final String TAG = "UpdateManager"; |
||||
|
||||
private static final String DOWNLOAD_FILE = "Clover_update.apk"; |
||||
|
||||
private UpdateCallback callback; |
||||
|
||||
public UpdateManager(UpdateCallback callback) { |
||||
this.callback = callback; |
||||
} |
||||
|
||||
public boolean isUpdatingAvailable() { |
||||
return !TextUtils.isEmpty(BuildConfig.UPDATE_API_ENDPOINT); |
||||
} |
||||
|
||||
public void runUpdateApi(final boolean manual) { |
||||
if (!manual) { |
||||
long lastUpdateTime = ChanSettings.updateCheckTime.get(); |
||||
long interval = ChanSettings.updateCheckInterval.get(); |
||||
long now = Time.get(); |
||||
long delta = (lastUpdateTime + interval) - now; |
||||
if (delta > 0) { |
||||
return; |
||||
} else { |
||||
ChanSettings.updateCheckTime.set(now); |
||||
} |
||||
} |
||||
|
||||
Logger.d(TAG, "Calling update API"); |
||||
Chan.getVolleyRequestQueue().add(new UpdateApiRequest(new Response.Listener<UpdateApiRequest.UpdateApiResponse>() { |
||||
@Override |
||||
public void onResponse(UpdateApiRequest.UpdateApiResponse response) { |
||||
if (!processUpdateApiResponse(response) && manual) { |
||||
callback.onManualCheckNone(); |
||||
} |
||||
} |
||||
}, new Response.ErrorListener() { |
||||
@Override |
||||
public void onErrorResponse(VolleyError error) { |
||||
Logger.e(TAG, "Failed to process API call for updating", error); |
||||
|
||||
if (manual) { |
||||
callback.onManualCheckFailed(); |
||||
} |
||||
} |
||||
})); |
||||
} |
||||
|
||||
private boolean processUpdateApiResponse(UpdateApiRequest.UpdateApiResponse response) { |
||||
if (response.newerApiVersion) { |
||||
Logger.e(TAG, "API endpoint reports a higher API version than we support, aborting update check."); |
||||
|
||||
// ignore
|
||||
return false; |
||||
} |
||||
|
||||
if (response.checkIntervalMs != 0) { |
||||
ChanSettings.updateCheckInterval.set(response.checkIntervalMs); |
||||
} |
||||
|
||||
for (UpdateApiRequest.UpdateApiMessage message : response.messages) { |
||||
if (processUpdateMessage(message)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
private boolean processUpdateMessage(UpdateApiRequest.UpdateApiMessage message) { |
||||
if (message.code <= BuildConfig.VERSION_CODE) { |
||||
Logger.d(TAG, "No newer version available (" + BuildConfig.VERSION_CODE + " >= " + message.code + ")."); |
||||
// Our code is newer than the message
|
||||
return false; |
||||
} |
||||
|
||||
if (message.type.equals(UpdateApiRequest.TYPE_UPDATE)) { |
||||
if (message.apkUrl == null) { |
||||
Logger.i(TAG, "Update available but none for this build flavor."); |
||||
// Not for this flavor, discard.
|
||||
return false; |
||||
} |
||||
|
||||
Logger.i(TAG, "Update available (" + message.code + ") with url \"" + message.apkUrl + "\"."); |
||||
callback.showUpdateAvailableDialog(message); |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Install the APK file specified in {@code update}. This methods needs the storage permission. |
||||
* |
||||
* @param update update with apk details. |
||||
*/ |
||||
public void doUpdate(Update update) { |
||||
Chan.getFileCache().downloadFile(update.apkUrl.toString(), new FileCache.DownloadedCallback() { |
||||
@Override |
||||
public void onProgress(long downloaded, long total, boolean done) { |
||||
if (!done) callback.onUpdateDownloadProgress(downloaded, total); |
||||
} |
||||
|
||||
@Override |
||||
public void onSuccess(File file) { |
||||
callback.onUpdateDownloadSuccess(); |
||||
copyToPublicDirectory(file); |
||||
} |
||||
|
||||
@Override |
||||
public void onFail(boolean notFound) { |
||||
callback.onUpdateDownloadFailed(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public void retry(Install install) { |
||||
installApk(install); |
||||
} |
||||
|
||||
private void copyToPublicDirectory(File cacheFile) { |
||||
File out = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), DOWNLOAD_FILE); |
||||
try { |
||||
IOUtils.copyFile(cacheFile, out); |
||||
} catch (IOException e) { |
||||
Logger.e(TAG, "requestApkInstall", e); |
||||
callback.onUpdateDownloadMoveFailed(); |
||||
return; |
||||
} |
||||
installApk(new Install(out)); |
||||
} |
||||
|
||||
private void installApk(Install install) { |
||||
// First open the dialog that asks to retry and calls this method again.
|
||||
callback.openUpdateRetryDialog(install); |
||||
|
||||
// Then launch the APK install intent.
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW); |
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); |
||||
intent.setDataAndType(Uri.fromFile(install.installFile), "application/vnd.android.package-archive"); |
||||
|
||||
// The installer wants a content scheme from android N and up,
|
||||
// but I don't feel like implementing a content provider just for this feature.
|
||||
// Temporary change the strictmode policy while starting the intent.
|
||||
StrictMode.VmPolicy vmPolicy = StrictMode.getVmPolicy(); |
||||
StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX); |
||||
|
||||
callback.onUpdateOpenInstallScreen(intent); |
||||
|
||||
StrictMode.setVmPolicy(vmPolicy); |
||||
} |
||||
|
||||
public static class Update { |
||||
private HttpUrl apkUrl; |
||||
|
||||
public Update(HttpUrl apkUrl) { |
||||
this.apkUrl = apkUrl; |
||||
} |
||||
} |
||||
|
||||
public static class Install { |
||||
private File installFile; |
||||
|
||||
public Install(File installFile) { |
||||
this.installFile = installFile; |
||||
} |
||||
} |
||||
|
||||
public interface UpdateCallback { |
||||
void onManualCheckNone(); |
||||
|
||||
void onManualCheckFailed(); |
||||
|
||||
void showUpdateAvailableDialog(UpdateApiRequest.UpdateApiMessage message); |
||||
|
||||
void onUpdateDownloadProgress(long downloaded, long total); |
||||
|
||||
void onUpdateDownloadSuccess(); |
||||
|
||||
void onUpdateDownloadFailed(); |
||||
|
||||
void onUpdateDownloadMoveFailed(); |
||||
|
||||
void onUpdateOpenInstallScreen(Intent intent); |
||||
|
||||
void openUpdateRetryDialog(Install install); |
||||
} |
||||
} |
@ -1,118 +0,0 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.helper; |
||||
|
||||
import android.content.Context; |
||||
import android.content.DialogInterface; |
||||
import android.support.v7.app.AlertDialog; |
||||
import android.text.Html; |
||||
import android.widget.Button; |
||||
|
||||
import org.floens.chan.R; |
||||
import org.floens.chan.core.settings.ChanSettings; |
||||
import org.floens.chan.utils.AndroidUtils; |
||||
import org.floens.chan.utils.Logger; |
||||
|
||||
import java.io.File; |
||||
|
||||
public class PreviousVersionHandler { |
||||
private static final String TAG = "PreviousVersionHandler"; |
||||
|
||||
/* |
||||
* Manifest version code, manifest version name, this version mapping: |
||||
* |
||||
* 28 = v1.1.2 |
||||
* 32 = v1.1.3 |
||||
* 36 = v1.2.0 |
||||
* 39 = v1.2.1 |
||||
* 40 = v1.2.2 |
||||
* 41 = v1.2.3 |
||||
* 42 = v1.2.4 |
||||
* 43 = v1.2.5 |
||||
* 44 = v1.2.6 |
||||
* 46 = v1.2.7 |
||||
* 47 = v1.2.8 |
||||
* 48 = v1.2.9 |
||||
* 49 = v1.2.10 |
||||
* 50 = v1.2.11 |
||||
* 51 = v2.0.0 = 1 |
||||
* 52 = v2.1.0 = 2 |
||||
* 53 = v2.1.1 = 2 |
||||
* 54 = v2.1.2 = 2 |
||||
* 55 = v2.1.3 = 2 |
||||
* 56 = v2.2.0 = 3 |
||||
*/ |
||||
private static final int CURRENT_VERSION = 3; |
||||
|
||||
public void run(Context context) { |
||||
int previous = ChanSettings.previousVersion.get(); |
||||
if (previous < CURRENT_VERSION) { |
||||
if (previous < 1) { |
||||
cleanupOutdatedIonFolder(context); |
||||
} |
||||
|
||||
// Add more previous version checks here
|
||||
|
||||
showMessage(context, CURRENT_VERSION); |
||||
|
||||
ChanSettings.previousVersion.set(CURRENT_VERSION); |
||||
} |
||||
} |
||||
|
||||
private void showMessage(Context context, int version) { |
||||
int resource = context.getResources().getIdentifier("previous_version_" + version, "string", context.getPackageName()); |
||||
if (resource != 0) { |
||||
CharSequence message = Html.fromHtml(context.getString(resource)); |
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context) |
||||
.setMessage(message) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.create(); |
||||
dialog.show(); |
||||
dialog.setCanceledOnTouchOutside(false); |
||||
|
||||
final Button button = dialog.getButton(DialogInterface.BUTTON_POSITIVE); |
||||
button.setEnabled(false); |
||||
AndroidUtils.runOnUiThread(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
dialog.setCanceledOnTouchOutside(true); |
||||
button.setEnabled(true); |
||||
} |
||||
}, 1500); |
||||
} |
||||
} |
||||
|
||||
private void cleanupOutdatedIonFolder(Context context) { |
||||
Logger.i(TAG, "Cleaning up old ion folder"); |
||||
File ionCacheFolder = new File(context.getCacheDir() + "/ion"); |
||||
if (ionCacheFolder.exists() && ionCacheFolder.isDirectory()) { |
||||
Logger.i(TAG, "Clearing old ion folder"); |
||||
for (File file : ionCacheFolder.listFiles()) { |
||||
if (!file.delete()) { |
||||
Logger.i(TAG, "Could not delete old ion file " + file.getName()); |
||||
} |
||||
} |
||||
if (!ionCacheFolder.delete()) { |
||||
Logger.i(TAG, "Could not delete old ion folder"); |
||||
} else { |
||||
Logger.i(TAG, "Deleted old ion folder"); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,282 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.helper; |
||||
|
||||
import android.Manifest; |
||||
import android.app.ProgressDialog; |
||||
import android.content.Context; |
||||
import android.content.DialogInterface; |
||||
import android.content.Intent; |
||||
import android.support.v7.app.AlertDialog; |
||||
import android.text.Html; |
||||
import android.text.Spanned; |
||||
import android.widget.Button; |
||||
|
||||
import org.floens.chan.R; |
||||
import org.floens.chan.core.net.UpdateApiRequest; |
||||
import org.floens.chan.core.settings.ChanSettings; |
||||
import org.floens.chan.core.update.UpdateManager; |
||||
import org.floens.chan.utils.AndroidUtils; |
||||
import org.floens.chan.utils.Logger; |
||||
|
||||
import java.io.File; |
||||
|
||||
public class VersionHandler implements UpdateManager.UpdateCallback { |
||||
private static final String TAG = "VersionHandler"; |
||||
|
||||
/* |
||||
* Manifest version code, manifest version name, this version mapping: |
||||
* |
||||
* 28 = v1.1.2 |
||||
* 32 = v1.1.3 |
||||
* 36 = v1.2.0 |
||||
* 39 = v1.2.1 |
||||
* 40 = v1.2.2 |
||||
* 41 = v1.2.3 |
||||
* 42 = v1.2.4 |
||||
* 43 = v1.2.5 |
||||
* 44 = v1.2.6 |
||||
* 46 = v1.2.7 |
||||
* 47 = v1.2.8 |
||||
* 48 = v1.2.9 |
||||
* 49 = v1.2.10 |
||||
* 50 = v1.2.11 |
||||
* 51 = v2.0.0 = 1 |
||||
* 52 = v2.1.0 = 2 |
||||
* 53 = v2.1.1 = 2 |
||||
* 54 = v2.1.2 = 2 |
||||
* 55 = v2.1.3 = 2 |
||||
* 56 = v2.2.0 = 3 |
||||
*/ |
||||
private static final int CURRENT_VERSION = 3; |
||||
|
||||
/** |
||||
* Context to show dialogs to. |
||||
*/ |
||||
private Context context; |
||||
private RuntimePermissionsHelper runtimePermissionsHelper; |
||||
|
||||
private UpdateManager updateManager; |
||||
|
||||
private ProgressDialog updateDownloadDialog; |
||||
|
||||
public VersionHandler(Context context, RuntimePermissionsHelper runtimePermissionsHelper) { |
||||
this.context = context; |
||||
this.runtimePermissionsHelper = runtimePermissionsHelper; |
||||
|
||||
updateManager = new UpdateManager(this); |
||||
} |
||||
|
||||
/** |
||||
* Runs every time onCreate is called on the StartActivity. |
||||
*/ |
||||
public void run() { |
||||
int previous = ChanSettings.previousVersion.get(); |
||||
if (previous < CURRENT_VERSION) { |
||||
if (previous < 1) { |
||||
cleanupOutdatedIonFolder(context); |
||||
} |
||||
|
||||
// Add more previous version checks here
|
||||
|
||||
showMessage(CURRENT_VERSION); |
||||
|
||||
ChanSettings.previousVersion.set(CURRENT_VERSION); |
||||
|
||||
// Don't process the updater because a dialog is now already showing.
|
||||
return; |
||||
} |
||||
|
||||
if (updateManager.isUpdatingAvailable()) { |
||||
updateManager.runUpdateApi(false); |
||||
} |
||||
} |
||||
|
||||
public boolean isUpdatingAvailable() { |
||||
return updateManager.isUpdatingAvailable(); |
||||
} |
||||
|
||||
public void manualUpdateCheck() { |
||||
updateManager.runUpdateApi(true); |
||||
} |
||||
|
||||
@Override |
||||
public void onManualCheckNone() { |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_none) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onManualCheckFailed() { |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_check_failed) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void showUpdateAvailableDialog(final UpdateApiRequest.UpdateApiMessage message) { |
||||
Spanned text = Html.fromHtml(message.messageHtml); |
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context) |
||||
.setMessage(text) |
||||
.setNegativeButton(R.string.update_later, new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int which) { |
||||
updatePostponed(message); |
||||
} |
||||
}) |
||||
.setPositiveButton(R.string.update_install, new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int which) { |
||||
updateInstallRequested(message); |
||||
} |
||||
}) |
||||
.create(); |
||||
dialog.show(); |
||||
dialog.setCanceledOnTouchOutside(false); |
||||
} |
||||
|
||||
private void updatePostponed(UpdateApiRequest.UpdateApiMessage message) { |
||||
} |
||||
|
||||
private void updateInstallRequested(final UpdateApiRequest.UpdateApiMessage message) { |
||||
runtimePermissionsHelper.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, new RuntimePermissionsHelper.Callback() { |
||||
@Override |
||||
public void onRuntimePermissionResult(boolean granted) { |
||||
if (granted) { |
||||
createDownloadProgressDialog(); |
||||
updateManager.doUpdate(new UpdateManager.Update(message.apkUrl)); |
||||
} else { |
||||
runtimePermissionsHelper.showPermissionRequiredDialog(context, |
||||
context.getString(R.string.update_storage_permission_required_title), |
||||
context.getString(R.string.update_storage_permission_required), |
||||
new RuntimePermissionsHelper.PermissionRequiredDialogCallback() { |
||||
@Override |
||||
public void retryPermissionRequest() { |
||||
updateInstallRequested(message); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void createDownloadProgressDialog() { |
||||
updateDownloadDialog = new ProgressDialog(context); |
||||
updateDownloadDialog.setCancelable(false); |
||||
updateDownloadDialog.setTitle(R.string.update_install_downloading); |
||||
updateDownloadDialog.setMax(10000); |
||||
updateDownloadDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); |
||||
updateDownloadDialog.setProgressNumberFormat(""); |
||||
updateDownloadDialog.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateDownloadProgress(long downloaded, long total) { |
||||
updateDownloadDialog.setProgress((int) (updateDownloadDialog.getMax() * (downloaded / (double) total))); |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateDownloadSuccess() { |
||||
updateDownloadDialog.dismiss(); |
||||
updateDownloadDialog = null; |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateDownloadFailed() { |
||||
updateDownloadDialog.dismiss(); |
||||
updateDownloadDialog = null; |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_install_download_failed) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateDownloadMoveFailed() { |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_install_download_move_failed) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateOpenInstallScreen(Intent intent) { |
||||
AndroidUtils.openIntent(intent); |
||||
} |
||||
|
||||
@Override |
||||
public void openUpdateRetryDialog(final UpdateManager.Install install) { |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_retry_title) |
||||
.setMessage(R.string.update_retry) |
||||
.setNegativeButton(R.string.cancel, null) |
||||
.setPositiveButton(R.string.update_retry_button, new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int which) { |
||||
updateManager.retry(install); |
||||
} |
||||
}) |
||||
.show(); |
||||
} |
||||
|
||||
private void showMessage(int version) { |
||||
int resource = context.getResources().getIdentifier("previous_version_" + version, "string", context.getPackageName()); |
||||
if (resource != 0) { |
||||
CharSequence message = Html.fromHtml(context.getString(resource)); |
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context) |
||||
.setMessage(message) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.create(); |
||||
dialog.show(); |
||||
dialog.setCanceledOnTouchOutside(false); |
||||
|
||||
final Button button = dialog.getButton(DialogInterface.BUTTON_POSITIVE); |
||||
button.setEnabled(false); |
||||
AndroidUtils.runOnUiThread(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
dialog.setCanceledOnTouchOutside(true); |
||||
button.setEnabled(true); |
||||
} |
||||
}, 1500); |
||||
} |
||||
} |
||||
|
||||
private void cleanupOutdatedIonFolder(Context context) { |
||||
Logger.i(TAG, "Cleaning up old ion folder"); |
||||
File ionCacheFolder = new File(context.getCacheDir() + "/ion"); |
||||
if (ionCacheFolder.exists() && ionCacheFolder.isDirectory()) { |
||||
Logger.i(TAG, "Clearing old ion folder"); |
||||
for (File file : ionCacheFolder.listFiles()) { |
||||
if (!file.delete()) { |
||||
Logger.i(TAG, "Could not delete old ion file " + file.getName()); |
||||
} |
||||
} |
||||
if (!ionCacheFolder.delete()) { |
||||
Logger.i(TAG, "Could not delete old ion folder"); |
||||
} else { |
||||
Logger.i(TAG, "Deleted old ion folder"); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
{ |
||||
"api_version": 1, |
||||
"messages": [ |
||||
{ |
||||
"type": "update", |
||||
|
||||
"code": 56, |
||||
"date": "2017-03-18T13:23:06.614104", |
||||
"message_html": "<h2>Clover v2.2.0 is available</h2>A new version of Clover is available.<br><br>This release fixes stuff.<br>- aaa<br>- bbb", |
||||
|
||||
"apk": { |
||||
"default": { |
||||
"url": "https://github.com/Floens/Clover/releases/download/v2.2.0/Clover_v2.2.0.apk" |
||||
}, |
||||
"fdroid": { |
||||
"url": "https://f-droid.org/repo/org.floens.chan_56.apk" |
||||
} |
||||
} |
||||
} |
||||
], |
||||
"check_interval": 432000000 |
||||
} |
@ -0,0 +1,29 @@ |
||||
update_api.json describes the update check api that Clover loads periodically. |
||||
|
||||
api_version |
||||
Version of this api, always 1. |
||||
|
||||
check_interval |
||||
the interval of loading the file, overrides the default interval of 5 days if set. |
||||
|
||||
messages |
||||
array of messages |
||||
|
||||
type: |
||||
type of the message, only "update" is supported |
||||
|
||||
code: |
||||
code of the new version. if this is higher than the code of the calling app then the message will be processed. |
||||
|
||||
date: |
||||
ISO8601 date, parsed but not used for now. |
||||
|
||||
message_html: |
||||
message shown to the user, parsed with Html.fromHtml() |
||||
|
||||
apk: |
||||
set of apks for each flavor. each key is only parsed if it equals to the flavor name that the app is compiled for. |
||||
|
||||
url: |
||||
url of the apk file to download and install. |
||||
|
Loading…
Reference in new issue