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