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