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