/*
* ConnectBot: simple, powerful, open-source SSH client for Android
* Copyright 2007 Kenny Root, Jeffrey Sharkey
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.woltage.irssiconnectbot.util;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import org.json.JSONObject;
import org.woltage.irssiconnectbot.R;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
/**
* Helper class that checks for updates to this application. On construction, it
* spawns a background thread that checks for any app updates. If available,
* shows a dialog to the user, prompting them to visit Market for the upgrade.
*
* <b>Be sure to change the UPDATE_URL field before using this class.</b> Then
* place a text file at that URL containing JSON data in the format:
*
* <code>{"versionCode": 110, "features": "Brand new interface with over
* 9,000 improvements.", "target": "search?q=searchterms"}</code>
*
* Which should contain information about your newest version. The
* <code>target</code> field is used to build an Intent that launches Market on
* the device, simply be prefixing it with <code>market://</code>. If you know
* your exact Market ID, you could use the value
* <code>details?id=yourexactmarketid</code>
*
* If you're looking for an advanced version-checking system that offers more
* customization, check out Veecheck: http://www.tomgibara.com/android/veecheck/
*
* @author jsharkey
*/
public final class UpdateHelper implements Runnable {
public final static String TAG = "ConnectBot.UpdateHelper";
public final static String UPDATE_URL = "http://connectbot.org/version";
protected Context context;
private String packageName, versionName;
protected int versionCode;
private String userAgent;
/**
* Constructor will automatically spawn thread to check for updates.
* Recommended usage is <code>new UpdateHelper(this);</code> in the first
* onCreate() of your app.
*/
public UpdateHelper(Context context) {
this.context = context;
try {
// read current version information about this package
PackageManager manager = context.getPackageManager();
PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
this.packageName = info.packageName;
this.versionCode = info.versionCode;
this.versionName = info.versionName;
} catch(Exception e) {
Log.e(TAG, "Couldn't find package information in PackageManager", e);
return;
}
// decide if we really need to check for update
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String frequency;
try {
frequency = prefs.getString(PreferenceConstants.UPDATE, PreferenceConstants.UPDATE_DAILY);
} catch (ClassCastException cce) {
// Hm, somehow we got a long in there in the previous upgrades.
frequency = PreferenceConstants.UPDATE_DAILY;
Editor editor = prefs.edit();
editor.putString(PreferenceConstants.UPDATE, frequency);
editor.commit();
}
long lastChecked = prefs.getLong(PreferenceConstants.LAST_CHECKED, 0);
long now = (System.currentTimeMillis() / 1000);
long passed = now - lastChecked;
boolean shouldCheck = false;
if (PreferenceConstants.UPDATE_DAILY.equals(frequency)) {
shouldCheck = (passed > 60 * 60 * 24);
} else if (PreferenceConstants.UPDATE_WEEKLY.equals(frequency)) {
shouldCheck = (passed > 60 * 60 * 24 * 7);
}
// place version information in user-agent string to be used later
userAgent = String.format("%s/%s (%d, freq=%s, lang=%s)",
packageName, versionName, versionCode, frequency,
Locale.getDefault().getLanguage());
if(shouldCheck) {
// spawn thread to check for update
// Note that this class should be marked final because a thread is started in the constructor.
Thread updateThread = new Thread(this);
updateThread.setName("UpdateHelper");
updateThread.start();
// update our last-checked time
Editor editor = prefs.edit();
editor.putLong(PreferenceConstants.LAST_CHECKED, now);
editor.commit();
}
}
public void run() {
try {
// fetch and parse the version update information as json
// pass information off to handler to create
JSONObject json = new JSONObject(UpdateHelper.getUrl(UPDATE_URL, userAgent));
Message.obtain(versionHandler, -1, json).sendToTarget();
} catch(Exception e) {
Log.e(TAG, "Problem while fetching/parsing update response", e);
}
}
/**
* Handler that will parse the JSON response and show dialog to user if an
* update is available.
*/
private Handler versionHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// make sure we are being passed a real json object
if(!(msg.obj instanceof JSONObject)) return;
JSONObject json = (JSONObject)msg.obj;
// pull out version and target information from response
final int versionCode = json.optInt("versionCode");
final String features = json.optString("features");
final String target = "market://" + json.optString("target");
// skip if we're already good enough
if(versionCode <= UpdateHelper.this.versionCode) return;
// build dialog to prompt user about updating
new AlertDialog.Builder(context)
.setTitle(R.string.upgrade)
.setMessage(features)
.setPositiveButton(R.string.upgrade_pos, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(target));
context.startActivity(intent);
}
})
.setNegativeButton(R.string.upgrade_neg, null).create().show();
}
};
/**
* Read contents of a URL and return as a String. Handles any server
* downtime with a 6-second timeout.
*/
private static String getUrl(String tryUrl, String userAgent) throws Exception {
URL url = new URL(tryUrl);
URLConnection connection = url.openConnection();
connection.setConnectTimeout(6000);
connection.setReadTimeout(6000);
connection.setRequestProperty("User-Agent", userAgent);
connection.connect();
InputStream is = connection.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
int bytesRead;
byte[] buffer = new byte[1024];
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
os.close();
is.close();
return new String(os.toByteArray());
}
}