package com.ringtone.music.download; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.ringtone.music.Constants; import com.ringtone.music.MediaScannerHelper; import com.ringtone.music.Utils; import android.app.Service; import android.content.Context; import android.content.Intent; import android.media.MediaScannerConnection; import android.media.MediaScannerConnection.MediaScannerConnectionClient; import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.os.PowerManager; import android.text.TextUtils; import android.util.Log; public class DownloadService extends Service { private static final int POOL_SIZE = 4; private static final int BUFFER_SIZE = 4096; private static final int MIN_PROGRESS_STEP = 4096; private static final long MIN_PROGRESS_TIME = 1500; private static final String TAG = "DownloadService"; private ArrayList<DownloadInfo> mDownloads = new ArrayList<DownloadInfo>(); private ArrayList<DownloadObserver> mObservers = new ArrayList<DownloadObserver>(); private ExecutorService mPool; private MediaScannerHelper mScanner = new MediaScannerHelper(); public class LocalBinder extends Binder { public DownloadService getService() { return DownloadService.this; } } @Override public void onCreate() { Log.i(TAG, "Download service started"); mPool = Executors.newFixedThreadPool(POOL_SIZE); } public void registerDownloadObserver(DownloadObserver observer) { if (observer == null) { throw new IllegalArgumentException("The observer is null."); } synchronized(mObservers) { if (mObservers.contains(observer)) { throw new IllegalStateException("Observer " + observer + " is already registered."); } mObservers.add(observer); } } public void unregisterObserver(DownloadObserver observer) { if (observer == null) { throw new IllegalArgumentException("The observer is null."); } synchronized(mObservers) { int index = mObservers.indexOf(observer); if (index == -1) { throw new IllegalStateException("Observer " + observer + " was not registered."); } mObservers.remove(index); } } /** * Remove all registered observer */ public void unregisterAll() { synchronized(mObservers) { mObservers.clear(); } } private void notifyChanged() { synchronized(mObservers) { for (DownloadObserver o : mObservers) { o.onChange(); } } } public ArrayList<DownloadInfo> getDownloadInfos() { synchronized(mDownloads) { return new ArrayList<DownloadInfo>(mDownloads); } } public void insertDownload(DownloadInfo info) { if (info == null) return; if (TextUtils.isEmpty(info.getSource()) || TextUtils.isEmpty(info.getTarget())) { Log.e(TAG, "Empty source or target"); return; } synchronized(mDownloads) { // Check if the request has already been added. for (DownloadInfo d : mDownloads) { if (d.getTarget().equals(info.getTarget())) return; } mDownloads.add(info); // This should not block. mPool.execute(new Task(info)); } notifyChanged(); } public void resumeDownload(DownloadInfo info) { synchronized(info) { mPool.execute(new Task(info)); } } public void removeDownload(DownloadInfo info) { if (info == null) return; synchronized(mDownloads) { int index = mDownloads.indexOf(info); if (index == -1) { return; } mDownloads.remove(index); } notifyChanged(); } public void clearFinished() { ArrayList<DownloadInfo> downloads = new ArrayList<DownloadInfo>(); boolean changed = false; synchronized(mDownloads) { for (DownloadInfo d : mDownloads) { if (d.getStatus() != DownloadInfo.STATUS_FINISHED) { downloads.add(d); } else { changed = true; } } if (changed) { mDownloads = downloads; } } if (changed) { notifyChanged(); } } @Override public IBinder onBind(Intent intent) { return mBinder; } private final IBinder mBinder = new LocalBinder(); private class Task implements Runnable { private DownloadInfo mInfo; private void download() throws IOException { if (mInfo == null) return; URL url; synchronized(mInfo) { mInfo.setThread(Thread.currentThread()); url = new URL(mInfo.getSource()); } HttpURLConnection connection = (HttpURLConnection)url.openConnection(); connection.setRequestProperty("User-Agent", Constants.USER_AGENT); RandomAccessFile outFile = null; InputStream input = null; String tmpFile = mInfo.getTarget() + ".tmp"; try { outFile = new RandomAccessFile(tmpFile, "rw"); if (outFile.length() > 0) connection.setRequestProperty("Range", "bytes=" + outFile.length() + "-"); connection.connect(); if (connection.getResponseCode() < 200 || connection.getResponseCode() >= 300) { synchronized(mInfo) { mInfo.setStatus(DownloadInfo.STATUS_FAILED); } mInfo.setError("Connection error (" + connection.getResponseCode() + "): " + connection.getResponseMessage()); return; } try { input = connection.getInputStream(); } catch (FileNotFoundException e) { if (outFile.length() > 0) { // The remote server does not support Range. outFile.close(); new File(tmpFile).delete(); outFile = new RandomAccessFile(tmpFile, "rw"); connection = (HttpURLConnection)url.openConnection(); connection.setRequestProperty("User-Agent", Constants.USER_AGENT); connection.connect(); input = connection.getInputStream(); } else { throw e; } } synchronized(mInfo) { mInfo.setCurrentBytes((int)outFile.length()); mInfo.setTotalBytes((int)outFile.length() + connection.getContentLength()); mInfo.setStatus(DownloadInfo.STATUS_DOWNLOADING); notifyChanged(); } outFile.seek(outFile.length()); byte[] buffer = new byte[BUFFER_SIZE]; int len; int bytesUnaccounted = 0; long timeLastNotification = System.currentTimeMillis(); while ((len = input.read(buffer)) >= 0) { synchronized(mInfo) { if (mInfo.getStatus() == DownloadInfo.STATUS_STOPPED) { return; } } outFile.write(buffer, 0, len); bytesUnaccounted += len; long now = System.currentTimeMillis(); if (bytesUnaccounted > MIN_PROGRESS_STEP && now - timeLastNotification > MIN_PROGRESS_TIME) { synchronized(mInfo) { mInfo.setCurrentBytes(mInfo.getCurrentBytes() + bytesUnaccounted); } bytesUnaccounted = 0; timeLastNotification = now; notifyChanged(); } } synchronized(mInfo) { if (mInfo.getCurrentBytes() < 100) { mInfo.setStatus(DownloadInfo.STATUS_FAILED); mInfo.setError("Incomplete file"); return; } mInfo.setCurrentBytes(mInfo.getTotalBytes()); mInfo.setStatus(DownloadInfo.STATUS_FINISHED); File oldFile = new File(tmpFile); if (oldFile.renameTo(new File(mInfo.getTarget()))) { mScanner.ScanMediaFile(DownloadService.this, mInfo.getTarget()); } } } finally { if (outFile != null) outFile.close(); if (input != null) input.close(); } } public Task(DownloadInfo download) { mInfo = download; } @Override public void run() { Utils.D("++++ task: " + mInfo); PowerManager.WakeLock wakeLock = null; try { PowerManager pm = (PowerManager)DownloadService.this.getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); wakeLock.acquire(); download(); } catch (Exception e) { synchronized(mInfo) { mInfo.setStatus(DownloadInfo.STATUS_FAILED); } e.printStackTrace(); mInfo.setError(e.getMessage()); } finally { if (wakeLock != null) { wakeLock.release(); wakeLock = null; } notifyChanged(); Utils.D("task finished: " + mInfo); } } } }