(GB/GBMR) (do not merge) delete file from disk when deleting from db
[android/platform/packages/providers/DownloadProvider.git] / ui / src / com / android / providers / downloads / ui / DownloadList.java
1 /*
2  * Copyright (C) 2010 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
17 package com.android.providers.downloads.ui;
18
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.DownloadManager;
22 import android.content.ActivityNotFoundException;
23 import android.content.ContentUris;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.DialogInterface.OnCancelListener;
27 import android.content.Intent;
28 import android.database.ContentObserver;
29 import android.database.Cursor;
30 import android.database.DataSetObserver;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.Environment;
34 import android.os.Handler;
35 import android.provider.Downloads;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.view.Menu;
39 import android.view.MenuInflater;
40 import android.view.MenuItem;
41 import android.view.View;
42 import android.view.View.OnClickListener;
43 import android.view.ViewGroup;
44 import android.view.animation.AnimationUtils;
45 import android.widget.AdapterView;
46 import android.widget.AdapterView.OnItemClickListener;
47 import android.widget.Button;
48 import android.widget.ExpandableListView;
49 import android.widget.ExpandableListView.OnChildClickListener;
50 import android.widget.ListView;
51 import android.widget.Toast;
52
53 import com.android.providers.downloads.ui.DownloadItem.DownloadSelectListener;
54
55 import java.io.File;
56 import java.io.FileNotFoundException;
57 import java.io.IOException;
58 import java.util.HashSet;
59 import java.util.Iterator;
60 import java.util.Set;
61
62 /**
63  *  View showing a list of all downloads the Download Manager knows about.
64  */
65 public class DownloadList extends Activity
66         implements OnChildClickListener, OnItemClickListener, DownloadSelectListener,
67         OnClickListener, OnCancelListener {
68     private static final String LOG_TAG = "DownloadList";
69
70     private ExpandableListView mDateOrderedListView;
71     private ListView mSizeOrderedListView;
72     private View mEmptyView;
73     private ViewGroup mSelectionMenuView;
74     private Button mSelectionDeleteButton;
75
76     private DownloadManager mDownloadManager;
77     private Cursor mDateSortedCursor;
78     private DateSortedDownloadAdapter mDateSortedAdapter;
79     private Cursor mSizeSortedCursor;
80     private DownloadAdapter mSizeSortedAdapter;
81     private MyContentObserver mContentObserver = new MyContentObserver();
82     private MyDataSetObserver mDataSetObserver = new MyDataSetObserver();
83
84     private int mStatusColumnId;
85     private int mIdColumnId;
86     private int mLocalUriColumnId;
87     private int mMediaTypeColumnId;
88     private int mReasonColumndId;
89     private int mMediaProviderUriId;
90
91     private boolean mIsSortedBySize = false;
92     private Set<Long> mSelectedIds = new HashSet<Long>();
93
94     /**
95      * We keep track of when a dialog is being displayed for a pending download, because if that
96      * download starts running, we want to immediately hide the dialog.
97      */
98     private Long mQueuedDownloadId = null;
99     private AlertDialog mQueuedDialog;
100
101
102     private class MyContentObserver extends ContentObserver {
103         public MyContentObserver() {
104             super(new Handler());
105         }
106
107         @Override
108         public void onChange(boolean selfChange) {
109             handleDownloadsChanged();
110         }
111     }
112
113     private class MyDataSetObserver extends DataSetObserver {
114         @Override
115         public void onChanged() {
116             // may need to switch to or from the empty view
117             chooseListToShow();
118             ensureSomeGroupIsExpanded();
119         }
120     }
121
122     @Override
123     public void onCreate(Bundle icicle) {
124         super.onCreate(icicle);
125         setupViews();
126
127         mDownloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
128         mDownloadManager.setAccessAllDownloads(true);
129         DownloadManager.Query baseQuery = new DownloadManager.Query()
130                 .setOnlyIncludeVisibleInDownloadsUi(true);
131         mDateSortedCursor = mDownloadManager.query(baseQuery);
132         mSizeSortedCursor = mDownloadManager.query(baseQuery
133                                                   .orderBy(DownloadManager.COLUMN_TOTAL_SIZE_BYTES,
134                                                           DownloadManager.Query.ORDER_DESCENDING));
135
136         // only attach everything to the listbox if we can access the download database. Otherwise,
137         // just show it empty
138         if (haveCursors()) {
139             startManagingCursor(mDateSortedCursor);
140             startManagingCursor(mSizeSortedCursor);
141
142             mStatusColumnId =
143                     mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS);
144             mIdColumnId =
145                     mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID);
146             mLocalUriColumnId =
147                     mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI);
148             mMediaTypeColumnId =
149                     mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_MEDIA_TYPE);
150             mReasonColumndId =
151                     mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON);
152             mMediaProviderUriId =
153                     mDateSortedCursor.getColumnIndexOrThrow(
154                             DownloadManager.COLUMN_MEDIAPROVIDER_URI);
155
156             mDateSortedAdapter = new DateSortedDownloadAdapter(this, mDateSortedCursor, this);
157             mDateOrderedListView.setAdapter(mDateSortedAdapter);
158             mSizeSortedAdapter = new DownloadAdapter(this, mSizeSortedCursor, this);
159             mSizeOrderedListView.setAdapter(mSizeSortedAdapter);
160
161             ensureSomeGroupIsExpanded();
162         }
163
164         chooseListToShow();
165     }
166
167     /**
168      * If no group is expanded in the date-sorted list, expand the first one.
169      */
170     private void ensureSomeGroupIsExpanded() {
171         mDateOrderedListView.post(new Runnable() {
172             public void run() {
173                 if (mDateSortedAdapter.getGroupCount() == 0) {
174                     return;
175                 }
176                 for (int group = 0; group < mDateSortedAdapter.getGroupCount(); group++) {
177                     if (mDateOrderedListView.isGroupExpanded(group)) {
178                         return;
179                     }
180                 }
181                 mDateOrderedListView.expandGroup(0);
182             }
183         });
184     }
185
186     private void setupViews() {
187         setContentView(R.layout.download_list);
188         setTitle(getText(R.string.download_title));
189
190         mDateOrderedListView = (ExpandableListView) findViewById(R.id.date_ordered_list);
191         mDateOrderedListView.setOnChildClickListener(this);
192         mSizeOrderedListView = (ListView) findViewById(R.id.size_ordered_list);
193         mSizeOrderedListView.setOnItemClickListener(this);
194         mEmptyView = findViewById(R.id.empty);
195
196         mSelectionMenuView = (ViewGroup) findViewById(R.id.selection_menu);
197         mSelectionDeleteButton = (Button) findViewById(R.id.selection_delete);
198         mSelectionDeleteButton.setOnClickListener(this);
199
200         ((Button) findViewById(R.id.deselect_all)).setOnClickListener(this);
201     }
202
203     private boolean haveCursors() {
204         return mDateSortedCursor != null && mSizeSortedCursor != null;
205     }
206
207     @Override
208     protected void onResume() {
209         super.onResume();
210         if (haveCursors()) {
211             mDateSortedCursor.registerContentObserver(mContentObserver);
212             mDateSortedCursor.registerDataSetObserver(mDataSetObserver);
213             refresh();
214         }
215     }
216
217     @Override
218     protected void onPause() {
219         super.onPause();
220         if (haveCursors()) {
221             mDateSortedCursor.unregisterContentObserver(mContentObserver);
222             mDateSortedCursor.unregisterDataSetObserver(mDataSetObserver);
223         }
224     }
225
226     @Override
227     protected void onSaveInstanceState(Bundle outState) {
228         super.onSaveInstanceState(outState);
229         outState.putBoolean("isSortedBySize", mIsSortedBySize);
230         outState.putLongArray("selection", getSelectionAsArray());
231     }
232
233     private long[] getSelectionAsArray() {
234         long[] selectedIds = new long[mSelectedIds.size()];
235         Iterator<Long> iterator = mSelectedIds.iterator();
236         for (int i = 0; i < selectedIds.length; i++) {
237             selectedIds[i] = iterator.next();
238         }
239         return selectedIds;
240     }
241
242     @Override
243     protected void onRestoreInstanceState(Bundle savedInstanceState) {
244         super.onRestoreInstanceState(savedInstanceState);
245         mIsSortedBySize = savedInstanceState.getBoolean("isSortedBySize");
246         mSelectedIds.clear();
247         for (long selectedId : savedInstanceState.getLongArray("selection")) {
248             mSelectedIds.add(selectedId);
249         }
250         chooseListToShow();
251         showOrHideSelectionMenu();
252     }
253
254     @Override
255     public boolean onCreateOptionsMenu(Menu menu) {
256         if (haveCursors()) {
257             MenuInflater inflater = getMenuInflater();
258             inflater.inflate(R.menu.download_menu, menu);
259         }
260         return true;
261     }
262
263     @Override
264     public boolean onPrepareOptionsMenu(Menu menu) {
265         menu.findItem(R.id.download_menu_sort_by_size).setVisible(!mIsSortedBySize);
266         menu.findItem(R.id.download_menu_sort_by_date).setVisible(mIsSortedBySize);
267         return super.onPrepareOptionsMenu(menu);
268     }
269
270     @Override
271     public boolean onOptionsItemSelected(MenuItem item) {
272         switch (item.getItemId()) {
273             case R.id.download_menu_sort_by_size:
274                 mIsSortedBySize = true;
275                 chooseListToShow();
276                 return true;
277             case R.id.download_menu_sort_by_date:
278                 mIsSortedBySize = false;
279                 chooseListToShow();
280                 return true;
281         }
282         return false;
283     }
284
285     /**
286      * Show the correct ListView and hide the other, or hide both and show the empty view.
287      */
288     private void chooseListToShow() {
289         mDateOrderedListView.setVisibility(View.GONE);
290         mSizeOrderedListView.setVisibility(View.GONE);
291
292         if (mDateSortedCursor == null || mDateSortedCursor.getCount() == 0) {
293             mEmptyView.setVisibility(View.VISIBLE);
294         } else {
295             mEmptyView.setVisibility(View.GONE);
296             activeListView().setVisibility(View.VISIBLE);
297             activeListView().invalidateViews(); // ensure checkboxes get updated
298         }
299     }
300
301     /**
302      * @return the ListView that should currently be visible.
303      */
304     private ListView activeListView() {
305         if (mIsSortedBySize) {
306             return mSizeOrderedListView;
307         }
308         return mDateOrderedListView;
309     }
310
311     /**
312      * @return an OnClickListener to delete the given downloadId from the Download Manager
313      */
314     private DialogInterface.OnClickListener getDeleteClickHandler(final long downloadId) {
315         return new DialogInterface.OnClickListener() {
316             @Override
317             public void onClick(DialogInterface dialog, int which) {
318                 deleteDownload(downloadId);
319             }
320         };
321     }
322
323     /**
324      * @return an OnClickListener to restart the given downloadId in the Download Manager
325      */
326     private DialogInterface.OnClickListener getRestartClickHandler(final long downloadId) {
327         return new DialogInterface.OnClickListener() {
328             @Override
329             public void onClick(DialogInterface dialog, int which) {
330                 mDownloadManager.restartDownload(downloadId);
331             }
332         };
333     }
334
335     /**
336      * Send an Intent to open the download currently pointed to by the given cursor.
337      */
338     private void openCurrentDownload(Cursor cursor) {
339         Uri localUri = Uri.parse(cursor.getString(mLocalUriColumnId));
340         try {
341             getContentResolver().openFileDescriptor(localUri, "r").close();
342         } catch (FileNotFoundException exc) {
343             Log.d(LOG_TAG, "Failed to open download " + cursor.getLong(mIdColumnId), exc);
344             showFailedDialog(cursor.getLong(mIdColumnId),
345                     getString(R.string.dialog_file_missing_body));
346             return;
347         } catch (IOException exc) {
348             // close() failed, not a problem
349         }
350
351         Intent intent = new Intent(Intent.ACTION_VIEW);
352         intent.setDataAndType(localUri, cursor.getString(mMediaTypeColumnId));
353         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
354         try {
355             startActivity(intent);
356         } catch (ActivityNotFoundException ex) {
357             Toast.makeText(this, R.string.download_no_application_title, Toast.LENGTH_LONG).show();
358         }
359     }
360
361     private void handleItemClick(Cursor cursor) {
362         long id = cursor.getInt(mIdColumnId);
363         switch (cursor.getInt(mStatusColumnId)) {
364             case DownloadManager.STATUS_PENDING:
365             case DownloadManager.STATUS_RUNNING:
366                 sendRunningDownloadClickedBroadcast(id);
367                 break;
368
369             case DownloadManager.STATUS_PAUSED:
370                 if (isPausedForWifi(cursor)) {
371                     mQueuedDownloadId = id;
372                     mQueuedDialog = new AlertDialog.Builder(this)
373                             .setTitle(R.string.dialog_title_queued_body)
374                             .setMessage(R.string.dialog_queued_body)
375                             .setPositiveButton(R.string.keep_queued_download, null)
376                             .setNegativeButton(R.string.remove_download, getDeleteClickHandler(id))
377                             .setOnCancelListener(this)
378                             .show();
379                 } else {
380                     sendRunningDownloadClickedBroadcast(id);
381                 }
382                 break;
383
384             case DownloadManager.STATUS_SUCCESSFUL:
385                 openCurrentDownload(cursor);
386                 break;
387
388             case DownloadManager.STATUS_FAILED:
389                 showFailedDialog(id, getErrorMessage(cursor));
390                 break;
391         }
392     }
393
394     /**
395      * @return the appropriate error message for the failed download pointed to by cursor
396      */
397     private String getErrorMessage(Cursor cursor) {
398         switch (cursor.getInt(mReasonColumndId)) {
399             case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
400                 if (isOnExternalStorage(cursor)) {
401                     return getString(R.string.dialog_file_already_exists);
402                 } else {
403                     // the download manager should always find a free filename for cache downloads,
404                     // so this indicates a strange internal error
405                     return getUnknownErrorMessage();
406                 }
407
408             case DownloadManager.ERROR_INSUFFICIENT_SPACE:
409                 if (isOnExternalStorage(cursor)) {
410                     return getString(R.string.dialog_insufficient_space_on_external);
411                 } else {
412                     return getString(R.string.dialog_insufficient_space_on_cache);
413                 }
414
415             case DownloadManager.ERROR_DEVICE_NOT_FOUND:
416                 return getString(R.string.dialog_media_not_found);
417
418             case DownloadManager.ERROR_CANNOT_RESUME:
419                 return getString(R.string.dialog_cannot_resume);
420
421             default:
422                 return getUnknownErrorMessage();
423         }
424     }
425
426     private boolean isOnExternalStorage(Cursor cursor) {
427         String localUriString = cursor.getString(mLocalUriColumnId);
428         if (localUriString == null) {
429             return false;
430         }
431         Uri localUri = Uri.parse(localUriString);
432         if (!localUri.getScheme().equals("file")) {
433             return false;
434         }
435         String path = localUri.getPath();
436         String externalRoot = Environment.getExternalStorageDirectory().getPath();
437         return path.startsWith(externalRoot);
438     }
439
440     private String getUnknownErrorMessage() {
441         return getString(R.string.dialog_failed_body);
442     }
443
444     private void showFailedDialog(long downloadId, String dialogBody) {
445         new AlertDialog.Builder(this)
446                 .setTitle(R.string.dialog_title_not_available)
447                 .setMessage(dialogBody)
448                 .setNegativeButton(R.string.delete_download, getDeleteClickHandler(downloadId))
449                 .setPositiveButton(R.string.retry_download, getRestartClickHandler(downloadId))
450                 .show();
451     }
452
453     /**
454      * TODO use constants/shared code?
455      */
456     private void sendRunningDownloadClickedBroadcast(long id) {
457         Intent intent = new Intent("android.intent.action.DOWNLOAD_LIST");
458         intent.setClassName("com.android.providers.downloads",
459                 "com.android.providers.downloads.DownloadReceiver");
460         intent.setData(ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id));
461         intent.putExtra("multiple", false);
462         sendBroadcast(intent);
463     }
464
465     // handle a click from the date-sorted list
466     @Override
467     public boolean onChildClick(ExpandableListView parent, View v,
468             int groupPosition, int childPosition, long id) {
469         mDateSortedAdapter.moveCursorToChildPosition(groupPosition, childPosition);
470         handleItemClick(mDateSortedCursor);
471         return true;
472     }
473
474     // handle a click from the size-sorted list
475     @Override
476     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
477         mSizeSortedCursor.moveToPosition(position);
478         handleItemClick(mSizeSortedCursor);
479     }
480
481     // handle a click on one of the download item checkboxes
482     @Override
483     public void onDownloadSelectionChanged(long downloadId, boolean isSelected) {
484         if (isSelected) {
485             mSelectedIds.add(downloadId);
486         } else {
487             mSelectedIds.remove(downloadId);
488         }
489         showOrHideSelectionMenu();
490     }
491
492     private void showOrHideSelectionMenu() {
493         boolean shouldBeVisible = !mSelectedIds.isEmpty();
494         boolean isVisible = mSelectionMenuView.getVisibility() == View.VISIBLE;
495         if (shouldBeVisible) {
496             updateSelectionMenu();
497             if (!isVisible) {
498                 // show menu
499                 mSelectionMenuView.setVisibility(View.VISIBLE);
500                 mSelectionMenuView.startAnimation(
501                         AnimationUtils.loadAnimation(this, R.anim.footer_appear));
502             }
503         } else if (!shouldBeVisible && isVisible) {
504             // hide menu
505             mSelectionMenuView.setVisibility(View.GONE);
506             mSelectionMenuView.startAnimation(
507                     AnimationUtils.loadAnimation(this, R.anim.footer_disappear));
508         }
509     }
510
511     /**
512      * Set up the contents of the selection menu based on the current selection.
513      */
514     private void updateSelectionMenu() {
515         int deleteButtonStringId = R.string.delete_download;
516         if (mSelectedIds.size() == 1) {
517             Cursor cursor = mDownloadManager.query(new DownloadManager.Query()
518                     .setFilterById(mSelectedIds.iterator().next()));
519             try {
520                 cursor.moveToFirst();
521                 switch (cursor.getInt(mStatusColumnId)) {
522                     case DownloadManager.STATUS_FAILED:
523                         deleteButtonStringId = R.string.delete_download;
524                         break;
525
526                     case DownloadManager.STATUS_PENDING:
527                         deleteButtonStringId = R.string.remove_download;
528                         break;
529
530                     case DownloadManager.STATUS_PAUSED:
531                     case DownloadManager.STATUS_RUNNING:
532                         deleteButtonStringId = R.string.cancel_running_download;
533                         break;
534                 }
535             } finally {
536                 cursor.close();
537             }
538         }
539         mSelectionDeleteButton.setText(deleteButtonStringId);
540     }
541
542     @Override
543     public void onClick(View v) {
544         switch (v.getId()) {
545             case R.id.selection_delete:
546                 for (Long downloadId : mSelectedIds) {
547                     deleteDownload(downloadId);
548                 }
549                 clearSelection();
550                 break;
551
552             case R.id.deselect_all:
553                 clearSelection();
554                 break;
555         }
556     }
557
558     /**
559      * Requery the database and update the UI.
560      */
561     private void refresh() {
562         mDateSortedCursor.requery();
563         mSizeSortedCursor.requery();
564         // Adapters get notification of changes and update automatically
565     }
566
567     private void clearSelection() {
568         mSelectedIds.clear();
569         showOrHideSelectionMenu();
570     }
571
572     /**
573      * Delete a download from the Download Manager.
574      */
575     private void deleteDownload(long downloadId) {
576         if (moveToDownload(downloadId)) {
577             int status = mDateSortedCursor.getInt(mStatusColumnId);
578             boolean isComplete = status == DownloadManager.STATUS_SUCCESSFUL
579                     || status == DownloadManager.STATUS_FAILED;
580             String localUri = mDateSortedCursor.getString(mLocalUriColumnId);
581             if (isComplete && localUri != null) {
582                 String path = Uri.parse(localUri).getPath();
583                 if (path.startsWith(Environment.getExternalStorageDirectory().getPath())) {
584                     String mediaProviderUri = mDateSortedCursor.getString(mMediaProviderUriId);
585                     if (TextUtils.isEmpty(mediaProviderUri)) {
586                         // downloads database doesn't have the mediaprovider_uri. It means
587                         // this download occurred before mediaprovider_uri column existed
588                         // in downloads table. Since MediaProvider needs the mediaprovider_uri to
589                         // delete this download, just set the 'deleted' flag to 1 on this row
590                         // in the database. DownloadService, upon seeing this flag set to 1, will
591                         // re-scan the file and get the MediaProviderUri and then delete the file
592                         mDownloadManager.markRowDeleted(downloadId);
593                         return;
594                     } else {
595                         getContentResolver().delete(Uri.parse(mediaProviderUri), null, null);
596                         // sometimes mediaprovider doesn't delete file from sdcard after deleting it
597                         // from its db. delete it now
598                         try {
599                           File file = new File(path);
600                           file.delete();
601                       } catch (Exception e) {
602                           Log.w(LOG_TAG, "file: '" + path + "' couldn't be deleted", e);
603                       }
604                     }
605                 }
606             }
607         }
608         mDownloadManager.remove(downloadId);
609     }
610
611     @Override
612     public boolean isDownloadSelected(long id) {
613         return mSelectedIds.contains(id);
614     }
615
616     /**
617      * Called when there's a change to the downloads database.
618      */
619     void handleDownloadsChanged() {
620         checkSelectionForDeletedEntries();
621
622         if (mQueuedDownloadId != null && moveToDownload(mQueuedDownloadId)) {
623             if (mDateSortedCursor.getInt(mStatusColumnId) != DownloadManager.STATUS_PAUSED
624                     || !isPausedForWifi(mDateSortedCursor)) {
625                 mQueuedDialog.cancel();
626             }
627         }
628     }
629
630     private boolean isPausedForWifi(Cursor cursor) {
631         return cursor.getInt(mReasonColumndId) == DownloadManager.PAUSED_QUEUED_FOR_WIFI;
632     }
633
634     /**
635      * Check if any of the selected downloads have been deleted from the downloads database, and
636      * remove such downloads from the selection.
637      */
638     private void checkSelectionForDeletedEntries() {
639         // gather all existing IDs...
640         Set<Long> allIds = new HashSet<Long>();
641         for (mDateSortedCursor.moveToFirst(); !mDateSortedCursor.isAfterLast();
642                 mDateSortedCursor.moveToNext()) {
643             allIds.add(mDateSortedCursor.getLong(mIdColumnId));
644         }
645
646         // ...and check if any selected IDs are now missing
647         for (Iterator<Long> iterator = mSelectedIds.iterator(); iterator.hasNext(); ) {
648             if (!allIds.contains(iterator.next())) {
649                 iterator.remove();
650             }
651         }
652     }
653
654     /**
655      * Move {@link #mDateSortedCursor} to the download with the given ID.
656      * @return true if the specified download ID was found; false otherwise
657      */
658     private boolean moveToDownload(long downloadId) {
659         for (mDateSortedCursor.moveToFirst(); !mDateSortedCursor.isAfterLast();
660                 mDateSortedCursor.moveToNext()) {
661             if (mDateSortedCursor.getLong(mIdColumnId) == downloadId) {
662                 return true;
663             }
664         }
665         return false;
666     }
667
668     /**
669      * Called when a dialog for a pending download is canceled.
670      */
671     @Override
672     public void onCancel(DialogInterface dialog) {
673         mQueuedDownloadId = null;
674         mQueuedDialog = null;
675     }
676 }