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