DownloadService.java revision 7dd92fa94df0a13b4592ee636b7aa2b605f6b473
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.Impl.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.Impl.CONTENT_URI, 311 null, null, null, Downloads.Impl._ID); 312 313 if (cursor == null) { 314 // TODO: this doesn't look right, it'd leave the loop in an inconsistent state 315 return; 316 } 317 318 cursor.moveToFirst(); 319 320 int arrayPos = 0; 321 322 boolean mustScan = false; 323 keepService = false; 324 wakeUp = Long.MAX_VALUE; 325 326 boolean isAfterLast = cursor.isAfterLast(); 327 328 int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); 329 330 /* 331 * Walk the cursor and the local array to keep them in sync. The key 332 * to the algorithm is that the ids are unique and sorted both in 333 * the cursor and in the array, so that they can be processed in 334 * order in both sources at the same time: at each step, both 335 * sources point to the lowest id that hasn't been processed from 336 * that source, and the algorithm processes the lowest id from 337 * those two possibilities. 338 * At each step: 339 * -If the array contains an entry that's not in the cursor, remove the 340 * entry, move to next entry in the array. 341 * -If the array contains an entry that's in the cursor, nothing to do, 342 * move to next cursor row and next array entry. 343 * -If the cursor contains an entry that's not in the array, insert 344 * a new entry in the array, move to next cursor row and next 345 * array entry. 346 */ 347 while (!isAfterLast || arrayPos < mDownloads.size()) { 348 if (isAfterLast) { 349 // We're beyond the end of the cursor but there's still some 350 // stuff in the local array, which can only be junk 351 if (Constants.LOGVV) { 352 int arrayId = ((DownloadInfo) mDownloads.get(arrayPos)).mId; 353 Log.v(Constants.TAG, "Array update: trimming " + 354 arrayId + " @ " + arrayPos); 355 } 356 if (shouldScanFile(arrayPos) && mediaScannerConnected()) { 357 scanFile(null, arrayPos); 358 } 359 deleteDownload(arrayPos); // this advances in the array 360 } else { 361 int id = cursor.getInt(idColumn); 362 363 if (arrayPos == mDownloads.size()) { 364 insertDownload(cursor, arrayPos, networkAvailable, networkRoaming, now); 365 if (Constants.LOGVV) { 366 Log.v(Constants.TAG, "Array update: appending " + 367 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).mId; 388 389 if (arrayId < id) { 390 // The array entry isn't in the cursor 391 if (Constants.LOGVV) { 392 Log.v(Constants.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( 402 cursor, arrayPos, 403 networkAvailable, networkRoaming, now); 404 if (shouldScanFile(arrayPos) 405 && (!mediaScannerConnected() 406 || !scanFile(cursor, arrayPos))) { 407 mustScan = true; 408 keepService = true; 409 } 410 if (visibleNotification(arrayPos)) { 411 keepService = true; 412 } 413 long next = nextAction(arrayPos, now); 414 if (next == 0) { 415 keepService = true; 416 } else if (next > 0 && next < wakeUp) { 417 wakeUp = next; 418 } 419 ++arrayPos; 420 cursor.moveToNext(); 421 isAfterLast = cursor.isAfterLast(); 422 } else { 423 // This cursor entry didn't exist in the stored array 424 if (Constants.LOGVV) { 425 Log.v(Constants.TAG, "Array update: inserting " + 426 id + " @ " + arrayPos); 427 } 428 insertDownload( 429 cursor, arrayPos, 430 networkAvailable, networkRoaming, now); 431 if (shouldScanFile(arrayPos) 432 && (!mediaScannerConnected() 433 || !scanFile(cursor, arrayPos))) { 434 mustScan = true; 435 keepService = true; 436 } 437 if (visibleNotification(arrayPos)) { 438 keepService = true; 439 } 440 long next = nextAction(arrayPos, now); 441 if (next == 0) { 442 keepService = true; 443 } else if (next > 0 && next < wakeUp) { 444 wakeUp = next; 445 } 446 ++arrayPos; 447 cursor.moveToNext(); 448 isAfterLast = cursor.isAfterLast(); 449 } 450 } 451 } 452 } 453 454 mNotifier.updateNotification(); 455 456 if (mustScan) { 457 if (!mMediaScannerConnecting) { 458 Intent intent = new Intent(); 459 intent.setClassName("com.android.providers.media", 460 "com.android.providers.media.MediaScannerService"); 461 mMediaScannerConnecting = true; 462 bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE); 463 } 464 } else { 465 mMediaScannerConnection.disconnectMediaScanner(); 466 } 467 468 cursor.close(); 469 } 470 } 471 } 472 473 /** 474 * Removes files that may have been left behind in the cache directory 475 */ 476 private void removeSpuriousFiles() { 477 File[] files = Environment.getDownloadCacheDirectory().listFiles(); 478 if (files == null) { 479 // The cache folder doesn't appear to exist (this is likely the case 480 // when running the simulator). 481 return; 482 } 483 HashSet<String> fileSet = new HashSet(); 484 for (int i = 0; i < files.length; i++) { 485 if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) { 486 continue; 487 } 488 if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) { 489 continue; 490 } 491 fileSet.add(files[i].getPath()); 492 } 493 494 Cursor cursor = getContentResolver().query(Downloads.Impl.CONTENT_URI, 495 new String[] { Downloads.Impl._DATA }, null, null, null); 496 if (cursor != null) { 497 if (cursor.moveToFirst()) { 498 do { 499 fileSet.remove(cursor.getString(0)); 500 } while (cursor.moveToNext()); 501 } 502 cursor.close(); 503 } 504 Iterator<String> iterator = fileSet.iterator(); 505 while (iterator.hasNext()) { 506 String filename = iterator.next(); 507 if (Constants.LOGV) { 508 Log.v(Constants.TAG, "deleting spurious file " + filename); 509 } 510 new File(filename).delete(); 511 } 512 } 513 514 /** 515 * Drops old rows from the database to prevent it from growing too large 516 */ 517 private void trimDatabase() { 518 Cursor cursor = getContentResolver().query(Downloads.Impl.CONTENT_URI, 519 new String[] { Downloads.Impl._ID }, 520 Downloads.Impl.COLUMN_STATUS + " >= '200'", null, 521 Downloads.Impl.COLUMN_LAST_MODIFICATION); 522 if (cursor == null) { 523 // This isn't good - if we can't do basic queries in our database, nothing's gonna work 524 Log.e(Constants.TAG, "null cursor in trimDatabase"); 525 return; 526 } 527 if (cursor.moveToFirst()) { 528 int numDelete = cursor.getCount() - Constants.MAX_DOWNLOADS; 529 int columnId = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); 530 while (numDelete > 0) { 531 getContentResolver().delete( 532 ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, 533 cursor.getLong(columnId)), null, null); 534 if (!cursor.moveToNext()) { 535 break; 536 } 537 numDelete--; 538 } 539 } 540 cursor.close(); 541 } 542 543 /** 544 * Keeps a local copy of the info about a download, and initiates the 545 * download if appropriate. 546 */ 547 private void insertDownload( 548 Cursor cursor, int arrayPos, 549 boolean networkAvailable, boolean networkRoaming, long now) { 550 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); 551 int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS); 552 int retryRedirect = 553 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT)); 554 DownloadInfo info = new DownloadInfo( 555 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl._ID)), 556 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_URI)), 557 cursor.getInt(cursor.getColumnIndexOrThrow( 558 Downloads.Impl.COLUMN_NO_INTEGRITY)) == 1, 559 cursor.getString(cursor.getColumnIndexOrThrow( 560 Downloads.Impl.COLUMN_FILE_NAME_HINT)), 561 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl._DATA)), 562 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE)), 563 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESTINATION)), 564 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_VISIBILITY)), 565 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_CONTROL)), 566 cursor.getInt(statusColumn), 567 cursor.getInt(failedColumn), 568 retryRedirect & 0xfffffff, 569 retryRedirect >> 28, 570 cursor.getLong(cursor.getColumnIndexOrThrow( 571 Downloads.Impl.COLUMN_LAST_MODIFICATION)), 572 cursor.getString(cursor.getColumnIndexOrThrow( 573 Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE)), 574 cursor.getString(cursor.getColumnIndexOrThrow( 575 Downloads.Impl.COLUMN_NOTIFICATION_CLASS)), 576 cursor.getString(cursor.getColumnIndexOrThrow( 577 Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS)), 578 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_COOKIE_DATA)), 579 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_USER_AGENT)), 580 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_REFERER)), 581 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TOTAL_BYTES)), 582 cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_CURRENT_BYTES)), 583 cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)), 584 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1); 585 586 if (Constants.LOGVV) { 587 Log.v(Constants.TAG, "Service adding new entry"); 588 Log.v(Constants.TAG, "ID : " + info.mId); 589 Log.v(Constants.TAG, "URI : " + ((info.mUri != null) ? "yes" : "no")); 590 Log.v(Constants.TAG, "NO_INTEG: " + info.mNoIntegrity); 591 Log.v(Constants.TAG, "HINT : " + info.mHint); 592 Log.v(Constants.TAG, "FILENAME: " + info.mFileName); 593 Log.v(Constants.TAG, "MIMETYPE: " + info.mMimeType); 594 Log.v(Constants.TAG, "DESTINAT: " + info.mDestination); 595 Log.v(Constants.TAG, "VISIBILI: " + info.mVisibility); 596 Log.v(Constants.TAG, "CONTROL : " + info.mControl); 597 Log.v(Constants.TAG, "STATUS : " + info.mStatus); 598 Log.v(Constants.TAG, "FAILED_C: " + info.mNumFailed); 599 Log.v(Constants.TAG, "RETRY_AF: " + info.mRetryAfter); 600 Log.v(Constants.TAG, "REDIRECT: " + info.mRedirectCount); 601 Log.v(Constants.TAG, "LAST_MOD: " + info.mLastMod); 602 Log.v(Constants.TAG, "PACKAGE : " + info.mPackage); 603 Log.v(Constants.TAG, "CLASS : " + info.mClass); 604 Log.v(Constants.TAG, "COOKIES : " + ((info.mCookies != null) ? "yes" : "no")); 605 Log.v(Constants.TAG, "AGENT : " + info.mUserAgent); 606 Log.v(Constants.TAG, "REFERER : " + ((info.mReferer != null) ? "yes" : "no")); 607 Log.v(Constants.TAG, "TOTAL : " + info.mTotalBytes); 608 Log.v(Constants.TAG, "CURRENT : " + info.mCurrentBytes); 609 Log.v(Constants.TAG, "ETAG : " + info.mETag); 610 Log.v(Constants.TAG, "SCANNED : " + info.mMediaScanned); 611 } 612 613 mDownloads.add(arrayPos, info); 614 615 if (info.mStatus == 0 616 && (info.mDestination == Downloads.Impl.DESTINATION_EXTERNAL 617 || info.mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) 618 && info.mMimeType != null 619 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType)) { 620 // Check to see if we are allowed to download this file. Only files 621 // that can be handled by the platform can be downloaded. 622 // special case DRM files, which we should always allow downloading. 623 Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW); 624 625 // We can provide data as either content: or file: URIs, 626 // so allow both. (I think it would be nice if we just did 627 // everything as content: URIs) 628 // Actually, right now the download manager's UId restrictions 629 // prevent use from using content: so it's got to be file: or 630 // nothing 631 632 mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mMimeType); 633 ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent, 634 PackageManager.MATCH_DEFAULT_ONLY); 635 //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list); 636 637 if (ri == null) { 638 if (Config.LOGD) { 639 Log.d(Constants.TAG, "no application to handle MIME type " + info.mMimeType); 640 } 641 info.mStatus = Downloads.Impl.STATUS_NOT_ACCEPTABLE; 642 643 Uri uri = ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, info.mId); 644 ContentValues values = new ContentValues(); 645 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_NOT_ACCEPTABLE); 646 getContentResolver().update(uri, values, null, null); 647 info.sendIntentIfRequested(uri, this); 648 return; 649 } 650 } 651 652 if (info.canUseNetwork(networkAvailable, networkRoaming)) { 653 if (info.isReadyToStart(now)) { 654 if (Constants.LOGV) { 655 Log.v(Constants.TAG, "Service spawning thread to handle new download " + 656 info.mId); 657 } 658 if (info.mHasActiveThread) { 659 throw new IllegalStateException("Multiple threads on same download on insert"); 660 } 661 if (info.mStatus != Downloads.Impl.STATUS_RUNNING) { 662 info.mStatus = Downloads.Impl.STATUS_RUNNING; 663 ContentValues values = new ContentValues(); 664 values.put(Downloads.Impl.COLUMN_STATUS, info.mStatus); 665 getContentResolver().update( 666 ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, info.mId), 667 values, null, null); 668 } 669 DownloadThread downloader = new DownloadThread(this, info); 670 info.mHasActiveThread = true; 671 downloader.start(); 672 } 673 } else { 674 if (info.mStatus == 0 675 || info.mStatus == Downloads.Impl.STATUS_PENDING 676 || info.mStatus == Downloads.Impl.STATUS_RUNNING) { 677 info.mStatus = Downloads.Impl.STATUS_RUNNING_PAUSED; 678 Uri uri = ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, info.mId); 679 ContentValues values = new ContentValues(); 680 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_RUNNING_PAUSED); 681 getContentResolver().update(uri, values, null, null); 682 } 683 } 684 } 685 686 /** 687 * Updates the local copy of the info about a download. 688 */ 689 private void updateDownload( 690 Cursor cursor, int arrayPos, 691 boolean networkAvailable, boolean networkRoaming, long now) { 692 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 693 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); 694 int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS); 695 info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl._ID)); 696 info.mUri = stringFromCursor(info.mUri, cursor, Downloads.Impl.COLUMN_URI); 697 info.mNoIntegrity = cursor.getInt(cursor.getColumnIndexOrThrow( 698 Downloads.Impl.COLUMN_NO_INTEGRITY)) == 1; 699 info.mHint = stringFromCursor(info.mHint, cursor, Downloads.Impl.COLUMN_FILE_NAME_HINT); 700 info.mFileName = stringFromCursor(info.mFileName, cursor, Downloads.Impl._DATA); 701 info.mMimeType = stringFromCursor(info.mMimeType, cursor, Downloads.Impl.COLUMN_MIME_TYPE); 702 info.mDestination = cursor.getInt(cursor.getColumnIndexOrThrow( 703 Downloads.Impl.COLUMN_DESTINATION)); 704 int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow( 705 Downloads.Impl.COLUMN_VISIBILITY)); 706 if (info.mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED 707 && newVisibility != Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED 708 && Downloads.Impl.isStatusCompleted(info.mStatus)) { 709 mNotifier.mNotificationMgr.cancel(info.mId); 710 } 711 info.mVisibility = newVisibility; 712 synchronized (info) { 713 info.mControl = cursor.getInt(cursor.getColumnIndexOrThrow( 714 Downloads.Impl.COLUMN_CONTROL)); 715 } 716 int newStatus = cursor.getInt(statusColumn); 717 if (!Downloads.Impl.isStatusCompleted(info.mStatus) && 718 Downloads.Impl.isStatusCompleted(newStatus)) { 719 mNotifier.mNotificationMgr.cancel(info.mId); 720 } 721 info.mStatus = newStatus; 722 info.mNumFailed = cursor.getInt(failedColumn); 723 int retryRedirect = 724 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT)); 725 info.mRetryAfter = retryRedirect & 0xfffffff; 726 info.mRedirectCount = retryRedirect >> 28; 727 info.mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow( 728 Downloads.Impl.COLUMN_LAST_MODIFICATION)); 729 info.mPackage = stringFromCursor( 730 info.mPackage, cursor, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); 731 info.mClass = stringFromCursor( 732 info.mClass, cursor, Downloads.Impl.COLUMN_NOTIFICATION_CLASS); 733 info.mCookies = stringFromCursor(info.mCookies, cursor, Downloads.Impl.COLUMN_COOKIE_DATA); 734 info.mUserAgent = stringFromCursor( 735 info.mUserAgent, cursor, Downloads.Impl.COLUMN_USER_AGENT); 736 info.mReferer = stringFromCursor(info.mReferer, cursor, Downloads.Impl.COLUMN_REFERER); 737 info.mTotalBytes = cursor.getInt(cursor.getColumnIndexOrThrow( 738 Downloads.Impl.COLUMN_TOTAL_BYTES)); 739 info.mCurrentBytes = cursor.getInt(cursor.getColumnIndexOrThrow( 740 Downloads.Impl.COLUMN_CURRENT_BYTES)); 741 info.mETag = stringFromCursor(info.mETag, cursor, Constants.ETAG); 742 info.mMediaScanned = 743 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1; 744 745 if (info.canUseNetwork(networkAvailable, networkRoaming)) { 746 if (info.isReadyToRestart(now)) { 747 if (Constants.LOGV) { 748 Log.v(Constants.TAG, "Service spawning thread to handle updated download " + 749 info.mId); 750 } 751 if (info.mHasActiveThread) { 752 throw new IllegalStateException("Multiple threads on same download on update"); 753 } 754 info.mStatus = Downloads.Impl.STATUS_RUNNING; 755 ContentValues values = new ContentValues(); 756 values.put(Downloads.Impl.COLUMN_STATUS, info.mStatus); 757 getContentResolver().update( 758 ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, info.mId), 759 values, null, null); 760 DownloadThread downloader = new DownloadThread(this, info); 761 info.mHasActiveThread = true; 762 downloader.start(); 763 } 764 } 765 } 766 767 /** 768 * Returns a String that holds the current value of the column, 769 * optimizing for the case where the value hasn't changed. 770 */ 771 private String stringFromCursor(String old, Cursor cursor, String column) { 772 int index = cursor.getColumnIndexOrThrow(column); 773 if (old == null) { 774 return cursor.getString(index); 775 } 776 if (mNewChars == null) { 777 mNewChars = new CharArrayBuffer(128); 778 } 779 cursor.copyStringToBuffer(index, mNewChars); 780 int length = mNewChars.sizeCopied; 781 if (length != old.length()) { 782 return cursor.getString(index); 783 } 784 if (oldChars == null || oldChars.sizeCopied < length) { 785 oldChars = new CharArrayBuffer(length); 786 } 787 char[] oldArray = oldChars.data; 788 char[] newArray = mNewChars.data; 789 old.getChars(0, length, oldArray, 0); 790 for (int i = length - 1; i >= 0; --i) { 791 if (oldArray[i] != newArray[i]) { 792 return new String(newArray, 0, length); 793 } 794 } 795 return old; 796 } 797 798 /** 799 * Removes the local copy of the info about a download. 800 */ 801 private void deleteDownload(int arrayPos) { 802 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 803 if (info.mStatus == Downloads.Impl.STATUS_RUNNING) { 804 info.mStatus = Downloads.Impl.STATUS_CANCELED; 805 } else if (info.mDestination != Downloads.Impl.DESTINATION_EXTERNAL 806 && info.mFileName != null) { 807 new File(info.mFileName).delete(); 808 } 809 mNotifier.mNotificationMgr.cancel(info.mId); 810 811 mDownloads.remove(arrayPos); 812 } 813 814 /** 815 * Returns the amount of time (as measured from the "now" parameter) 816 * at which a download will be active. 817 * 0 = immediately - service should stick around to handle this download. 818 * -1 = never - service can go away without ever waking up. 819 * positive value - service must wake up in the future, as specified in ms from "now" 820 */ 821 private long nextAction(int arrayPos, long now) { 822 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 823 if (Downloads.Impl.isStatusCompleted(info.mStatus)) { 824 return -1; 825 } 826 if (info.mStatus != Downloads.Impl.STATUS_RUNNING_PAUSED) { 827 return 0; 828 } 829 if (info.mNumFailed == 0) { 830 return 0; 831 } 832 long when = info.restartTime(); 833 if (when <= now) { 834 return 0; 835 } 836 return when - now; 837 } 838 839 /** 840 * Returns whether there's a visible notification for this download 841 */ 842 private boolean visibleNotification(int arrayPos) { 843 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 844 return info.hasCompletionNotification(); 845 } 846 847 /** 848 * Returns whether a file should be scanned 849 */ 850 private boolean shouldScanFile(int arrayPos) { 851 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 852 return !info.mMediaScanned 853 && info.mDestination == Downloads.Impl.DESTINATION_EXTERNAL 854 && Downloads.Impl.isStatusSuccess(info.mStatus) 855 && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType); 856 } 857 858 /** 859 * Returns whether we have a live connection to the Media Scanner 860 */ 861 private boolean mediaScannerConnected() { 862 return mMediaScannerService != null; 863 } 864 865 /** 866 * Attempts to scan the file if necessary. 867 * Returns true if the file has been properly scanned. 868 */ 869 private boolean scanFile(Cursor cursor, int arrayPos) { 870 DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); 871 synchronized (this) { 872 if (mMediaScannerService != null) { 873 try { 874 if (Constants.LOGV) { 875 Log.v(Constants.TAG, "Scanning file " + info.mFileName); 876 } 877 mMediaScannerService.scanFile(info.mFileName, info.mMimeType); 878 if (cursor != null) { 879 ContentValues values = new ContentValues(); 880 values.put(Constants.MEDIA_SCANNED, 1); 881 getContentResolver().update(ContentUris.withAppendedId( 882 Downloads.Impl.CONTENT_URI, cursor.getLong( 883 cursor.getColumnIndexOrThrow(Downloads.Impl._ID))), 884 values, null, null); 885 } 886 return true; 887 } catch (RemoteException e) { 888 if (Config.LOGD) { 889 Log.d(Constants.TAG, "Failed to scan file " + info.mFileName); 890 } 891 } 892 } 893 } 894 return false; 895 } 896 897} 898