, 1 April 1990
480 | Ty Coon, President of Vice
481 |
482 | That's all there is to it!
483 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | App Update Checker
2 | ==================
3 |
4 | A simple non-Market way to keep your app updated.
5 |
6 | All it requires to set up is a URL pointing to a JSON document describing your
7 | app's changes.
8 |
9 | It will compare its version code (from the manifest file) to the versions
10 | listed in the JSON. If there are newer version(s), it will provide the
11 | changelog between the installed version and the latest version. The updater
12 | checks against the versionCode, but displays the versionName.
13 |
14 | While you can create your own OnAppUpdateListener to listen for new updates,
15 | OnUpdateDialog is a handy implementation that displays a Dialog with a bulleted
16 | list and a button to do the upgrade.
17 |
18 | The JSON format looks like this:
19 |
20 | {
21 | "package": {
22 | "downloadUrl": "http://coolapp.example.com/myapp.apk"
23 | },
24 |
25 | "0.2": {
26 | "versionCode": 2,
27 | "changelog": ["New automatic update checker", "Improved template interactions"]
28 | },
29 | "0.1": {
30 | "versionCode": 1,
31 | "changelog": ["fixed crash"]
32 | }
33 | }
34 |
35 | Publishing steps
36 | ----------------
37 |
38 | 1. point the app updater to a URL you control (in the strings.xml file)
39 | 2. upload apk to server
40 | 3. generate the json document that the app updater looks at which contains information about the release. There's a python script that can generate such documents in the app updater's source.
41 | 4. publish the json document at the URL in step 1
42 | 5. every time the app starts, it'll check to see if there's an update to that json file. It has a backoff threshhold that's set compile time for the app updater so it won't check it if it already checked it within N minutes.
43 |
44 |
45 | License
46 | =======
47 |
48 | AppUpdateChecker
49 | Copyright (C) 2011 [MIT Mobile Experience Lab][mel]
50 |
51 | This library is free software; you can redistribute it and/or
52 | modify it under the terms of the GNU Lesser General Public
53 | License as published by the Free Software Foundation; either
54 | version 2.1 of the License, or (at your option) any later version.
55 |
56 | This library is distributed in the hope that it will be useful,
57 | but WITHOUT ANY WARRANTY; without even the implied warranty of
58 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
59 | Lesser General Public License for more details.
60 |
61 | You should have received a copy of the GNU Lesser General Public
62 | License along with this library; if not, write to the Free Software
63 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
64 |
65 | [mel]: http://mobile.mit.edu/
66 |
--------------------------------------------------------------------------------
/extras/release.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2.7
2 |
3 | import json, sys
4 | import urllib2
5 | import rfc822, datetime, time
6 | import os
7 |
8 | import argparse
9 |
10 | DOWNLOAD_URL = 'downloadUrl'
11 | PACKAGE = 'package'
12 | VERSION_CODE = 'versionCode'
13 | CHANGELOG = 'changelog'
14 |
15 | APK_CONTENT_TYPE = 'application/vnd.android.package-archive'
16 |
17 | class HeadRequest(urllib2.Request):
18 | def get_method(self):
19 | return "HEAD"
20 |
21 | class VersionListException(Exception):
22 | pass
23 |
24 | class VersionList:
25 | """AppUpdateChecker JSON file generator / updater
26 |
27 | This creates and updates the JSON file read by AppUpdateChecker, as well as allows
28 | a file to be verified to ensure that it's constructed properly.
29 | """
30 | versions = None
31 | package = None
32 |
33 | def __init__(self, json_file=None):
34 | self.json_file = json_file
35 | if json_file:
36 | self.json = json.load(json_file)
37 | self.parse()
38 |
39 | def parse(self):
40 | if not self.json_file:
41 | return
42 |
43 | if type(self.json) != dict:
44 | return
45 | self.versions = dict(self.json)
46 | if PACKAGE in self.versions:
47 | self.package = self.versions[PACKAGE]
48 | del self.versions[PACKAGE]
49 |
50 | def verify(self, online=False):
51 | """Returns a tuple of verification, error message
52 |
53 | Raises VersionListException if the list hasn't been loaded"""
54 |
55 | if not self.json_file:
56 | raise VersionListException("must load a version list file first")
57 |
58 | ver = self.versions
59 | if type(ver) != dict:
60 | return (False,"Document is not a JSON object")
61 |
62 | if not self.package:
63 | return (False,"missing %s key" % PACKAGE)
64 | if DOWNLOAD_URL not in self.package:
65 | return (False,"missing %s key in %s object" % (DOWNLOAD_URL, PACKAGE))
66 |
67 | for code, info in ver.iteritems():
68 | if type(info) != dict:
69 | return (False,"value for version '%s' is not a JSON object" % code)
70 | if type(ver[code][VERSION_CODE]) != int:
71 | return (False,"version code in key %s of version '%s' is not an int" % (VERSION_CODE, code))
72 | if type(ver[code][CHANGELOG]) != list:
73 | return (False, "key %s in version '%s' is not a list" % (CHANGELOG, code))
74 |
75 | if online:
76 | return self.verify_online()
77 |
78 | return (True, None)
79 |
80 | def download_url(self, url=None):
81 | if url:
82 | if not self.package:
83 | self.package = {}
84 | self.package[DOWNLOAD_URL] = url
85 |
86 | return self.package[DOWNLOAD_URL]
87 |
88 | def version_latest(self):
89 | ver = self.versions_sorted()[-1]
90 | return (ver, self.versions[ver])
91 |
92 | def versions_sorted(self):
93 | """Retrieves a sorted list of all the version names, sorted by version code
94 |
95 | Raises VersionListException if the list hasn't been loaded"""
96 | if not self.json_file:
97 | raise VersionListException("must load a version list file first")
98 |
99 | return sorted(self.versions, key=lambda ver: self.versions[ver][VERSION_CODE])
100 |
101 | def verify_online(self):
102 | url = self.download_url()
103 |
104 | try:
105 | res = urllib2.urlopen(HeadRequest(url))
106 | except urllib2.HTTPError as e:
107 | return (False, e)
108 |
109 | if res.code != 200:
110 | return (False, "%d %s" % (res.code, res.msg))
111 |
112 | sys.stderr.writelines("HEAD %s returned %d %s\n" % (url, res.code, res.msg))
113 |
114 | content_type = res.headers['content-type']
115 | if APK_CONTENT_TYPE != content_type:
116 | sys.stderr.writelines("warning: content type returned by %s should be %s, not %s\n" % (url, APK_CONTENT_TYPE, content_type))
117 |
118 | last_modified = res.headers.get('last-modified', None)
119 | if last_modified:
120 | last_modified = datetime.datetime.fromtimestamp(time.mktime(rfc822.parsedate(last_modified)))
121 | sys.stderr.writelines("last modified %s\n" % last_modified)
122 |
123 | size = res.headers.get('content-length', None)
124 | if size and size < 4000:
125 | return (False, "content length of %s was less than 4k." % url)
126 |
127 | res.close()
128 |
129 | return (True, None)
130 |
131 | def write_json(self, out):
132 | j = dict(self.versions)
133 | j[PACKAGE] = self.package
134 | json.dump(j, out, indent=2)
135 |
136 | def add_release(self, ver_code, ver_name, changelog=[]):
137 | if not self.versions:
138 | self.versions = {}
139 | if ver_name in self.versions.keys():
140 | raise VersionListException("version '%s' already exists" % ver_name)
141 | ver_code = int(ver_code)
142 | if ver_code in map(lambda v: v[VERSION_CODE], self.versions.values()):
143 | raise VersionListException("version code '%d' already exists" % ver_code)
144 | self.versions[ver_name] = {VERSION_CODE: ver_code, CHANGELOG: changelog}
145 |
146 | def release_cmd(args):
147 | if args.filename:
148 | try:
149 | inpt = open(args.filename)
150 | except IOError as e:
151 | # this is not ideal according to http://docs.python.org/howto/doanddont.html
152 | # but there's no good way to determine if it's a "file not found" or other error
153 | # based on the exception alone
154 | if not os.path.exists(args.filename):
155 | inpt = None
156 | else:
157 | raise e
158 |
159 | ver = VersionList(inpt)
160 | if inpt:
161 | ver.verify()
162 | else:
163 | ver = VersionList(sys.stdin)
164 | ver.verify()
165 |
166 | try:
167 | ver.add_release(args.code, args.name, args.changelog)
168 | if args.url:
169 | ver.download_url(args.url)
170 | if args.filename:
171 | out = open(args.filename, 'w')
172 | else:
173 | out = sys.stdout
174 | ver.write_json(out)
175 | except VersionListException as e:
176 | sys.stderr.writelines("%s\n" % e)
177 |
178 | def verify_cmd(args):
179 | if args.filename:
180 | inpt = open(args.filename)
181 | else:
182 | inpt = sys.stdin
183 | ver = VersionList(inpt)
184 | (res, err) = ver.verify(online=args.online)
185 | if res:
186 | print "verification succeeded: no errors found"
187 | latest, latest_info = ver.version_latest()
188 | print "Latest version is %s (%d)" % (latest, latest_info[VERSION_CODE])
189 | else:
190 | print "verification failed: %s" % err
191 |
192 | if __name__=='__main__':
193 | parser = argparse.ArgumentParser(
194 | description="Generate or update a static json AppUpdateChecker file.")
195 |
196 | parser.add_argument("-f", "--file", dest="filename",
197 | help="read/write version information to FILE", metavar="FILE")
198 |
199 | subparsers = parser.add_subparsers(help='sub-command help')
200 |
201 | verify = subparsers.add_parser('verify', help="verify the document")
202 | release = subparsers.add_parser('release', help="add a new release")
203 |
204 | verify.add_argument("-d", "--verify-download", dest="online", action="store_true",
205 | help="when verifying, perform a HEAD request on the download URL to ensure it is valid")
206 |
207 | verify.set_defaults(func=verify_cmd)
208 |
209 | release.add_argument("-u", "--url", dest="url", help="set/update download URL")
210 |
211 | release.add_argument('code', help='integer version code')
212 | release.add_argument('name', help='a unique name for the release')
213 | release.add_argument('changelog', help='changelog entries', nargs='+')
214 | release.set_defaults(func=release_cmd)
215 |
216 | args = parser.parse_args()
217 | args.func(args)
218 |
--------------------------------------------------------------------------------
/libs/melautils.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitmel/AppUpdateChecker/0163f36c5ed25168ab59395170973d32c3d17baf/libs/melautils.jar
--------------------------------------------------------------------------------
/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=Google Inc.:Google APIs:8
12 | android.library=true
13 |
--------------------------------------------------------------------------------
/res/values-it/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | C\'è una nuova versione (%1$s) di %2$s disponibile!
4 | Aggiorna
5 |
6 |
--------------------------------------------------------------------------------
/res/values-pt/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Nova versão (%1$s) de %2$s disponível!
4 | Nova versão
5 |
6 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | There is a new version (%1$s) of %2$s available!
6 |
7 | Upgrade
8 |
9 | Check for updates
10 |
11 | Minimum update frequency
12 |
13 | Checks for updates at most this many minutes
14 |
15 |
--------------------------------------------------------------------------------
/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/edu/mit/mobile/android/appupdater/AppUpdateChecker.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.appupdater;
2 |
3 | /*
4 | * Copyright (C) 2010-2012 MIT Mobile Experience Lab
5 | *
6 | * This library is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 2.1 of the License, or (at your option) any later version.
10 | *
11 | * This library is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public
17 | * License along with this library; if not, write to the Free Software
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 | */
20 | import java.util.ArrayList;
21 | import java.util.Comparator;
22 | import java.util.Iterator;
23 | import java.util.Map.Entry;
24 | import java.util.TreeMap;
25 |
26 | import org.apache.http.HttpEntity;
27 | import org.apache.http.HttpResponse;
28 | import org.apache.http.HttpStatus;
29 | import org.apache.http.StatusLine;
30 | import org.apache.http.client.methods.HttpGet;
31 | import org.apache.http.impl.client.DefaultHttpClient;
32 | import org.json.JSONArray;
33 | import org.json.JSONException;
34 | import org.json.JSONObject;
35 |
36 | import android.content.Context;
37 | import android.content.Intent;
38 | import android.content.SharedPreferences;
39 | import android.content.pm.PackageManager.NameNotFoundException;
40 | import android.net.Uri;
41 | import android.os.AsyncTask;
42 | import android.preference.PreferenceManager;
43 | import android.util.Log;
44 | import edu.mit.mobile.android.utils.StreamUtils;
45 |
46 | /**
47 | * A fairly simple non-Market app update checker. Give it a URL pointing to a JSON file
48 | * and it will compare its version (from the manifest file) to the versions listed in the JSON.
49 | * If there are newer version(s), it will provide the changelog between the installed version
50 | * and the latest version. The updater checks against the versionCode, but displays the versionName.
51 | *
52 | * While you can create your own OnAppUpdateListener to listen for new updates, OnUpdateDialog is
53 | * a handy implementation that displays a Dialog with a bulleted list and a button to do the upgrade.
54 | *
55 | * The JSON format looks like this:
56 | *
57 | {
58 | "package": {
59 | "downloadUrl": "http://locast.mit.edu/connects/lcc.apk"
60 | },
61 |
62 | "1.4.3": {
63 | "versionCode": 6,
64 | "changelog": ["New automatic update checker", "Improved template interactions"]
65 | },
66 | "1.4.2": {
67 | "versionCode": 5,
68 | "changelog": ["fixed crash when saving cast"]
69 | }
70 | }
71 | *
72 | *
73 | * @author Steve Pomeroy
74 | *
75 | */
76 | public class AppUpdateChecker {
77 | private final static String TAG = AppUpdateChecker.class.getSimpleName();
78 |
79 | public static final String SHARED_PREFERENCES_NAME = "edu.mit.mobile.android.appupdater.preferences";
80 | public static final String
81 | PREF_ENABLED = "enabled",
82 | PREF_MIN_INTERVAL = "min_interval",
83 | PREF_LAST_UPDATED = "last_checked";
84 |
85 | private final String mVersionListUrl;
86 | private int currentAppVersion;
87 |
88 | private JSONObject pkgInfo;
89 | private final Context mContext;
90 |
91 | private final OnAppUpdateListener mUpdateListener;
92 | private SharedPreferences mPrefs;
93 |
94 | private static final int MILLISECONDS_IN_MINUTE = 60000;
95 |
96 | /**
97 | * @param context
98 | * @param versionListUrl URL pointing to a JSON file with the update list.
99 | * @param updateListener
100 | */
101 | public AppUpdateChecker(Context context, String versionListUrl, OnAppUpdateListener updateListener) {
102 | mContext = context;
103 | mVersionListUrl = versionListUrl;
104 | mUpdateListener = updateListener;
105 |
106 | try {
107 | currentAppVersion = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
108 | } catch (final NameNotFoundException e) {
109 | Log.e(TAG, "Cannot get version for self! Who am I?! What's going on!? I'm so confused :-(");
110 | return;
111 | }
112 |
113 | mPrefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
114 | // defaults are kept in the preference file for ease of tweaking
115 | // TODO put this on a thread somehow
116 | PreferenceManager.setDefaultValues(mContext, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE, R.xml.preferences, false);
117 | }
118 |
119 | // min interval is stored as a string so a preference editor could potentially edit it using a text edit widget
120 |
121 | public int getMinInterval(){
122 | return Integer.valueOf(mPrefs.getString(PREF_MIN_INTERVAL, "60"));
123 | }
124 |
125 | public void setMinInterval(int minutes){
126 | mPrefs.edit().putString(PREF_MIN_INTERVAL, String.valueOf(minutes)).commit();
127 | }
128 |
129 | public boolean getEnabled(){
130 | return mPrefs.getBoolean(PREF_ENABLED, true);
131 | }
132 |
133 | public void setEnabled(boolean enabled){
134 | mPrefs.edit().putBoolean(PREF_ENABLED, enabled).commit();
135 | }
136 |
137 | /**
138 | * You normally shouldn't need to call this, as {@link #checkForUpdates()} checks it before doing any updates.
139 | *
140 | * @return true if the updater should check for updates
141 | */
142 | public boolean isStale(){
143 | return System.currentTimeMillis() - mPrefs.getLong(PREF_LAST_UPDATED, 0) > getMinInterval() * MILLISECONDS_IN_MINUTE;
144 | }
145 |
146 | /**
147 | * Checks for updates if updates haven't been checked for recently and if checking is enabled.
148 | */
149 | public void checkForUpdates(){
150 | if (mPrefs.getBoolean(PREF_ENABLED, true) && isStale()){
151 | forceCheckForUpdates();
152 | }
153 | }
154 |
155 | /**
156 | * Checks for updates regardless of when the last check happened or if checking for updates is enabled.
157 | */
158 | public void forceCheckForUpdates(){
159 | Log.d(TAG, "checking for updates...");
160 | if (versionTask == null){
161 | versionTask = new GetVersionJsonTask();
162 | versionTask.execute(mVersionListUrl);
163 | }else{
164 | Log.w(TAG, "checkForUpdates() called while already checking for updates. Ignoring...");
165 | }
166 | }
167 |
168 | // why oh why is the JSON API so poorly integrated into java?
169 | @SuppressWarnings("unchecked")
170 | private void triggerFromJson(JSONObject jo) throws JSONException {
171 |
172 | final ArrayList changelog = new ArrayList();
173 |
174 | // keep a sorted map of versionCode to the version information objects.
175 | // Most recent is at the top.
176 | final TreeMap versionMap =
177 | new TreeMap(new Comparator() {
178 | public int compare(Integer object1, Integer object2) {
179 | return object2.compareTo(object1);
180 | };
181 | });
182 |
183 | for (final Iterator i = jo.keys(); i.hasNext(); ){
184 | final String versionName = i.next();
185 | if (versionName.equals("package")){
186 | pkgInfo = jo.getJSONObject(versionName);
187 | continue;
188 | }
189 | final JSONObject versionInfo = jo.getJSONObject(versionName);
190 | versionInfo.put("versionName", versionName);
191 |
192 | final int versionCode = versionInfo.getInt("versionCode");
193 | versionMap.put(versionCode, versionInfo);
194 | }
195 | final int latestVersionNumber = versionMap.firstKey();
196 | final String latestVersionName = versionMap.get(latestVersionNumber).getString("versionName");
197 | final Uri downloadUri = Uri.parse(pkgInfo.getString("downloadUrl"));
198 |
199 | if (currentAppVersion > latestVersionNumber){
200 | Log.d(TAG, "We're newer than the latest published version ("+latestVersionName+"). Living in the future...");
201 | mUpdateListener.appUpdateStatus(true, latestVersionName, null, downloadUri);
202 | return;
203 | }
204 |
205 | if (currentAppVersion == latestVersionNumber){
206 | Log.d(TAG, "We're at the latest version ("+currentAppVersion+")");
207 | mUpdateListener.appUpdateStatus(true, latestVersionName, null, downloadUri);
208 | return;
209 | }
210 |
211 | // construct the changelog. Newest entries are at the top.
212 | for (final Entry version: versionMap.headMap(currentAppVersion).entrySet()){
213 | final JSONObject versionInfo = version.getValue();
214 | final JSONArray versionChangelog = versionInfo.optJSONArray("changelog");
215 | if (versionChangelog != null){
216 | final int len = versionChangelog.length();
217 | for (int i = 0; i < len; i++){
218 | changelog.add(versionChangelog.getString(i));
219 | }
220 | }
221 | }
222 |
223 | mUpdateListener.appUpdateStatus(false, latestVersionName, changelog, downloadUri);
224 | }
225 |
226 | private class VersionCheckException extends Exception {
227 | /**
228 | *
229 | */
230 | private static final long serialVersionUID = 397593559982487816L;
231 |
232 | public VersionCheckException(String msg) {
233 | super(msg);
234 | }
235 | }
236 |
237 | /**
238 | * Send off an intent to start the download of the app.
239 | */
240 | public void startUpgrade(){
241 | try {
242 | final Uri downloadUri = Uri.parse(pkgInfo.getString("downloadUrl"));
243 | mContext.startActivity(new Intent(Intent.ACTION_VIEW, downloadUri));
244 | } catch (final JSONException e) {
245 | e.printStackTrace();
246 | }
247 | }
248 |
249 | private GetVersionJsonTask versionTask;
250 | private class GetVersionJsonTask extends AsyncTask{
251 | private String errorMsg = null;
252 |
253 | @Override
254 | protected void onProgressUpdate(Integer... values) {
255 | Log.d(TAG, "update check progress: " + values[0]);
256 | super.onProgressUpdate(values);
257 | }
258 |
259 | @Override
260 | protected JSONObject doInBackground(String... params) {
261 | publishProgress(0);
262 | final DefaultHttpClient hc = new DefaultHttpClient();
263 | final String url = params[0];
264 | final HttpGet req = new HttpGet(url);
265 | JSONObject jo = null;
266 | try {
267 | publishProgress(50);
268 | final HttpResponse res = hc.execute(req);
269 |
270 | final StatusLine status = res.getStatusLine();
271 | final int statusCode = status.getStatusCode();
272 | if (statusCode == HttpStatus.SC_NOT_FOUND) {
273 | throw new VersionCheckException(url + " " + status.getReasonPhrase());
274 | }
275 | if (statusCode != HttpStatus.SC_OK){
276 | final HttpEntity e = res.getEntity();
277 | if (e.getContentType().getValue().equals("text/html") || e.getContentLength() > 40){
278 | // long response body. Serving HTML...
279 | throw new VersionCheckException("Got a HTML instead of expected JSON.");
280 | }
281 | throw new VersionCheckException("HTTP " + res.getStatusLine().getStatusCode() + " "+ res.getStatusLine().getReasonPhrase());
282 | }
283 |
284 | final HttpEntity ent = res.getEntity();
285 |
286 | jo = new JSONObject(StreamUtils.inputStreamToString(ent.getContent()));
287 | ent.consumeContent();
288 | mPrefs.edit().putLong(PREF_LAST_UPDATED, System.currentTimeMillis()).commit();
289 |
290 | } catch (final Exception e) {
291 | //e.printStackTrace();
292 |
293 | errorMsg = e.getClass().getSimpleName() + ": " + e.getLocalizedMessage();
294 | }finally {
295 | publishProgress(100);
296 | }
297 | return jo;
298 | }
299 |
300 | @Override
301 | protected void onPostExecute(JSONObject result) {
302 | if (result == null){
303 | Log.e(TAG, errorMsg);
304 | }else{
305 | try {
306 | triggerFromJson(result);
307 |
308 | } catch (final JSONException e) {
309 | Log.e(TAG, "Error in JSON version file.", e);
310 | }
311 | }
312 | versionTask = null; // forget about us, we're done.
313 | };
314 | };
315 | }
316 |
--------------------------------------------------------------------------------
/src/edu/mit/mobile/android/appupdater/OnAppUpdateListener.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.appupdater;
2 |
3 | import java.util.List;
4 |
5 | import android.net.Uri;
6 |
7 | public interface OnAppUpdateListener {
8 | public void appUpdateStatus(boolean isLatestVersion, String latestVersionName, List changelog, Uri downloadUrl);
9 | }
--------------------------------------------------------------------------------
/src/edu/mit/mobile/android/appupdater/OnUpdateDialog.java:
--------------------------------------------------------------------------------
1 | package edu.mit.mobile.android.appupdater;
2 |
3 | import java.util.List;
4 |
5 | import android.app.Dialog;
6 | import android.app.AlertDialog.Builder;
7 | import android.content.Context;
8 | import android.content.DialogInterface;
9 | import android.content.Intent;
10 | import android.net.Uri;
11 | import android.os.Handler;
12 | import android.os.Message;
13 |
14 | /**
15 | * A handy pop-up dialog box which lists the changelog and asks if you want to update.
16 | *
17 | * @author steve
18 | *
19 | */
20 | public class OnUpdateDialog implements OnAppUpdateListener {
21 | private final Context mContext;
22 | private final CharSequence mAppName;
23 | private Uri downloadUri;
24 | private final Handler mHandler;
25 | private static final int MSG_SHOW_DIALOG = 1;
26 | private Dialog mDialog;
27 |
28 | public OnUpdateDialog(Context context, CharSequence appName) {
29 | mContext = context;
30 | mAppName = appName;
31 | mHandler = new Handler(){
32 | @Override
33 | public void handleMessage(Message msg) {
34 | switch (msg.what){
35 | case MSG_SHOW_DIALOG:
36 | try{
37 | // TODO fix this so it'll pop up appropriately
38 | mDialog.show();
39 | }catch (final Exception e){
40 | // XXX ignore for the moment
41 | }
42 |
43 | break;
44 | }
45 | }
46 | };
47 | }
48 |
49 | public void appUpdateStatus(boolean isLatestVersion,
50 | String latestVersionName, List changelog, Uri downloadUri) {
51 | this.downloadUri = downloadUri;
52 |
53 | if (!isLatestVersion){
54 | final Builder db = new Builder(mContext);
55 | db.setTitle(mAppName);
56 |
57 | final StringBuilder sb = new StringBuilder();
58 | sb.append(mContext.getString(R.string.app_update_new_version, latestVersionName, mAppName));
59 | sb.append("\n\n");
60 | for (final String item: changelog){
61 | sb.append(" • ").append(item).append("\n");
62 | }
63 |
64 | db.setMessage(sb);
65 |
66 | db.setPositiveButton(R.string.upgrade, dialogOnClickListener);
67 | db.setNegativeButton(android.R.string.cancel, dialogOnClickListener);
68 | mDialog = db.create();
69 | mHandler.sendEmptyMessage(MSG_SHOW_DIALOG);
70 |
71 | }
72 | }
73 |
74 | private final DialogInterface.OnClickListener dialogOnClickListener = new DialogInterface.OnClickListener() {
75 |
76 | public void onClick(DialogInterface dialog, int which) {
77 | switch(which){
78 | case Dialog.BUTTON_POSITIVE:
79 | mContext.startActivity(new Intent(Intent.ACTION_VIEW, downloadUri));
80 | }
81 |
82 | }
83 | };
84 | }
--------------------------------------------------------------------------------