DownloadService.java revision 93155e1da7e89d4925e244f5afa94afb8ada7381
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.providers.downloads; 18 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.app.Service; 22import android.content.ComponentName; 23import android.content.ContentUris; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.Intent; 27import android.content.ServiceConnection; 28import android.content.pm.PackageManager; 29import android.content.pm.ResolveInfo; 30import android.database.CharArrayBuffer; 31import android.database.ContentObserver; 32import android.database.Cursor; 33import android.drm.mobile1.DrmRawContent; 34import android.media.IMediaScannerService; 35import android.net.Uri; 36import android.os.Environment; 37import android.os.Handler; 38import android.os.IBinder; 39import android.os.Process; 40import android.os.RemoteException; 41import android.provider.Downloads; 42import android.util.Config; 43import android.util.Log; 44 45import com.google.android.collect.Lists; 46import com.google.common.annotations.VisibleForTesting; 47 48import java.io.File; 49import java.util.ArrayList; 50import java.util.HashSet; 51import java.util.Iterator; 52 53 54/** 55 * Performs the background downloads requested by applications that use the Downloads provider. 56 */ 57public class DownloadService extends Service { 58 59 /* ------------ Constants ------------ */ 60 61 /* ------------ Members ------------ */ 62 63 /** Observer to get notified when the content observer's data changes */ 64 private DownloadManagerContentObserver mObserver; 65 66 /** Class to handle Notification Manager updates */ 67 private DownloadNotification mNotifier; 68 69 /** 70 * The Service's view of the list of downloads. This is kept independently 71 * from the content provider, and the Service only initiates downloads 72 * based on this data, so that it can deal with situation where the data 73 * in the content provider changes or disappears. 74 */ 75 private ArrayList<DownloadInfo> mDownloads; 76 77 /** 78 * The thread that updates the internal download list from the content 79 * provider. 80 */ 81 @VisibleForTesting 82 UpdateThread mUpdateThread; 83 84 /** 85 * Whether the internal download list should be updated from the content 86 * provider. 87 */ 88 private boolean mPendingUpdate; 89 90 /** 91 * The ServiceConnection object that tells us when we're connected to and disconnected from 92 * the Media Scanner 93 */ 94 private MediaScannerConnection mMediaScannerConnection; 95 96 private boolean mMediaScannerConnecting; 97 98 /** 99 * The IPC interface to the Media Scanner 100 */ 101 private IMediaScannerService mMediaScannerService; 102 103 /** 104 * Array used when extracting strings from content provider 105 */ 106 private CharArrayBuffer oldChars; 107 108 /** 109 * Array used when extracting strings from content provider 110 */ 111 private CharArrayBuffer mNewChars; 112 113 @VisibleForTesting 114 SystemFacade mSystemFacade; 115 116 /* ------------ Inner Classes ------------ */ 117 118 /** 119 * Receives notifications when the data in the content provider changes 120 */ 121 private class DownloadManagerContentObserver extends ContentObserver { 122 123 public DownloadManagerContentObserver() { 124 super(new Handler()); 125 } 126 127 /** 128 * Receives notification when the data in the observed content 129 * provider changes. 130 */ 131 public void onChange(final boolean selfChange) { 132 if (Constants.LOGVV) { 133 Log.v(Constants.TAG, "Service ContentObserver received notification"); 134 } 135 updateFromProvider(); 136 } 137 138 } 139 140 /** 141 * Gets called back when the connection to the media 142 * scanner is established or lost. 143 */ 144 public class MediaScannerConnection implements ServiceConnection { 145 public void onServiceConnected(ComponentName className, IBinder service) { 146 if (Constants.LOGVV) { 147 Log.v(Constants.TAG, "Connected to Media Scanner"); 148 } 149 mMediaScannerConnecting = false; 150 synchronized (DownloadService.this) { 151 mMediaScannerService = IMediaScannerService.Stub.asInterface(service); 152 if (mMediaScannerService != null) { 153 updateFromProvider(); 154 } 155 } 156 } 157 158 public void disconnectMediaScanner() { 159 synchronized (DownloadService.this) { 160 if (mMediaScannerService != null) { 161 mMediaScannerService = null; 162 if (Constants.LOGVV) { 163 Log.v(Constants.TAG, "Disconnecting from Media Scanner"); 164 } 165 try { 166 unbindService(this); 167 } catch (IllegalArgumentException ex) { 168 if (Constants.LOGV) { 169 Log.v(Constants.TAG, "unbindService threw up: " + ex); 170 } 171 } 172 } 173 } 174 } 175 176 public void onServiceDisconnected(ComponentName className) { 177 if (Constants.LOGVV) { 178 Log.v(Constants.TAG, "Disconnected from Media Scanner"); 179 } 180 synchronized (DownloadService.this) { 181 mMediaScannerService = null; 182 } 183 } 184 } 185 186 /* ------------ Methods ------------ */ 187 188 /** 189 * Returns an IBinder instance when someone wants to connect to this 190 * service. Binding to this service is not allowed. 191 * 192 * @throws UnsupportedOperationException 193 */ 194 public IBinder onBind(Intent i) { 195 throw new UnsupportedOperationException("Cannot bind to Download Manager Service"); 196 } 197 198 /** 199 * Initializes the service when it is first created 200 */ 201 public void onCreate() { 202 super.onCreate(); 203 if (Constants.LOGVV) { 204 Log.v(Constants.TAG, "Service onCreate"); 205 } 206 207 if (mSystemFacade == null) { 208 mSystemFacade = new RealSystemFacade(this); 209 } 210 211 mDownloads = Lists.newArrayList(); 212 213 mObserver = new DownloadManagerContentObserver(); 214 getContentResolver().registerContentObserver(Downloads.Impl.CONTENT_URI, 215 true, mObserver); 216 217 mMediaScannerService = null; 218 mMediaScannerConnecting = false; 219 mMediaScannerConnection = new MediaScannerConnection(); 220 221 mNotifier = new DownloadNotification(this, mSystemFacade); 222 mSystemFacade.cancelAllNotifications(); 223 mNotifier.updateNotification(); 224 225 trimDatabase(); 226 removeSpuriousFiles(); 227 updateFromProvider(); 228 } 229 230 /** 231 * Responds to a call to startService 232 */ 233 public void onStart(Intent intent, int startId) { 234 super.onStart(intent, startId); 235 if (Constants.LOGVV) { 236 Log.v(Constants.TAG, "Service onStart"); 237 } 238 239 updateFromProvider(); 240 } 241 242 /** 243 * Cleans up when the service is destroyed 244 */ 245 public void onDestroy() { 246 getContentResolver().unregisterContentObserver(mObserver); 247 if (Constants.LOGVV) { 248 Log.v(Constants.TAG, "Service onDestroy"); 249 } 250 super.onDestroy(); 251 } 252 253 /** 254 * Parses data from the content provider into private array 255 */ 256 private void updateFromProvider() { 257 synchronized (this) { 258 mPendingUpdate = true; 259 if (mUpdateThread == null) { 260 mUpdateThread = new UpdateThread(); 261 mUpdateThread.start(); 262 } 263 } 264 } 265 266 private class UpdateThread extends Thread { 267 public UpdateThread() { 268 super("Download Service"); 269 } 270 271 public void run() { 272 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 273 274 boolean keepService = false; 275 // for each update from the database, remember which download is 276 // supposed to get restarted soonest in the future 277 long wakeUp = Long.MAX_VALUE; 278 for (;;) { 279 synchronized (DownloadService.this) { 280 if (mUpdateThread != this) { 281 throw new IllegalStateException( 282 "multiple UpdateThreads in DownloadService"); 283 } 284 if (!mPendingUpdate) { 285 mUpdateThread = null; 286 if (!keepService) { 287 stopSelf(); 288 } 289 if (wakeUp != Long.MAX_VALUE) { 290 AlarmManager alarms = 291 (AlarmManager) getSystemService(Context.ALARM_SERVICE); 292 if (alarms == null) { 293 Log.e(Constants.TAG, "couldn't get alarm manager"); 294 } else { 295 if (Constants.LOGV) { 296 Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms"); 297 } 298 Intent intent = new Intent(Constants.ACTION_RETRY); 299 intent.setClassName("com.android.providers.downloads", 300 DownloadReceiver.class.getName()); 301 alarms.set( 302 AlarmManager.RTC_WAKEUP, 303 mSystemFacade.currentTimeMillis() + wakeUp, 304 PendingIntent.getBroadcast(DownloadService.this, 0, intent, 305 PendingIntent.FLAG_ONE_SHOT)); 306 } 307 } 308 oldChars = null; 309 mNewChars = null; 310 return; 311 } 312 mPendingUpdate = false; 313 } 314 long now = mSystemFacade.currentTimeMillis(); 315 316 Cursor cursor = getContentResolver().query(Downloads.Impl.CONTENT_URI, 317 null, null, null, Downloads.Impl._ID); 318 319 if (cursor == null) { 320 // TODO: this doesn't look right, it'd leave the loop in an inconsistent state 321 return; 322 } 323 324 cursor.moveToFirst(); 325 326 int arrayPos = 0; 327 328 boolean mustScan = false; 329 keepService = false; 330 wakeUp = Long.MAX_VALUE; 331 332 boolean isAfterLast = cursor.isAfterLast(); 333 334 int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); 335 336 /* 337 * Walk the cursor and the local array to keep them in sync. The key 338 * to the algorithm is that the ids are unique and sorted both in 339 * the cursor and in the array, so that they can be processed in 340 * order in both sources at the same time: at each step, both 341 * sources point to the lowest id that hasn't been processed from 342 * that source, and the algorithm processes the lowest id from 343 * those two possibilities. 344 * At each step: 345 * -If the array contains an entry that's not in the cursor, remove the 346 * entry, move to next entry in the array. 347 * -If the array contains an entry that's in the cursor, nothing to do, 348 * move to next cursor row and next array entry. 349 * -If the cursor contains an entry that's not in the array, insert 350 * a new entry in the array, move to next cursor row and next 351 * array entry. 352 */ 353 while (!isAfterLast || arrayPos < mDownloads.size()) { 354 if (isAfterLast) { 355 // We're beyond the end of the cursor but there's still some 356 // stuff in the local array, which can only be junk 357 if (Constants.LOGVV) { 358 int arrayId = ((DownloadInfo) mDownloads.get(arrayPos)).mId; 359 Log.v(Constants.TAG, "Array update: trimming " + 360 arrayId + " @ " + arrayPos); 361 } 362 if (shouldScanFile(arrayPos) && mediaScannerConnected()) { 363 scanFile(null, arrayPos); 364 } 365 deleteDownload(arrayPos); // this advances in the array 366 } else { 367 int id = cursor.getInt(idColumn); 368 369 if (arrayPos == mDownloads.size()) { 370 insertDownload(cursor, arrayPos, now); 371 if (Constants.LOGVV) { 372 Log.v(Constants.TAG, "Array update: appending " + 373 id + " @ " + arrayPos); 374 } 375 if (shouldScanFile(arrayPos) 376 && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) { 377 mustScan = true; 378 keepService = true; 379 } 380 if (visibleNotification(arrayPos)) { 381 keepService = true; 382 } 383 long next = nextAction(arrayPos, now); 384 if (next == 0) { 385 keepService = true; 386 } else if (next > 0 && next < wakeUp) { 387 wakeUp = next; 388 } 389 ++arrayPos; 390 cursor.moveToNext(); 391 isAfterLast = cursor.isAfterLast(); 392 } else { 393 int arrayId = mDownloads.get(arrayPos).mId; 394 395 if (arrayId < id) { 396 // The array entry isn't in the cursor 397 if (Constants.LOGVV) { 398 Log.v(Constants.TAG, "Array update: removing " + arrayId 399 + " @ " + arrayPos); 400 } 401 if (shouldScanFile(arrayPos) && mediaScannerConnected()) { 402 scanFile(null, arrayPos); 403 } 404 deleteDownload(arrayPos); // this advances in the array 405 } else if (arrayId == id) { 406 // This cursor row already exists in the stored array 407 updateDownload(cursor, arrayPos, now); 408 if (shouldScanFile(arrayPos) 409 && (!mediaScannerConnected() 410 || !scanFile(cursor, arrayPos))) { 411 mustScan = true; 412 keepService = true; 413 } 414 if (visibleNotification(arrayPos)) { 415 keepService = true; 416 } 417 long next = nextAction(arrayPos, now); 418 if (next == 0) { 419 keepService = true; 420 } else if (next > 0 && next < wakeUp) { 421 wakeUp = next; 422 } 423 ++arrayPos; 424 cursor.moveToNext(); 425 isAfterLast = cursor.isAfterLast(); 426 } else { 427 // This cursor entry didn't exist in the stored array 428 if (Constants.LOGVV) { 429 Log.v(Constants.TAG, "Array update: inserting " + 430 id + " @ " + arrayPos); 431 } 432 insertDownload(cursor, arrayPos, now); 433 if (shouldScanFile(arrayPos) 434 && (!mediaScannerConnected() 435 || !scanFile(cursor, arrayPos))) { 436 mustScan = true; 437 keepService = true; 438 } 439 if (visibleNotification(arrayPos)) { 440 keepService = true; 441 } 442 long next = nextAction(arrayPos, now); 443 if (next == 0) { 444 keepService = true; 445 } else if (next > 0 && next < wakeUp) { 446 wakeUp = next; 447 } 448 ++arrayPos; 449 cursor.moveToNext(); 450 isAfterLast = cursor.isAfterLast(); 451 } 452 } 453 } 454 } 455 456 mNotifier.updateNotification(); 457 458 if (mustScan) { 459 if (!mMediaScannerConnecting) { 460 Intent intent = new Intent(); 461 intent.setClassName("com.android.providers.media", 462 "com.android.providers.media.MediaScannerService"); 463 mMediaScannerConnecting = true; 464 bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE); 465 } 466 } else { 467 mMediaScannerConnection.disconnectMediaScanner(); 468 } 469 470 cursor.close(); 471 } 472 } 473 } 474 475 /** 476 * Removes files that may have been left behind in the cache directory 477 */ 478 private void removeSpuriousFiles() { 479 File[] files = Environment.getDownloadCacheDirectory().listFiles(); 480 if (files == null) { 481 // The cache folder doesn't appear to exist (this is likely the case 482 // when running the simulator). 483 return; 484 } 485 HashSet<String> fileSet = new HashSet(); 486 for (int i = 0; i < files.length; i++) { 487 if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) { 488 continue; 489 } 490 if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) { 491 continue; 492 } 493 fileSet.add(files[i].getPath()); 494 } 495 496 Cursor cursor = getContentResolver().query(Downloads.Impl.CONTENT_URI, 497 new String[] { Downloads.Impl._DATA }, null, null, null); 498 if (cursor != null) { 499 if (cursor.moveToFirst()) { 500 do { 501 fileSet.remove(cursor.getString(0)); 502 } while (cursor.moveToNext()); 503 } 504 cursor.close(); 505 } 506 Iterator<String> iterator = fileSet.iterator(); 507 while (iterator.hasNext()) { 508 String filename = iterator.next(); 509 if (Constants.LOGV) { 510 Log.v(Constants.TAG, "deleting spurious file " + filename); 511 } 512 new File(filename).delete(); 513 } 514 } 515 516 /** 517 * Drops old rows from the database to prevent it from growing too large 518 */ 519 private void trimDatabase() { 520 Cursor cursor = getContentResolver().query(Downloads.Impl.CONTENT_URI, 521 new String[] { Downloads.Impl._ID }, 522 Downloads.Impl.COLUMN_STATUS + " >= '200'", null, 523 Downloads.Impl.COLUMN_LAST_MODIFICATION); 524 if (cursor == null) { 525 // This isn't good - if we can't do basic queries in our database, nothing's gonna work 526 Log.e(Constants.TAG, "null cursor in trimDatabase"); 527 return; 528 } 529 if (cursor.moveToFirst()) { 530 int numDelete = cursor.getCount() - Constants.MAX_DOWNLOADS; 531 int columnId = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); 532 while (numDelete > 0) { 533 getContentResolver().delete( 534 ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, 535 cursor.getLong(columnId)), null, null); 536 if (!cursor.moveToNext()) { 537 break; 538 } 539 numDelete--; 540 } 541 } 542 cursor.close(); 543 } 544 545 /** 546 * Keeps a local copy of the info about a download, and initiates the 547 * download if appropriate. 548 */ 549 private void insertDownload(Cursor cursor, int arrayPos, long now) { 550 DownloadInfo info = new DownloadInfo(this, mSystemFacade, cursor); 551 552 if (Constants.LOGVV) { 553 Log.v(Constants.TAG, "Service adding new entry"); 554 Log.v(Constants.TAG, "ID : " + info.mId); 555 Log.v(Constants.TAG, "URI : " + ((info.mUri != null) ? "yes" : "no")); 556 Log.v(Constants.TAG, "NO_INTEG: " + info.mNoIntegrity); 557 Log.v(Constants.TAG, "HINT : " + info.mHint); 558 Log.v(Constants.TAG, "FILENAME: " + info.mFileName); 559 Log.v(Constants.TAG, "MIMETYPE: " + info.mMimeType); 560 Log.v(Constants.TAG, "DESTINAT: " + info.mDestination); 561 Log.v(Constants.TAG, "VISIBILI: " + info.mVisibility); 562 Log.v(Constants.TAG, "CONTROL : " + info.mControl); 563 Log.v(Constants.TAG, "STATUS : " + info.mStatus); 564 Log.v(Constants.TAG, "FAILED_C: " + info.mNumFailed); 565 Log.v(Constants.TAG, "RETRY_AF: " + info.mRetryAfter); 566 Log.v(Constants.TAG, "REDIRECT: " + info.mRedirectCount); 567 Log.v(Constants.TAG, "LAST_MOD: " + info.mLastMod); 568 Log.v(Constants.TAG, "PACKAGE : " + info.mPackage); 569 Log.v(Constants.TAG, "CLASS : " + info.mClass); 570 Log.v(Constants.TAG, "COOKIES : " + ((info.mCookies != null) ? "yes" : "no")); 571 Log.v(Constants.TAG, "AGENT : " + info.mUserAgent); 572 Log.v(Constants.TAG, "REFERER : " + ((info.mReferer != null) ? "yes" : "no")); 573 Log.v(Constants.TAG, "TOTAL : " + info.mTotalBytes); 574 Log.v(Constants.TAG, "CURRENT : " + info.mCurrentBytes); 575 Log.v(Constants.TAG, "ETAG : " + info.mETag); 576 Log.v(Constants.TAG, "SCANNED : " + info.mMediaScanned); 577 } 578 579 mDownloads.add(arrayPos, info); 580 581 if (info.mStatus == 0 582 && (info.mDestination == Downloads.Impl.DESTINATION_EXTERNAL 583 || info.mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) 584 && info.mMimeType != null 585 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType)) { 586 // Check to see if we are allowed to download this file. Only files 587 // that can be handled by the platform can be downloaded. 588 // special case DRM files, which we should always allow downloading. 589 Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW); 590 591 // We can provide data as either content: or file: URIs, 592 // so allow both. (I think it would be nice if we just did 593 // everything as content: URIs) 594 // Actually, right now the download manager's UId restrictions 595 // prevent use from using content: so it's got to be file: or 596 // nothing 597 598 mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mMimeType); 599 ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent, 600 PackageManager.MATCH_DEFAULT_ONLY); 601 //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list); 602 603 if (ri == null) { 604 if (Config.LOGD) { 605 Log.d(Constants.TAG, "no application to handle MIME type " + info.mMimeType); 606 } 607 info.mStatus = Downloads.Impl.STATUS_NOT_ACCEPTABLE; 608 609 Uri uri = ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, info.mId); 610 ContentValues values = new ContentValues(); 611 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_NOT_ACCEPTABLE); 612 getContentResolver().update(uri, values, null, null); 613 info.sendIntentIfRequested(uri); 614 return; 615 } 616 } 617 618 if (info.isReadyToStart(now)) { 619 info.start(now); 620 } 621 } 622 623 /** 624 * Updates the local copy of the info about a download. 625 */ 626 private void updateDownload(Cursor cursor, int arrayPos, long now) { 627 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 628 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); 629 int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS); 630 info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl._ID)); 631 info.mUri = stringFromCursor(info.mUri, cursor, Downloads.Impl.COLUMN_URI); 632 info.mNoIntegrity = cursor.getInt(cursor.getColumnIndexOrThrow( 633 Downloads.Impl.COLUMN_NO_INTEGRITY)) == 1; 634 info.mHint = stringFromCursor(info.mHint, cursor, Downloads.Impl.COLUMN_FILE_NAME_HINT); 635 info.mFileName = stringFromCursor(info.mFileName, cursor, Downloads.Impl._DATA); 636 info.mMimeType = stringFromCursor(info.mMimeType, cursor, Downloads.Impl.COLUMN_MIME_TYPE); 637 info.mDestination = cursor.getInt(cursor.getColumnIndexOrThrow( 638 Downloads.Impl.COLUMN_DESTINATION)); 639 int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow( 640 Downloads.Impl.COLUMN_VISIBILITY)); 641 if (info.mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED 642 && newVisibility != Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED 643 && Downloads.Impl.isStatusCompleted(info.mStatus)) { 644 mSystemFacade.cancelNotification(info.mId); 645 } 646 info.mVisibility = newVisibility; 647 synchronized (info) { 648 info.mControl = cursor.getInt(cursor.getColumnIndexOrThrow( 649 Downloads.Impl.COLUMN_CONTROL)); 650 } 651 int newStatus = cursor.getInt(statusColumn); 652 if (!Downloads.Impl.isStatusCompleted(info.mStatus) && 653 Downloads.Impl.isStatusCompleted(newStatus)) { 654 mSystemFacade.cancelNotification(info.mId); 655 } 656 info.mStatus = newStatus; 657 info.mNumFailed = cursor.getInt(failedColumn); 658 int retryRedirect = 659 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT)); 660 info.mRetryAfter = retryRedirect & 0xfffffff; 661 info.mRedirectCount = retryRedirect >> 28; 662 info.mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow( 663 Downloads.Impl.COLUMN_LAST_MODIFICATION)); 664 info.mPackage = stringFromCursor( 665 info.mPackage, cursor, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); 666 info.mClass = stringFromCursor( 667 info.mClass, cursor, Downloads.Impl.COLUMN_NOTIFICATION_CLASS); 668 info.mCookies = stringFromCursor(info.mCookies, cursor, Downloads.Impl.COLUMN_COOKIE_DATA); 669 info.mUserAgent = stringFromCursor( 670 info.mUserAgent, cursor, Downloads.Impl.COLUMN_USER_AGENT); 671 info.mReferer = stringFromCursor(info.mReferer, cursor, Downloads.Impl.COLUMN_REFERER); 672 info.mTotalBytes = cursor.getInt(cursor.getColumnIndexOrThrow( 673 Downloads.Impl.COLUMN_TOTAL_BYTES)); 674 info.mCurrentBytes = cursor.getInt(cursor.getColumnIndexOrThrow( 675 Downloads.Impl.COLUMN_CURRENT_BYTES)); 676 info.mETag = stringFromCursor(info.mETag, cursor, Constants.ETAG); 677 info.mMediaScanned = 678 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1; 679 680 if (info.isReadyToRestart(now)) { 681 info.start(now); 682 } 683 } 684 685 /** 686 * Returns a String that holds the current value of the column, 687 * optimizing for the case where the value hasn't changed. 688 */ 689 private String stringFromCursor(String old, Cursor cursor, String column) { 690 int index = cursor.getColumnIndexOrThrow(column); 691 if (old == null) { 692 return cursor.getString(index); 693 } 694 if (mNewChars == null) { 695 mNewChars = new CharArrayBuffer(128); 696 } 697 cursor.copyStringToBuffer(index, mNewChars); 698 int length = mNewChars.sizeCopied; 699 if (length != old.length()) { 700 return cursor.getString(index); 701 } 702 if (oldChars == null || oldChars.sizeCopied < length) { 703 oldChars = new CharArrayBuffer(length); 704 } 705 char[] oldArray = oldChars.data; 706 char[] newArray = mNewChars.data; 707 old.getChars(0, length, oldArray, 0); 708 for (int i = length - 1; i >= 0; --i) { 709 if (oldArray[i] != newArray[i]) { 710 return new String(newArray, 0, length); 711 } 712 } 713 return old; 714 } 715 716 /** 717 * Removes the local copy of the info about a download. 718 */ 719 private void deleteDownload(int arrayPos) { 720 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 721 if (info.mStatus == Downloads.Impl.STATUS_RUNNING) { 722 info.mStatus = Downloads.Impl.STATUS_CANCELED; 723 } else if (info.mDestination != Downloads.Impl.DESTINATION_EXTERNAL 724 && info.mFileName != null) { 725 new File(info.mFileName).delete(); 726 } 727 mSystemFacade.cancelNotification(info.mId); 728 729 mDownloads.remove(arrayPos); 730 } 731 732 /** 733 * Returns the amount of time (as measured from the "now" parameter) 734 * at which a download will be active. 735 * 0 = immediately - service should stick around to handle this download. 736 * -1 = never - service can go away without ever waking up. 737 * positive value - service must wake up in the future, as specified in ms from "now" 738 */ 739 private long nextAction(int arrayPos, long now) { 740 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 741 if (Downloads.Impl.isStatusCompleted(info.mStatus)) { 742 return -1; 743 } 744 if (info.mStatus != Downloads.Impl.STATUS_RUNNING_PAUSED) { 745 return 0; 746 } 747 if (info.mNumFailed == 0) { 748 return 0; 749 } 750 long when = info.restartTime(); 751 if (when <= now) { 752 return 0; 753 } 754 return when - now; 755 } 756 757 /** 758 * Returns whether there's a visible notification for this download 759 */ 760 private boolean visibleNotification(int arrayPos) { 761 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 762 return info.hasCompletionNotification(); 763 } 764 765 /** 766 * Returns whether a file should be scanned 767 */ 768 private boolean shouldScanFile(int arrayPos) { 769 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 770 return !info.mMediaScanned 771 && info.mDestination == Downloads.Impl.DESTINATION_EXTERNAL 772 && Downloads.Impl.isStatusSuccess(info.mStatus) 773 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType); 774 } 775 776 /** 777 * Returns whether we have a live connection to the Media Scanner 778 */ 779 private boolean mediaScannerConnected() { 780 return mMediaScannerService != null; 781 } 782 783 /** 784 * Attempts to scan the file if necessary. 785 * Returns true if the file has been properly scanned. 786 */ 787 private boolean scanFile(Cursor cursor, int arrayPos) { 788 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 789 synchronized (this) { 790 if (mMediaScannerService != null) { 791 try { 792 if (Constants.LOGV) { 793 Log.v(Constants.TAG, "Scanning file " + info.mFileName); 794 } 795 mMediaScannerService.scanFile(info.mFileName, info.mMimeType); 796 if (cursor != null) { 797 ContentValues values = new ContentValues(); 798 values.put(Constants.MEDIA_SCANNED, 1); 799 getContentResolver().update(ContentUris.withAppendedId( 800 Downloads.Impl.CONTENT_URI, cursor.getLong( 801 cursor.getColumnIndexOrThrow(Downloads.Impl._ID))), 802 values, null, null); 803 } 804 return true; 805 } catch (RemoteException e) { 806 if (Config.LOGD) { 807 Log.d(Constants.TAG, "Failed to scan file " + info.mFileName); 808 } 809 } 810 } 811 } 812 return false; 813 } 814 815} 816