Java tutorial
/* MythDroid: Android MythTV Remote Copyright (C) 2009-2010 foobum@gmail.com 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.mythdroid.util; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.SocketException; import java.net.URL; import java.util.ArrayList; import org.apache.http.client.ClientProtocolException; import org.mythdroid.Globals; import org.mythdroid.R; import org.mythdroid.data.XMLHandler; import org.mythdroid.data.XMLHandler.Element; import org.mythdroid.mdd.MDDManager; import org.mythdroid.resource.Messages; import org.xml.sax.SAXException; import android.annotation.TargetApi; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Environment; import android.os.IBinder; import android.sax.EndTextElementListener; import android.util.Xml; /** An update checker for MythDroid and MDD */ @TargetApi(8) public class UpdateService extends Service { /** ACTION intent extra value to check for MythDroid updates */ final public static int CHECKMD = 0; /** ACTION intent extra value to check for MDD updates */ final public static int CHECKMDD = 1; /** ACTION intent extra, determines which package to check */ final public static String ACTION = "ACTION"; //$NON-NLS-1$ /** ADDR intent extra, value should be address of MDD */ final public static String ADDR = "ADDR"; //$NON-NLS-1$ final private static int UPDATEMD = 2, UPDATEMDD = 3; final private static String VER = "VERSION", URL = "URL"; //$NON-NLS-1$ //$NON-NLS-2$ final private static String urlString = "http://code.google.com/feeds/p/mythdroid/downloads/basic"; //$NON-NLS-1$ final private static String MDACTION = "org.mythdroid.action.MDUpdate"; //$NON-NLS-1$ final private static String MDDACTION = "org.mythdroid.action.MDDUpdate"; //$NON-NLS-1$ final private static IntentFilter filter = new IntentFilter(); static { filter.addAction(MDACTION); filter.addAction(MDDACTION); } private XMLHandler handler = null; private ArrayList<DownloadEntry> entries = null; private DownloadEntry download = null; private Version MDVer = null, MDDVer = null; private Context ctx = this; private class DownloadEntry { public String url; public String title; public DownloadEntry() { } } private class Version implements Comparable<Version> { public int major, minor, inc; public Uri url; public Version(String ver, String url) throws NumberFormatException { int first = ver.indexOf('.'); major = Integer.valueOf(ver.substring(0, first)); int second = ver.indexOf('.', first + 1); minor = Integer.valueOf(ver.substring(first + 1, second)); inc = Integer.valueOf(ver.substring(second + 1)); if (url != null) this.url = Uri.parse(url); } @Override public int compareTo(Version another) { if (major > another.major) return 1; if (another.major > major) return -1; if (minor > another.minor) return 1; if (another.minor > minor) return -1; if (inc > another.inc) return 1; if (another.inc > inc) return -1; return 0; } @Override public String toString() { return major + "." + minor + "." + inc; //$NON-NLS-1$ //$NON-NLS-2$ } } final private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { intent.setClass(ctx, ctx.getClass()); if (intent.getAction().equals(MDACTION)) { intent.putExtra(ACTION, UPDATEMD); startService(intent); } else if (intent.getAction().equals(MDDACTION)) { intent.putExtra(ACTION, UPDATEMDD); startService(intent); } } }; @Override public void onStart(Intent intent, int startId) { handleCommand(intent); } @Override public int onStartCommand(final Intent intent, int flags, int startId) { if (intent != null) handleCommand(intent); return Service.START_NOT_STICKY; } @Override public void onCreate() { registerReceiver(receiver, filter); handler = new XMLHandler("feed"); //$NON-NLS-1$ final Element root = handler.rootElement(); final Element entry = root.getChild("entry"); //$NON-NLS-1$ entry.getChild("content").setTextElementListener( //$NON-NLS-1$ new EndTextElementListener() { @Override public void end(String text) { int start = text.indexOf("http:"); //$NON-NLS-1$ int end = text.indexOf("\">", start); //$NON-NLS-1$ download.url = text.substring(start, end); entries.add(download); } }); entry.getChild("title").setTextElementListener( //$NON-NLS-1$ new EndTextElementListener() { @Override public void end(String text) { download = new DownloadEntry(); download.title = text; } }); Globals.runOnWorker(new Runnable() { @Override public void run() { getAvailableVersions(); } }); } @Override public void onDestroy() { unregisterReceiver(receiver); } private void handleCommand(final Intent intent) { switch (intent.getIntExtra(ACTION, CHECKMD)) { case CHECKMD: Globals.runOnWorker(new Runnable() { @Override public void run() { checkMythDroid(); } }); break; case CHECKMDD: Globals.runOnWorker(new Runnable() { @Override public void run() { checkMDD(intent.getStringExtra(ADDR)); } }); break; case UPDATEMD: Globals.runOnWorker(new Runnable() { @Override public void run() { updateMythDroid(intent.getStringExtra(VER), intent.getStringExtra(URL)); } }); break; case UPDATEMDD: Globals.runOnWorker(new Runnable() { @Override public void run() { updateMDD(intent.getStringExtra(ADDR), intent.getStringExtra(VER), intent.getStringExtra(URL)); } }); break; } } private void checkMythDroid() { if (MDVer == null) return; Version runningVer; try { runningVer = new Version(getPackageManager().getPackageInfo(getPackageName(), 0).versionName, null); } catch (NameNotFoundException e) { return; } if (runningVer.compareTo(MDVer) >= 0) { LogUtil.debug("Already running latest version of MythDroid"); //$NON-NLS-1$ return; } LogUtil.debug("Version " + MDVer.toString() + //$NON-NLS-1$ " is available (current version is " + runningVer.toString() + ")" //$NON-NLS-1$ //$NON-NLS-2$ ); final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); final Notification notification = new Notification(R.drawable.logo, Messages.getString("UpdateService.0") + "MythDroid" + //$NON-NLS-1$ //$NON-NLS-2$ Messages.getString("UpdateService.1"), //$NON-NLS-1$ System.currentTimeMillis()); notification.flags = Notification.FLAG_AUTO_CANCEL; final Intent intent = new Intent(MDACTION); intent.putExtra(VER, MDVer.toString()); intent.putExtra(URL, MDVer.url.toString()); notification.setLatestEventInfo(getApplicationContext(), "MythDroid" + Messages.getString("UpdateService.2"), //$NON-NLS-1$ //$NON-NLS-2$ MDVer.toString() + Messages.getString("UpdateService.1") + //$NON-NLS-1$ Messages.getString("UpdateService.3"), //$NON-NLS-1$ PendingIntent.getBroadcast(this, 0, intent, 0)); nm.notify(CHECKMD, notification); } private void updateMythDroid(String ver, String url) { LogUtil.debug("Fetching APK from " + url); //$NON-NLS-1$ HttpFetcher fetcher = null; try { fetcher = new HttpFetcher(url, false); } catch (ClientProtocolException e) { ErrUtil.logErr(e); notify("MythDroid" + Messages.getString("UpdateService.2"), //$NON-NLS-1$ //$NON-NLS-2$ e.getMessage()); return; } catch (IOException e) { ErrUtil.logErr(e); notify("MythDroid" + Messages.getString("UpdateService.2"), //$NON-NLS-1$ //$NON-NLS-2$ e.getMessage()); return; } final File storage = Environment.getExternalStorageDirectory(); final File outputFile = new File(storage.getAbsolutePath() + '/' + "Download", //$NON-NLS-1$ "MythDroid-" + ver + ".apk" //$NON-NLS-1$ //$NON-NLS-2$ ); LogUtil.debug("Saving to " + outputFile.getAbsolutePath()); //$NON-NLS-1$ FileOutputStream outputStream; try { outputStream = new FileOutputStream(outputFile); } catch (FileNotFoundException e) { ErrUtil.logErr("SDCard is unavailable"); //$NON-NLS-1$ notify("MythDroid" + Messages.getString("UpdateService.2"), //$NON-NLS-1$ //$NON-NLS-2$ Messages.getString("UpdateService.4") //$NON-NLS-1$ ); return; } try { fetcher.writeTo(outputStream); } catch (IOException e) { ErrUtil.logErr(e); notify("MythDroid" + Messages.getString("UpdateService.2"), //$NON-NLS-1$ //$NON-NLS-2$ e.getMessage()); return; } LogUtil.debug("Download successful, installing..."); //$NON-NLS-1$ final Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive" //$NON-NLS-1$ ); installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(installIntent); } private void checkMDD(String addr) { if (MDDVer == null) return; Version currentVer; try { currentVer = new Version(MDDManager.getVersion(addr), null); } catch (NumberFormatException e) { ErrUtil.logErr(e); return; } catch (IOException e) { ErrUtil.logErr(e); return; } if (currentVer.compareTo(MDDVer) >= 0) { LogUtil.debug("MDD on " + addr + " is already the latest version" //$NON-NLS-1$ //$NON-NLS-2$ ); return; } LogUtil.debug("MDD ver " + MDDVer.toString() + " is available (current ver on " + //$NON-NLS-1$ //$NON-NLS-2$ addr + " is " + currentVer.toString() + ")" //$NON-NLS-1$ //$NON-NLS-2$ ); if (MDVer != null && MDVer.compareTo(MDDVer) != 0) { LogUtil.warn("Version mismatch:" + " MythDroid is " + MDVer.toString() + //$NON-NLS-1$ //$NON-NLS-2$ ", MDD on " + addr + " is " + MDDVer.toString() //$NON-NLS-1$ //$NON-NLS-2$ ); } final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); final Notification notification = new Notification(R.drawable.logo, Messages.getString("UpdateService.0") + "MDD" + //$NON-NLS-1$ //$NON-NLS-2$ Messages.getString("UpdateService.1"), //$NON-NLS-1$ System.currentTimeMillis()); notification.flags = Notification.FLAG_AUTO_CANCEL; final Intent intent = new Intent(MDDACTION); intent.putExtra(ADDR, addr); intent.putExtra(VER, MDDVer.toString()); intent.putExtra(URL, MDDVer.url.toString()); notification.setLatestEventInfo(getApplicationContext(), "MDD" + Messages.getString("UpdateService.2"), //$NON-NLS-1$ //$NON-NLS-2$ MDDVer.toString() + Messages.getString("UpdateService.1") + //$NON-NLS-1$ Messages.getString("UpdateService.3"), //$NON-NLS-1$ PendingIntent.getBroadcast(this, 0, intent, 0)); nm.notify(CHECKMDD, notification); } private void updateMDD(String addr, String ver, String url) { String file; try { file = MDDManager.downloadUpdate(addr, ver, url); } catch (IOException e) { ErrUtil.logErr(e); notify("MDD" + Messages.getString("UpdateService.5") + addr, //$NON-NLS-1$ //$NON-NLS-2$ e.getMessage()); return; } if (file.equals("FAILED")) { //$NON-NLS-1$ notify("MDD" + Messages.getString("UpdateService.5") + addr, //$NON-NLS-1$ //$NON-NLS-2$ Messages.getString("UpdateService.7") //$NON-NLS-1$ ); } notify("MDD" + Messages.getString("UpdateService.5") + addr, //$NON-NLS-1$ //$NON-NLS-2$ Messages.getString("UpdateService.6") + file //$NON-NLS-1$ ); } private void notify(String title, String message) { NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new Notification(R.drawable.logo, title, System.currentTimeMillis()); notification.flags = Notification.FLAG_AUTO_CANCEL; notification.setLatestEventInfo(getApplicationContext(), title, message, PendingIntent.getActivity(Globals.appContext, 0, new Intent(), 0)); nm.notify(-1, notification); } private void getAvailableVersions() { final URL url; try { url = new URL(urlString); } catch (MalformedURLException e) { ErrUtil.logErr(e); return; } entries = new ArrayList<DownloadEntry>(2); try { Xml.parse(url.openStream(), Xml.Encoding.UTF_8, handler); } catch (SocketException e) { ErrUtil.logErr(e); } catch (SAXException e) { ErrUtil.logErr(e); } catch (IOException e) { ErrUtil.logErr(e); } if (entries.size() != 2) return; for (int i = 0; i < 2; i++) { String version; final DownloadEntry entry = entries.get(i); int start = -1; if ((start = entry.title.indexOf("MythDroid")) != -1) { //$NON-NLS-1$ start += 10; int end = entry.title.indexOf(".apk"); //$NON-NLS-1$ if (end == -1) continue; version = entry.title.substring(start, end); try { MDVer = new Version(version, entry.url); } catch (NumberFormatException e) { MDVer = null; } } else if ((start = entry.title.indexOf("mdd")) != -1) { //$NON-NLS-1$ start += 4; int end = entry.title.indexOf(".tgz"); //$NON-NLS-1$ if (end == -1) continue; version = entry.title.substring(start, end); try { MDDVer = new Version(version, entry.url); } catch (NumberFormatException e) { MDDVer = null; } } else ErrUtil.logErr("Unexpected title: " + entry.title); //$NON-NLS-1$ } } @Override public IBinder onBind(Intent intent) { return null; } }