| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.providers.downloads.ui; |
| |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.DownloadManager; |
| import android.content.ActivityNotFoundException; |
| import android.content.ContentUris; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.database.DataSetObserver; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.Handler; |
| import android.os.Parcelable; |
| import android.provider.BaseColumns; |
| import android.provider.Downloads; |
| import android.util.Log; |
| import android.util.SparseBooleanArray; |
| import android.view.ActionMode; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.widget.AbsListView.MultiChoiceModeListener; |
| import android.widget.AdapterView; |
| import android.widget.AdapterView.OnItemClickListener; |
| import android.widget.Button; |
| import android.widget.ExpandableListView; |
| import android.widget.ExpandableListView.OnChildClickListener; |
| import android.widget.ListView; |
| import android.widget.Toast; |
| |
| import com.android.providers.downloads.Constants; |
| import com.android.providers.downloads.OpenHelper; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * View showing a list of all downloads the Download Manager knows about. |
| */ |
| public class DownloadList extends Activity { |
| static final String LOG_TAG = "DownloadList"; |
| |
| private ExpandableListView mDateOrderedListView; |
| private ListView mSizeOrderedListView; |
| private View mEmptyView; |
| |
| private DownloadManager mDownloadManager; |
| private Cursor mDateSortedCursor; |
| private DateSortedDownloadAdapter mDateSortedAdapter; |
| private Cursor mSizeSortedCursor; |
| private DownloadAdapter mSizeSortedAdapter; |
| private ActionMode mActionMode; |
| private MyContentObserver mContentObserver = new MyContentObserver(); |
| private MyDataSetObserver mDataSetObserver = new MyDataSetObserver(); |
| |
| private int mStatusColumnId; |
| private int mIdColumnId; |
| private int mLocalUriColumnId; |
| private int mMediaTypeColumnId; |
| private int mReasonColumndId; |
| |
| // TODO this shouldn't be necessary |
| private final Map<Long, SelectionObjAttrs> mSelectedIds = |
| new HashMap<Long, SelectionObjAttrs>(); |
| private static class SelectionObjAttrs { |
| private String mFileName; |
| private String mMimeType; |
| SelectionObjAttrs(String fileName, String mimeType) { |
| mFileName = fileName; |
| mMimeType = mimeType; |
| } |
| String getFileName() { |
| return mFileName; |
| } |
| String getMimeType() { |
| return mMimeType; |
| } |
| } |
| private ListView mCurrentView; |
| private Cursor mCurrentCursor; |
| private boolean mCurrentViewIsExpandableListView = false; |
| private boolean mIsSortedBySize = false; |
| |
| /** |
| * We keep track of when a dialog is being displayed for a pending download, because if that |
| * download starts running, we want to immediately hide the dialog. |
| */ |
| private Long mQueuedDownloadId = null; |
| private AlertDialog mQueuedDialog; |
| String mSelectedCountFormat; |
| |
| private Button mSortOption; |
| |
| private class MyContentObserver extends ContentObserver { |
| public MyContentObserver() { |
| super(new Handler()); |
| } |
| |
| @Override |
| public void onChange(boolean selfChange) { |
| handleDownloadsChanged(); |
| } |
| } |
| |
| private class MyDataSetObserver extends DataSetObserver { |
| @Override |
| public void onChanged() { |
| // ignore change notification if there are selections |
| if (mSelectedIds.size() > 0) { |
| return; |
| } |
| // may need to switch to or from the empty view |
| chooseListToShow(); |
| ensureSomeGroupIsExpanded(); |
| } |
| } |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| setFinishOnTouchOutside(true); |
| setupViews(); |
| |
| mDownloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); |
| mDownloadManager.setAccessAllDownloads(true); |
| DownloadManager.Query baseQuery = new DownloadManager.Query() |
| .setOnlyIncludeVisibleInDownloadsUi(true); |
| //TODO don't do both queries - do them as needed |
| mDateSortedCursor = mDownloadManager.query(baseQuery); |
| mSizeSortedCursor = mDownloadManager.query(baseQuery |
| .orderBy(DownloadManager.COLUMN_TOTAL_SIZE_BYTES, |
| DownloadManager.Query.ORDER_DESCENDING)); |
| |
| // only attach everything to the listbox if we can access the download database. Otherwise, |
| // just show it empty |
| if (haveCursors()) { |
| startManagingCursor(mDateSortedCursor); |
| startManagingCursor(mSizeSortedCursor); |
| |
| mStatusColumnId = |
| mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS); |
| mIdColumnId = |
| mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID); |
| mLocalUriColumnId = |
| mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI); |
| mMediaTypeColumnId = |
| mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_MEDIA_TYPE); |
| mReasonColumndId = |
| mDateSortedCursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON); |
| |
| mDateSortedAdapter = new DateSortedDownloadAdapter(this, mDateSortedCursor); |
| mDateOrderedListView.setAdapter(mDateSortedAdapter); |
| mSizeSortedAdapter = new DownloadAdapter(this, mSizeSortedCursor); |
| mSizeOrderedListView.setAdapter(mSizeSortedAdapter); |
| |
| ensureSomeGroupIsExpanded(); |
| } |
| |
| // did the caller want to display the data sorted by size? |
| Bundle extras = getIntent().getExtras(); |
| if (extras != null && |
| extras.getBoolean(DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, false)) { |
| mIsSortedBySize = true; |
| } |
| mSortOption = (Button) findViewById(R.id.sort_button); |
| mSortOption.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| // flip the view |
| mIsSortedBySize = !mIsSortedBySize; |
| // clear all selections |
| mSelectedIds.clear(); |
| chooseListToShow(); |
| } |
| }); |
| |
| chooseListToShow(); |
| mSelectedCountFormat = getString(R.string.selected_count); |
| } |
| |
| /** |
| * If no group is expanded in the date-sorted list, expand the first one. |
| */ |
| private void ensureSomeGroupIsExpanded() { |
| mDateOrderedListView.post(new Runnable() { |
| public void run() { |
| if (mDateSortedAdapter.getGroupCount() == 0) { |
| return; |
| } |
| for (int group = 0; group < mDateSortedAdapter.getGroupCount(); group++) { |
| if (mDateOrderedListView.isGroupExpanded(group)) { |
| return; |
| } |
| } |
| mDateOrderedListView.expandGroup(0); |
| } |
| }); |
| } |
| |
| private void setupViews() { |
| setContentView(R.layout.download_list); |
| ModeCallback modeCallback = new ModeCallback(this); |
| |
| //TODO don't create both views. create only the one needed. |
| mDateOrderedListView = (ExpandableListView) findViewById(R.id.date_ordered_list); |
| mDateOrderedListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); |
| mDateOrderedListView.setMultiChoiceModeListener(modeCallback); |
| mDateOrderedListView.setOnChildClickListener(new OnChildClickListener() { |
| // called when a child is clicked on (this is NOT the checkbox click) |
| @Override |
| public boolean onChildClick(ExpandableListView parent, View v, |
| int groupPosition, int childPosition, long id) { |
| if (!(v instanceof DownloadItem)) { |
| // can this even happen? |
| return false; |
| } |
| if (mSelectedIds.size() > 0) { |
| ((DownloadItem)v).setChecked(true); |
| } else { |
| mDateSortedAdapter.moveCursorToChildPosition(groupPosition, childPosition); |
| handleItemClick(mDateSortedCursor); |
| } |
| return true; |
| } |
| }); |
| mSizeOrderedListView = (ListView) findViewById(R.id.size_ordered_list); |
| mSizeOrderedListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); |
| mSizeOrderedListView.setMultiChoiceModeListener(modeCallback); |
| mSizeOrderedListView.setOnItemClickListener(new OnItemClickListener() { |
| // handle a click from the size-sorted list. (this is NOT the checkbox click) |
| @Override |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| mSizeSortedCursor.moveToPosition(position); |
| handleItemClick(mSizeSortedCursor); |
| } |
| }); |
| mEmptyView = findViewById(R.id.empty); |
| } |
| |
| private static class ModeCallback implements MultiChoiceModeListener { |
| private final DownloadList mDownloadList; |
| |
| public ModeCallback(DownloadList downloadList) { |
| mDownloadList = downloadList; |
| } |
| |
| @Override public void onDestroyActionMode(ActionMode mode) { |
| mDownloadList.mSelectedIds.clear(); |
| mDownloadList.mActionMode = null; |
| } |
| |
| @Override |
| public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |
| return true; |
| } |
| |
| @Override |
| public boolean onCreateActionMode(ActionMode mode, Menu menu) { |
| if (mDownloadList.haveCursors()) { |
| final MenuInflater inflater = mDownloadList.getMenuInflater(); |
| inflater.inflate(R.menu.download_menu, menu); |
| } |
| mDownloadList.mActionMode = mode; |
| return true; |
| } |
| |
| @Override |
| public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| if (mDownloadList.mSelectedIds.size() == 0) { |
| // nothing selected. |
| return true; |
| } |
| switch (item.getItemId()) { |
| case R.id.delete_download: |
| for (Long downloadId : mDownloadList.mSelectedIds.keySet()) { |
| mDownloadList.deleteDownload(downloadId); |
| } |
| // uncheck all checked items |
| ListView lv = mDownloadList.getCurrentView(); |
| SparseBooleanArray checkedPositionList = lv.getCheckedItemPositions(); |
| int checkedPositionListSize = checkedPositionList.size(); |
| ArrayList<DownloadItem> sharedFiles = null; |
| for (int i = 0; i < checkedPositionListSize; i++) { |
| int position = checkedPositionList.keyAt(i); |
| if (checkedPositionList.get(position, false)) { |
| lv.setItemChecked(position, false); |
| onItemCheckedStateChanged(mode, position, 0, false); |
| } |
| } |
| mDownloadList.mSelectedIds.clear(); |
| // update the subtitle |
| onItemCheckedStateChanged(mode, 1, 0, false); |
| break; |
| case R.id.share_download: |
| mDownloadList.shareDownloadedFiles(); |
| break; |
| } |
| return true; |
| } |
| |
| @Override |
| public void onItemCheckedStateChanged(ActionMode mode, int position, long id, |
| boolean checked) { |
| // ignore long clicks on groups |
| if (mDownloadList.isCurrentViewExpandableListView()) { |
| ExpandableListView ev = mDownloadList.getExpandableListView(); |
| long pos = ev.getExpandableListPosition(position); |
| if (checked && (ExpandableListView.getPackedPositionType(pos) == |
| ExpandableListView.PACKED_POSITION_TYPE_GROUP)) { |
| // ignore this click |
| ev.setItemChecked(position, false); |
| return; |
| } |
| } |
| mDownloadList.setActionModeTitle(mode); |
| } |
| } |
| |
| void setActionModeTitle(ActionMode mode) { |
| int numSelected = mSelectedIds.size(); |
| if (numSelected > 0) { |
| mode.setTitle(String.format(mSelectedCountFormat, numSelected, |
| mCurrentCursor.getCount())); |
| } else { |
| mode.setTitle(""); |
| } |
| } |
| |
| private boolean haveCursors() { |
| return mDateSortedCursor != null && mSizeSortedCursor != null; |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| if (haveCursors()) { |
| mDateSortedCursor.registerContentObserver(mContentObserver); |
| mDateSortedCursor.registerDataSetObserver(mDataSetObserver); |
| refresh(); |
| } |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| if (haveCursors()) { |
| mDateSortedCursor.unregisterContentObserver(mContentObserver); |
| mDateSortedCursor.unregisterDataSetObserver(mDataSetObserver); |
| } |
| } |
| |
| private static final String BUNDLE_SAVED_DOWNLOAD_IDS = "download_ids"; |
| private static final String BUNDLE_SAVED_FILENAMES = "filenames"; |
| private static final String BUNDLE_SAVED_MIMETYPES = "mimetypes"; |
| @Override |
| protected void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putBoolean("isSortedBySize", mIsSortedBySize); |
| int len = mSelectedIds.size(); |
| if (len == 0) { |
| return; |
| } |
| long[] selectedIds = new long[len]; |
| String[] fileNames = new String[len]; |
| String[] mimeTypes = new String[len]; |
| int i = 0; |
| for (long id : mSelectedIds.keySet()) { |
| selectedIds[i] = id; |
| SelectionObjAttrs obj = mSelectedIds.get(id); |
| fileNames[i] = obj.getFileName(); |
| mimeTypes[i] = obj.getMimeType(); |
| i++; |
| } |
| outState.putLongArray(BUNDLE_SAVED_DOWNLOAD_IDS, selectedIds); |
| outState.putStringArray(BUNDLE_SAVED_FILENAMES, fileNames); |
| outState.putStringArray(BUNDLE_SAVED_MIMETYPES, mimeTypes); |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(Bundle savedInstanceState) { |
| super.onRestoreInstanceState(savedInstanceState); |
| mIsSortedBySize = savedInstanceState.getBoolean("isSortedBySize"); |
| mSelectedIds.clear(); |
| long[] selectedIds = savedInstanceState.getLongArray(BUNDLE_SAVED_DOWNLOAD_IDS); |
| String[] fileNames = savedInstanceState.getStringArray(BUNDLE_SAVED_FILENAMES); |
| String[] mimeTypes = savedInstanceState.getStringArray(BUNDLE_SAVED_MIMETYPES); |
| if (selectedIds != null && selectedIds.length > 0) { |
| for (int i = 0; i < selectedIds.length; i++) { |
| mSelectedIds.put(selectedIds[i], new SelectionObjAttrs(fileNames[i], mimeTypes[i])); |
| } |
| } |
| chooseListToShow(); |
| } |
| |
| /** |
| * Show the correct ListView and hide the other, or hide both and show the empty view. |
| */ |
| private void chooseListToShow() { |
| mDateOrderedListView.setVisibility(View.GONE); |
| mSizeOrderedListView.setVisibility(View.GONE); |
| |
| if (mDateSortedCursor == null || mDateSortedCursor.getCount() == 0) { |
| mEmptyView.setVisibility(View.VISIBLE); |
| } else { |
| mEmptyView.setVisibility(View.GONE); |
| ListView lv = activeListView(); |
| lv.setVisibility(View.VISIBLE); |
| lv.invalidateViews(); // ensure checkboxes get updated |
| } |
| // restore the ActionMode title if there are selections |
| if (mActionMode != null) { |
| setActionModeTitle(mActionMode); |
| } |
| } |
| |
| ListView getCurrentView() { |
| return mCurrentView; |
| } |
| |
| ExpandableListView getExpandableListView() { |
| return mDateOrderedListView; |
| } |
| |
| boolean isCurrentViewExpandableListView() { |
| return mCurrentViewIsExpandableListView; |
| } |
| |
| private ListView activeListView() { |
| if (mIsSortedBySize) { |
| mCurrentCursor = mSizeSortedCursor; |
| mCurrentView = mSizeOrderedListView; |
| setTitle(R.string.download_title_sorted_by_size); |
| mSortOption.setText(R.string.button_sort_by_date); |
| mCurrentViewIsExpandableListView = false; |
| } else { |
| mCurrentCursor = mDateSortedCursor; |
| mCurrentView = mDateOrderedListView; |
| setTitle(R.string.download_title_sorted_by_date); |
| mSortOption.setText(R.string.button_sort_by_size); |
| mCurrentViewIsExpandableListView = true; |
| } |
| if (mActionMode != null) { |
| mActionMode.finish(); |
| } |
| return mCurrentView; |
| } |
| |
| /** |
| * @return an OnClickListener to delete the given downloadId from the Download Manager |
| */ |
| private DialogInterface.OnClickListener getDeleteClickHandler(final long downloadId) { |
| return new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| deleteDownload(downloadId); |
| } |
| }; |
| } |
| |
| /** |
| * @return an OnClickListener to restart the given downloadId in the Download Manager |
| */ |
| private DialogInterface.OnClickListener getRestartClickHandler(final long downloadId) { |
| return new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| mDownloadManager.restartDownload(downloadId); |
| } |
| }; |
| } |
| |
| /** |
| * Send an Intent to open the download currently pointed to by the given cursor. |
| */ |
| private void openCurrentDownload(Cursor cursor) { |
| final Uri localUri = Uri.parse(cursor.getString(mLocalUriColumnId)); |
| try { |
| getContentResolver().openFileDescriptor(localUri, "r").close(); |
| } catch (FileNotFoundException exc) { |
| Log.d(LOG_TAG, "Failed to open download " + cursor.getLong(mIdColumnId), exc); |
| showFailedDialog(cursor.getLong(mIdColumnId), |
| getString(R.string.dialog_file_missing_body)); |
| return; |
| } catch (IOException exc) { |
| // close() failed, not a problem |
| } |
| |
| final long id = cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID)); |
| final Intent intent = OpenHelper.buildViewIntent(this, id); |
| try { |
| startActivity(intent); |
| } catch (ActivityNotFoundException ex) { |
| Toast.makeText(this, R.string.download_no_application_title, Toast.LENGTH_LONG).show(); |
| } |
| } |
| |
| private void handleItemClick(Cursor cursor) { |
| long id = cursor.getInt(mIdColumnId); |
| switch (cursor.getInt(mStatusColumnId)) { |
| case DownloadManager.STATUS_PENDING: |
| case DownloadManager.STATUS_RUNNING: |
| sendRunningDownloadClickedBroadcast(id); |
| break; |
| |
| case DownloadManager.STATUS_PAUSED: |
| if (isPausedForWifi(cursor)) { |
| mQueuedDownloadId = id; |
| mQueuedDialog = new AlertDialog.Builder(this) |
| .setTitle(R.string.dialog_title_queued_body) |
| .setMessage(R.string.dialog_queued_body) |
| .setPositiveButton(R.string.keep_queued_download, null) |
| .setNegativeButton(R.string.remove_download, getDeleteClickHandler(id)) |
| .setOnCancelListener(new DialogInterface.OnCancelListener() { |
| /** |
| * Called when a dialog for a pending download is canceled. |
| */ |
| @Override |
| public void onCancel(DialogInterface dialog) { |
| mQueuedDownloadId = null; |
| mQueuedDialog = null; |
| } |
| }) |
| .show(); |
| } else { |
| sendRunningDownloadClickedBroadcast(id); |
| } |
| break; |
| |
| case DownloadManager.STATUS_SUCCESSFUL: |
| openCurrentDownload(cursor); |
| break; |
| |
| case DownloadManager.STATUS_FAILED: |
| showFailedDialog(id, getErrorMessage(cursor)); |
| break; |
| } |
| } |
| |
| /** |
| * @return the appropriate error message for the failed download pointed to by cursor |
| */ |
| private String getErrorMessage(Cursor cursor) { |
| switch (cursor.getInt(mReasonColumndId)) { |
| case DownloadManager.ERROR_FILE_ALREADY_EXISTS: |
| if (isOnExternalStorage(cursor)) { |
| return getString(R.string.dialog_file_already_exists); |
| } else { |
| // the download manager should always find a free filename for cache downloads, |
| // so this indicates a strange internal error |
| return getUnknownErrorMessage(); |
| } |
| |
| case DownloadManager.ERROR_INSUFFICIENT_SPACE: |
| if (isOnExternalStorage(cursor)) { |
| return getString(R.string.dialog_insufficient_space_on_external); |
| } else { |
| return getString(R.string.dialog_insufficient_space_on_cache); |
| } |
| |
| case DownloadManager.ERROR_DEVICE_NOT_FOUND: |
| return getString(R.string.dialog_media_not_found); |
| |
| case DownloadManager.ERROR_CANNOT_RESUME: |
| return getString(R.string.dialog_cannot_resume); |
| |
| default: |
| return getUnknownErrorMessage(); |
| } |
| } |
| |
| private boolean isOnExternalStorage(Cursor cursor) { |
| String localUriString = cursor.getString(mLocalUriColumnId); |
| if (localUriString == null) { |
| return false; |
| } |
| Uri localUri = Uri.parse(localUriString); |
| if (!localUri.getScheme().equals("file")) { |
| return false; |
| } |
| String path = localUri.getPath(); |
| String externalRoot = Environment.getExternalStorageDirectory().getPath(); |
| return path.startsWith(externalRoot); |
| } |
| |
| private String getUnknownErrorMessage() { |
| return getString(R.string.dialog_failed_body); |
| } |
| |
| private void showFailedDialog(long downloadId, String dialogBody) { |
| new AlertDialog.Builder(this) |
| .setTitle(R.string.dialog_title_not_available) |
| .setMessage(dialogBody) |
| .setNegativeButton(R.string.delete_download, getDeleteClickHandler(downloadId)) |
| .setPositiveButton(R.string.retry_download, getRestartClickHandler(downloadId)) |
| .show(); |
| } |
| |
| private void sendRunningDownloadClickedBroadcast(long id) { |
| final Intent intent = new Intent(Constants.ACTION_LIST); |
| intent.setPackage(Constants.PROVIDER_PACKAGE_NAME); |
| intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, |
| new long[] { id }); |
| sendBroadcast(intent); |
| } |
| |
| // handle a click on one of the download item checkboxes |
| public void onDownloadSelectionChanged(long downloadId, boolean isSelected, |
| String fileName, String mimeType) { |
| if (isSelected) { |
| mSelectedIds.put(downloadId, new SelectionObjAttrs(fileName, mimeType)); |
| } else { |
| mSelectedIds.remove(downloadId); |
| } |
| } |
| |
| /** |
| * Requery the database and update the UI. |
| */ |
| private void refresh() { |
| mDateSortedCursor.requery(); |
| mSizeSortedCursor.requery(); |
| // Adapters get notification of changes and update automatically |
| } |
| |
| /** |
| * Delete a download from the Download Manager. |
| */ |
| private void deleteDownload(long downloadId) { |
| // let DownloadService do the job of cleaning up the downloads db, mediaprovider db, |
| // and removal of file from sdcard |
| // TODO do the following in asynctask - not on main thread. |
| mDownloadManager.markRowDeleted(downloadId); |
| } |
| |
| public boolean isDownloadSelected(long id) { |
| return mSelectedIds.containsKey(id); |
| } |
| |
| /** |
| * Called when there's a change to the downloads database. |
| */ |
| void handleDownloadsChanged() { |
| checkSelectionForDeletedEntries(); |
| |
| if (mQueuedDownloadId != null && moveToDownload(mQueuedDownloadId)) { |
| if (mDateSortedCursor.getInt(mStatusColumnId) != DownloadManager.STATUS_PAUSED |
| || !isPausedForWifi(mDateSortedCursor)) { |
| mQueuedDialog.cancel(); |
| } |
| } |
| } |
| |
| private boolean isPausedForWifi(Cursor cursor) { |
| return cursor.getInt(mReasonColumndId) == DownloadManager.PAUSED_QUEUED_FOR_WIFI; |
| } |
| |
| /** |
| * Check if any of the selected downloads have been deleted from the downloads database, and |
| * remove such downloads from the selection. |
| */ |
| private void checkSelectionForDeletedEntries() { |
| // gather all existing IDs... |
| Set<Long> allIds = new HashSet<Long>(); |
| for (mDateSortedCursor.moveToFirst(); !mDateSortedCursor.isAfterLast(); |
| mDateSortedCursor.moveToNext()) { |
| allIds.add(mDateSortedCursor.getLong(mIdColumnId)); |
| } |
| |
| // ...and check if any selected IDs are now missing |
| for (Iterator<Long> iterator = mSelectedIds.keySet().iterator(); iterator.hasNext(); ) { |
| if (!allIds.contains(iterator.next())) { |
| iterator.remove(); |
| } |
| } |
| } |
| |
| /** |
| * Move {@link #mDateSortedCursor} to the download with the given ID. |
| * @return true if the specified download ID was found; false otherwise |
| */ |
| private boolean moveToDownload(long downloadId) { |
| for (mDateSortedCursor.moveToFirst(); !mDateSortedCursor.isAfterLast(); |
| mDateSortedCursor.moveToNext()) { |
| if (mDateSortedCursor.getLong(mIdColumnId) == downloadId) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * handle share menu button click when one more files are selected for sharing |
| */ |
| public boolean shareDownloadedFiles() { |
| Intent intent = new Intent(); |
| if (mSelectedIds.size() > 1) { |
| intent.setAction(Intent.ACTION_SEND_MULTIPLE); |
| ArrayList<Parcelable> attachments = new ArrayList<Parcelable>(); |
| ArrayList<String> mimeTypes = new ArrayList<String>(); |
| for (Map.Entry<Long, SelectionObjAttrs> item : mSelectedIds.entrySet()) { |
| final Uri uri = ContentUris.withAppendedId( |
| Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, item.getKey()); |
| final String mimeType = item.getValue().getMimeType(); |
| attachments.add(uri); |
| if (mimeType != null) { |
| mimeTypes.add(mimeType); |
| } |
| } |
| intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); |
| intent.setType(findCommonMimeType(mimeTypes)); |
| } else { |
| // get the entry |
| // since there is ONLY one entry in this, we can do the following |
| for (Map.Entry<Long, SelectionObjAttrs> item : mSelectedIds.entrySet()) { |
| final Uri uri = ContentUris.withAppendedId( |
| Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, item.getKey()); |
| final String mimeType = item.getValue().getMimeType(); |
| intent.setAction(Intent.ACTION_SEND); |
| intent.putExtra(Intent.EXTRA_STREAM, uri); |
| intent.setType(mimeType); |
| } |
| } |
| intent = Intent.createChooser(intent, getText(R.string.download_share_dialog)); |
| startActivity(intent); |
| return true; |
| } |
| |
| private String findCommonMimeType(ArrayList<String> mimeTypes) { |
| // are all mimeypes the same? |
| String str = findCommonString(mimeTypes); |
| if (str != null) { |
| return str; |
| } |
| |
| // are all prefixes of the given mimetypes the same? |
| ArrayList<String> mimeTypePrefixes = new ArrayList<String>(); |
| for (String s : mimeTypes) { |
| mimeTypePrefixes.add(s.substring(0, s.indexOf('/'))); |
| } |
| str = findCommonString(mimeTypePrefixes); |
| if (str != null) { |
| return str + "/*"; |
| } |
| |
| // return generic mimetype |
| return "*/*"; |
| } |
| private String findCommonString(Collection<String> set) { |
| String str = null; |
| boolean found = true; |
| for (String s : set) { |
| if (str == null) { |
| str = s; |
| } else if (!str.equals(s)) { |
| found = false; |
| break; |
| } |
| } |
| return (found) ? str : null; |
| } |
| } |