/* * Copyright (C) 2010 mAPPn.Inc * * 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 com.lan.nicehair.common.download; import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Process; import com.lan.nicehair.utils.AppLog; import com.lan.nicehair.utils.Utils; /** * Performs the background downloads requested by applications that use the Downloads provider. */ public class DownloadService extends Service { public static final String TAG = null; /** Observer to get notified when the content observer's data changes */ private DownloadManagerContentObserver mObserver; /** Class to handle Notification Manager updates */ private DownloadNotification mNotifier; /** * The Service's view of the list of downloads, mapping download IDs to the corresponding info * object. This is kept independently from the content provider, and the Service only initiates * downloads based on this data, so that it can deal with situation where the data in the * content provider changes or disappears. */ private Map<Long, DownloadInfo> mDownloads = new HashMap<Long, DownloadInfo>(); /** * The thread that updates the internal download list from the content * provider. */ UpdateThread mUpdateThread; /** * Whether the internal download list should be updated from the content * provider. */ private boolean mPendingUpdate; /** * Receives notifications when the data in the content provider changes */ private class DownloadManagerContentObserver extends ContentObserver { public DownloadManagerContentObserver() { super(new Handler()); } /** * Receives notification when the data in the observed content * provider changes. */ public void onChange(final boolean selfChange) { AppLog.d(TAG,"Service ContentObserver received notification"); updateFromProvider(); } } /** * Returns an IBinder instance when someone wants to connect to this * service. Binding to this service is not allowed. * * @throws UnsupportedOperationException */ public IBinder onBind(Intent i) { throw new UnsupportedOperationException("Cannot bind to Download Manager Service"); } /** * Initializes the service when it is first created */ public void onCreate() { super.onCreate(); AppLog.d(TAG,"Service onCreate"); mObserver = new DownloadManagerContentObserver(); getContentResolver().registerContentObserver(DownloadManager.Impl.CONTENT_URI, true, mObserver); mNotifier = new DownloadNotification(this); mNotifier.clearAllNotification(); updateFromProvider(); } @Override public void onStart(Intent intent, int startId) { AppLog.d(TAG,"Service onStart"); updateFromProvider(); } /** * Cleans up when the service is destroyed */ public void onDestroy() { getContentResolver().unregisterContentObserver(mObserver); AppLog.d(TAG,"Service onDestroy"); super.onDestroy(); } /** * Parses data from the content provider into private array */ private void updateFromProvider() { synchronized (this) { mPendingUpdate = true; if (mUpdateThread == null) { mUpdateThread = new UpdateThread(); mUpdateThread.start(); } } } private class UpdateThread extends Thread { public UpdateThread() { super("Download Service"); } public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); trimDatabase(); removeSpuriousFiles(); boolean keepService = false; // for each update from the database, remember which download is // supposed to get restarted soonest in the future long wakeUp = Long.MAX_VALUE; for (;;) { synchronized (DownloadService.this) { if (mUpdateThread != this) { throw new IllegalStateException( "multiple UpdateThreads in DownloadService"); } if (!mPendingUpdate) { mUpdateThread = null; if (!keepService) { stopSelf(); } if (wakeUp != Long.MAX_VALUE) { scheduleAlarm(wakeUp); } return; } mPendingUpdate = false; } long now = System.currentTimeMillis(); keepService = false; wakeUp = Long.MAX_VALUE; Set<Long> idsNoLongerInDatabase = new HashSet<Long>(mDownloads.keySet()); Cursor cursor = getContentResolver().query(DownloadManager.Impl.CONTENT_URI, null, null, null, null); if (cursor == null) { continue; } try { DownloadInfo.Reader reader = new DownloadInfo.Reader(cursor); int idColumn = cursor.getColumnIndexOrThrow(DownloadManager.Impl._ID); for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { long id = cursor.getLong(idColumn); idsNoLongerInDatabase.remove(id); DownloadInfo info = mDownloads.get(id); if (info != null) { updateDownload(reader, info, now); } else { info = insertDownload(reader, now); } if (info.hasCompletionNotification()) { keepService = true; } long next = info.nextAction(now); if (next == 0) { keepService = true; } else if (next > 0 && next < wakeUp) { wakeUp = next; } } } finally { cursor.close(); } for (Long id : idsNoLongerInDatabase) { deleteDownload(id); } mNotifier.updateNotification(mDownloads.values()); // look for all rows with deleted flag set and delete the rows from the database // permanently for (DownloadInfo info : mDownloads.values()) { if (info.mDeleted) { getContentResolver().delete(DownloadManager.Impl.CONTENT_URI, DownloadManager.Impl._ID + " = ? ", new String[] { String.valueOf(info.mId) }); } } } } /* * Schedule the retry task */ private void scheduleAlarm(long wakeUp) { AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); if (alarms == null) { AppLog.e(TAG, "couldn't get alarm manager"); return; } AppLog.d(TAG,"scheduling retry in " + wakeUp + "ms"); Intent intent = new Intent(Constants.ACTION_RETRY); intent.setClassName("com.mappn.gfan", DownloadReceiver.class.getName()); alarms.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + wakeUp, PendingIntent.getBroadcast(DownloadService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT)); } } /** * Removes files that may have been left behind in the cache directory */ private void removeSpuriousFiles() { File[] files = Environment.getDownloadCacheDirectory().listFiles(); if (files == null) { // The cache folder doesn't appear to exist (this is likely the case // when running the simulator). return; } // 获取缓存文件夹下所有的文件 HashSet<String> fileSet = new HashSet<String>(); for (int i = 0; i < files.length; i++) { if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) { continue; } if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) { continue; } fileSet.add(files[i].getPath()); } // 筛选出可以废弃的文件 Cursor cursor = getContentResolver().query(DownloadManager.Impl.CONTENT_URI, new String[] { DownloadManager.Impl.COLUMN_DATA }, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { do { fileSet.remove(cursor.getString(0)); } while (cursor.moveToNext()); } cursor.close(); } Iterator<String> iterator = fileSet.iterator(); while (iterator.hasNext()) { String filename = iterator.next(); AppLog.d(TAG,"deleting spurious file " + filename); new File(filename).delete(); } } /** * Drops old rows from the database to prevent it from growing too large */ private void trimDatabase() { Cursor cursor = getContentResolver().query(DownloadManager.Impl.CONTENT_URI, new String[] { DownloadManager.Impl._ID }, DownloadManager.Impl.COLUMN_STATUS + " >= '200'", null, DownloadManager.Impl.COLUMN_LAST_MODIFICATION); if (cursor == null) { // This isn't good - if we can't do basic queries in our database, nothing's gonna work AppLog.e(TAG,"null cursor in trimDatabase"); return; } if (cursor.moveToFirst()) { int numDelete = cursor.getCount() - Constants.MAX_DOWNLOADS; int columnId = cursor.getColumnIndexOrThrow(DownloadManager.Impl._ID); while (numDelete > 0) { Uri downloadUri = ContentUris.withAppendedId( DownloadManager.Impl.CONTENT_URI, cursor.getLong(columnId)); getContentResolver().delete(downloadUri, null, null); if (!cursor.moveToNext()) { break; } numDelete--; } } cursor.close(); } /** * Keeps a local copy of the info about a download, and initiates the * download if appropriate. */ private DownloadInfo insertDownload(DownloadInfo.Reader reader, long now) { DownloadInfo info = reader.newDownloadInfo(this); mDownloads.put(info.mId, info); info.logVerboseInfo(); info.startIfReady(now); return info; } /** * Updates the local copy of the info about a download. */ private void updateDownload(DownloadInfo.Reader reader, DownloadInfo info, long now) { int oldVisibility = info.mVisibility; int oldStatus = info.mStatus; reader.updateFromDatabase(info); boolean lostVisibility = oldVisibility == DownloadManager.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED && info.mVisibility != DownloadManager.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED && DownloadManager.Impl.isStatusCompleted(info.mStatus); boolean justCompleted = !DownloadManager.Impl.isStatusCompleted(oldStatus) && DownloadManager.Impl.isStatusCompleted(info.mStatus); if (lostVisibility || justCompleted) { mNotifier.cancelNotification(info.mId); } info.startIfReady(now); } /** * Removes the local copy of the info about a download. */ private void deleteDownload(long id) { DownloadInfo info = mDownloads.get(id); if (info.mStatus == DownloadManager.Impl.STATUS_RUNNING) { info.mStatus = DownloadManager.Impl.STATUS_CANCELED; } if (info.mDestination != DownloadManager.Impl.DESTINATION_EXTERNAL && info.mFileName != null) { new File(info.mFileName).delete(); } mNotifier.cancelNotification(info.mId); mDownloads.remove(info.mId); } }