package com.miniabs; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.List; import org.json.JSONObject; import android.app.IntentService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.net.wifi.WifiManager; import android.os.PowerManager; /** Downloads episodes one at a time to the SD card, resuming partial files. */ public class DownloadService extends IntentService { private static final int NOTIF_ID = 2; private static final List queue = new ArrayList(); static volatile Episode current; static volatile int percent; static volatile String lastError; private static volatile String cancelId; public DownloadService() { super("MiniABS downloads"); } static void enqueue(Context c, Episode e) { synchronized (queue) { if (current != null && current.id.equals(e.id)) return; for (Episode q : queue) if (q.id.equals(e.id)) return; queue.add(e); } lastError = null; c.startService(new Intent(c, DownloadService.class).putExtra("ep", e.toJson().toString())); } static void cancel(String epId) { synchronized (queue) { for (int i = queue.size() - 1; i >= 0; i--) if (queue.get(i).id.equals(epId)) queue.remove(i); } Episode cur = current; if (cur != null && cur.id.equals(epId)) cancelId = epId; } static List queued() { synchronized (queue) { return new ArrayList(queue); } } static boolean busyWith(String epId) { Episode cur = current; if (cur != null && cur.id.equals(epId)) return true; synchronized (queue) { for (Episode q : queue) if (q.id.equals(epId)) return true; } return false; } @Override protected void onHandleIntent(Intent intent) { Episode e; try { e = Episode.fromStored(new JSONObject(intent.getStringExtra("ep"))); } catch (Exception ex) { return; } synchronized (queue) { boolean found = false; for (int i = 0; i < queue.size(); i++) { if (queue.get(i).id.equals(e.id)) { queue.remove(i); found = true; break; } } if (!found) return; // cancelled while waiting current = e; } percent = 0; cancelId = null; PowerManager.WakeLock wake = ((PowerManager) getSystemService(POWER_SERVICE)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MiniABS download"); WifiManager.WifiLock wifi = ((WifiManager) getSystemService(WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, "MiniABS download"); wake.acquire(); wifi.acquire(); try { download(e); if (e.id.equals(cancelId)) { Downloads.delete(this, e); } } catch (Exception ex) { if (!e.id.equals(cancelId)) { lastError = "Fallou a descarga: " + Util.describe(ex); notifyDone(e.title, lastError); } } finally { current = null; cancelId = null; stopForeground(true); wifi.release(); wake.release(); } } private void download(Episode e) throws IOException { if (Prefs.get(this).wifiOnly() && !Util.onWifi(this)) { throw new IOException("non hai wifi (Axustes: Descargar só con wifi)"); } File dir = Downloads.dir(this); if (Downloads.audio(this, e) != null) { Downloads.saved(this, e); return; } if (e.ino.length() == 0) throw new IOException("este episodio non ten ficheiro de audio"); File part = new File(dir, e.id + ".part"); Notification n = progressNotification(e, 0); startForeground(NOTIF_ID, n); Api.ensureFresh(this, 600); HttpURLConnection conn = null; int code = 0; long have = part.exists() ? part.length() : 0; boolean refreshed = false; String[] urls = Api.downloadUrls(this, e); for (int i = 0; i < urls.length; i++) { conn = Api.open(this, urls[i], "GET"); conn.setReadTimeout(60000); if (have > 0) conn.setRequestProperty("Range", "bytes=" + have + "-"); code = Api.responseCode(conn); if (code == 401 && !refreshed && Prefs.get(this).canRefresh()) { conn.disconnect(); Api.refresh(this, true); refreshed = true; urls = Api.downloadUrls(this, e); i--; continue; } if (code == 200 || code == 206) break; conn.disconnect(); if (code == 416 && have > 0) { // stale partial file: start over part.delete(); have = 0; i--; continue; } if (code != 404 && code != 403) break; } if (code != 200 && code != 206) { if (code == 401) throw new Api.AuthException("a sesión caducou; inicia sesión de novo"); if (code == 403) throw new IOException("a túa conta non ten permiso para descargar"); throw new Api.ApiException(code, ""); } try { boolean append = code == 206 && have > 0; if (!append) have = 0; long len = conn.getContentLength(); long total = len > 0 ? have + len : (e.size > 0 ? e.size : -1); InputStream in = conn.getInputStream(); OutputStream out = new FileOutputStream(part, append); try { byte[] buf = new byte[16384]; long done = have, lastUi = 0; int r; while ((r = in.read(buf)) > 0) { if (e.id.equals(cancelId)) return; out.write(buf, 0, r); done += r; long now = System.currentTimeMillis(); if (now - lastUi > 1500) { lastUi = now; percent = total > 0 ? (int) (done * 100 / total) : 0; n = progressNotification(e, percent); nm().notify(NOTIF_ID, n); } } } finally { out.close(); in.close(); } } finally { conn.disconnect(); } if (e.id.equals(cancelId)) return; File dest = new File(dir, e.id + e.ext); if (!part.renameTo(dest)) throw new IOException("non se puido gardar o ficheiro"); Downloads.saved(this, e); percent = 100; } private NotificationManager nm() { return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } private PendingIntent openApp() { Intent i = new Intent(this, HomeActivity.class).putExtra("tab", HomeActivity.TAB_SAVED) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return PendingIntent.getActivity(this, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); } private Notification progressNotification(Episode e, int pct) { Notification n = new Notification(android.R.drawable.stat_sys_download, null, System.currentTimeMillis()); n.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_ONLY_ALERT_ONCE; int left; synchronized (queue) { left = queue.size(); } String text = "Descargando " + pct + " %" + (left > 0 ? " · " + left + " máis na cola" : ""); n.setLatestEventInfo(this, e.title, text, openApp()); return n; } private void notifyDone(String title, String text) { Notification n = new Notification(android.R.drawable.stat_notify_error, text, System.currentTimeMillis()); n.flags |= Notification.FLAG_AUTO_CANCEL; n.setLatestEventInfo(this, title, text, openApp()); nm().notify(3, n); } }