Java tutorial
/* * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.yeldi.yeldibazaar; import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; import android.app.AlarmManager; import android.app.IntentService; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.ResultReceiver; import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.util.Log; import com.yeldi.yeldibazaar.R; public class UpdateService extends IntentService implements ProgressListener { public static final String RESULT_MESSAGE = "msg"; public static final int STATUS_COMPLETE = 0; public static final int STATUS_ERROR = 1; public static final int STATUS_INFO = 2; private ResultReceiver receiver = null; public UpdateService() { super("UpdateService"); } // Schedule (or cancel schedule for) this service, according to the // current preferences. Should be called a) at boot, b) if the preference // is changed, or c) on startup, in case we get upgraded. public static void schedule(Context ctx) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); String sint = prefs.getString("updateInterval", "0"); int interval = Integer.parseInt(sint); Intent intent = new Intent(ctx, UpdateService.class); PendingIntent pending = PendingIntent.getService(ctx, 0, intent, 0); AlarmManager alarm = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); alarm.cancel(pending); if (interval > 0) { alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 5000, AlarmManager.INTERVAL_HOUR, pending); Log.d("FDroid", "Update scheduler alarm set"); } else { Log.d("FDroid", "Update scheduler alarm not set"); } } protected void sendStatus(int statusCode) { sendStatus(statusCode, null); } protected void sendStatus(int statusCode, String message) { if (receiver != null) { Bundle resultData = new Bundle(); if (message != null && message.length() > 0) resultData.putString(RESULT_MESSAGE, message); receiver.send(statusCode, resultData); } } /** * We might be doing a scheduled run, or we might have been launched by the * app in response to a user's request. If we have a receiver, it's the * latter... */ private boolean isScheduledRun() { return receiver == null; } protected void onHandleIntent(Intent intent) { receiver = intent.getParcelableExtra("receiver"); long startTime = System.currentTimeMillis(); String errmsg = ""; try { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); // See if it's time to actually do anything yet... if (isScheduledRun()) { long lastUpdate = prefs.getLong("lastUpdateCheck", 0); String sint = prefs.getString("updateInterval", "0"); int interval = Integer.parseInt(sint); if (interval == 0) { Log.d("FDroid", "Skipping update - disabled"); return; } long elapsed = System.currentTimeMillis() - lastUpdate; if (elapsed < interval * 60 * 60 * 1000) { Log.d("FDroid", "Skipping update - done " + elapsed + "ms ago, interval is " + interval + " hours"); return; } } else { Log.d("FDroid", "Unscheduled (manually requested) update"); } boolean notify = prefs.getBoolean("updateNotify", false); // Grab some preliminary information, then we can release the // database while we do all the downloading, etc... int prevUpdates = 0; int newUpdates = 0; List<DB.Repo> repos; try { DB db = DB.getDB(); repos = db.getRepos(); } finally { DB.releaseDB(); } // Process each repo... List<DB.App> apps = new ArrayList<DB.App>(); List<Integer> keeprepos = new ArrayList<Integer>(); boolean success = true; boolean changes = false; for (DB.Repo repo : repos) { if (repo.inuse) { sendStatus(STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address)); StringBuilder newetag = new StringBuilder(); String err = RepoXMLHandler.doUpdate(getBaseContext(), repo, apps, newetag, keeprepos, this); if (err == null) { String nt = newetag.toString(); if (!nt.equals(repo.lastetag)) { repo.lastetag = newetag.toString(); changes = true; } } else { success = false; err = "Update failed for " + repo.address + " - " + err; Log.d("FDroid", err); if (errmsg.length() == 0) errmsg = err; else errmsg += "\n" + err; } } } List<DB.App> acceptedapps = new ArrayList<DB.App>(); if (!changes && success) { Log.d("FDroid", "Not checking app details or compatibility, because all repos were up to date."); } else if (changes && success) { sendStatus(STATUS_INFO, getString(R.string.status_checking_compatibility)); List<DB.App> prevapps = ((FDroidApp) getApplication()).getApps(); DB db = DB.getDB(); try { // Need to flag things we're keeping despite having received // no data about during the update. (i.e. stuff from a repo // that we know is unchanged due to the etag) for (int keep : keeprepos) { for (DB.App app : prevapps) { boolean keepapp = false; for (DB.Apk apk : app.apks) { if (apk.repo == keep) { keepapp = true; break; } } if (keepapp) { DB.App app_k = null; for (DB.App app2 : apps) { if (app2.id.equals(app.id)) { app_k = app2; break; } } if (app_k == null) { apps.add(app); app_k = app; } app_k.updated = true; db.populateDetails(app_k, keep); for (DB.Apk apk : app.apks) if (apk.repo == keep) apk.updated = true; } } } prevUpdates = db.beginUpdate(prevapps); for (DB.App app : apps) { if (db.updateApplication(app)) acceptedapps.add(app); } db.endUpdate(); if (notify) newUpdates = db.getNumUpdates(); for (DB.Repo repo : repos) db.writeLastEtag(repo); } catch (Exception ex) { db.cancelUpdate(); Log.e("FDroid", "Exception during update processing:\n" + Log.getStackTraceString(ex)); errmsg = "Exception during processing - " + ex.getMessage(); success = false; } finally { DB.releaseDB(); } } if (success) { File d = DB.getIconsPath(this); List<DB.App> toDownloadIcons = null; if (!d.exists()) { Log.d("FDroid", "Icons were wiped. Re-downloading all of them."); d.mkdirs(); toDownloadIcons = ((FDroidApp) getApplication()).getApps(); } else if (changes) { toDownloadIcons = acceptedapps; } if (toDownloadIcons != null) { // Create a .nomedia file in the icons directory. For // recent Android versions this isn't necessary, because // they recognise the cache location. Older versions don't // though. File f = new File(d, ".nomedia"); if (!f.exists()) { try { f.createNewFile(); } catch (Exception e) { Log.d("FDroid", "Failed to create .nomedia"); } } sendStatus(STATUS_INFO, getString(R.string.status_downloading_icons)); for (DB.App app : toDownloadIcons) getIcon(app, repos); } } if (success && changes) ((FDroidApp) getApplication()).invalidateAllApps(); if (success && changes && notify && (newUpdates > prevUpdates)) { Log.d("FDroid", "Notifying updates. Apps before:" + prevUpdates + ", apps after: " + newUpdates); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.icon) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon)) .setAutoCancel(true).setContentTitle(getString(R.string.fdroid_updates_available)); Intent notifyIntent = new Intent(this, FDroid.class).putExtra(FDroid.EXTRA_TAB_UPDATE, true); if (newUpdates > 1) { mBuilder.setContentText(getString(R.string.many_updates_available, newUpdates)); } else { mBuilder.setContentText(getString(R.string.one_update_available)); } TaskStackBuilder stackBuilder = TaskStackBuilder.create(this).addParentStack(FDroid.class) .addNextIntent(notifyIntent); PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(pendingIntent); NotificationManager mNotificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE); mNotificationManager.notify(1, mBuilder.build()); } if (!success) { if (errmsg.length() == 0) errmsg = "Unknown error"; sendStatus(STATUS_ERROR, errmsg); } else { sendStatus(STATUS_COMPLETE); Editor e = prefs.edit(); e.putLong("lastUpdateCheck", System.currentTimeMillis()); e.commit(); } } catch (Exception e) { Log.e("FDroid", "Exception during update processing:\n" + Log.getStackTraceString(e)); if (errmsg.length() == 0) errmsg = "Unknown error"; sendStatus(STATUS_ERROR, errmsg); } finally { Log.d("FDroid", "Update took " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds."); receiver = null; } } private void getIcon(final DB.App app, List<DB.Repo> repos) { InputStream input = null; OutputStream output = null; try { File f = new File(DB.getIconsPath(this), app.icon); if (f.exists()) return; if (app.apks.size() == 0) return; String server = null; for (DB.Repo repo : repos) if (repo.id == app.apks.get(0).repo) server = repo.address; if (server == null) return; // Get it from the server... URL u = new URL(server + "/icons/" + app.icon); HttpURLConnection uc = (HttpURLConnection) u.openConnection(); if (uc.getResponseCode() == 200) { // Delete all other icons for the same app final File[] files = DB.getIconsPath(this).listFiles(new FilenameFilter() { @Override public boolean accept(final File d, final String n) { return n.matches(app.id + "\\.[0-9]+\\.png"); } }); for (final File file : files) { if (!file.delete()) Log.e("FDroid", "Cannot remove icon file " + file.getAbsolutePath()); } input = uc.getInputStream(); output = new FileOutputStream(f); Utils.copy(input, output); } } catch (Exception e) { } finally { Utils.closeQuietly(output); Utils.closeQuietly(input); } } /** * Received progress event from the RepoXMLHandler. It could be progress * downloading from the repo, or perhaps processing the info from the repo. */ @Override public void onProgress(ProgressListener.Event event) { String message = ""; if (event.type == RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD) { String repoAddress = event.data.getString(RepoXMLHandler.PROGRESS_DATA_REPO); String downloadedSize = Utils.getFriendlySize(event.progress); String totalSize = Utils.getFriendlySize(event.total); int percent = (int) ((double) event.progress / event.total * 100); message = getString(R.string.status_download, repoAddress, downloadedSize, totalSize, percent); } else if (event.type == RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML) { String repoAddress = event.data.getString(RepoXMLHandler.PROGRESS_DATA_REPO); message = getString(R.string.status_processing_xml, repoAddress, event.progress, event.total); } sendStatus(STATUS_INFO, message); } }