summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/res/layout/download_list.xml9
-rw-r--r--ui/res/values/strings.xml13
-rw-r--r--ui/src/com/android/providers/downloads/ui/DateSortedExpandableListAdapter.java15
-rw-r--r--ui/src/com/android/providers/downloads/ui/DownloadAdapter.java7
-rw-r--r--ui/src/com/android/providers/downloads/ui/DownloadList.java147
5 files changed, 169 insertions, 22 deletions
diff --git a/ui/res/layout/download_list.xml b/ui/res/layout/download_list.xml
index 241bb3d3..696030ff 100644
--- a/ui/res/layout/download_list.xml
+++ b/ui/res/layout/download_list.xml
@@ -54,8 +54,11 @@
<Button android:id="@+id/selection_delete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:paddingLeft="30dip"
- android:paddingRight="30dip"/>
+ android:layout_weight="1"/>
+ <Button android:id="@+id/deselect_all"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/deselect_all"/>
</LinearLayout>
</LinearLayout>
diff --git a/ui/res/values/strings.xml b/ui/res/values/strings.xml
index 5bebb3ca..8806f7e4 100644
--- a/ui/res/values/strings.xml
+++ b/ui/res/values/strings.xml
@@ -23,6 +23,10 @@
[CHAR LIMIT=200] -->
<string name="no_downloads">No downloads.</string>
+ <!-- Default title for an item in the download list for which no title was provided by the app.
+ [CHAR LIMIT=20] -->
+ <string name="missing_title">&lt;Unknown&gt;</string>
+
<!-- Menu items -->
<!-- Menu option to sort the list of downloads by the size of the downloaded file
@@ -58,6 +62,9 @@
<!-- Text for dialog when user clicks on a download that has not yet begun, but will be started
in the future. [CHAR LIMIT=200] -->
<string name="dialog_queued_body">This file is queued for future download.</string>
+ <!-- Text for dialog when user clicks on a completed download but the file is missing
+ [CHAR LIMIT=200] -->
+ <string name="dialog_file_missing_body">The downloaded file cannot be found.</string>
<!-- Text for a toast appearing when a user clicks on a completed download, informing the user
that there is no application on the device that can open the file that was downloaded
[CHAR LIMIT=200] -->
@@ -75,4 +82,10 @@
<string name="keep_queued_download">Keep</string>
<!-- Text for button to cancel a download that is currently in progress [CHAR LIMIT=25] -->
<string name="cancel_running_download">Cancel</string>
+ <!-- Text for button appearing in a dialog to restart a download, either one that failed or one
+ for which the downloaded file is now missing [CHAR LIMIT=25] -->
+ <string name="retry_download">Retry</string>
+ <!-- Text for button appearing in the pop-up selection menu to deselect all currently selected
+ downloads in the download list [CHAR LIMIT=25] -->
+ <string name="deselect_all">Clear selection</string>
</resources>
diff --git a/ui/src/com/android/providers/downloads/ui/DateSortedExpandableListAdapter.java b/ui/src/com/android/providers/downloads/ui/DateSortedExpandableListAdapter.java
index 88ffdee3..58dd4bb3 100644
--- a/ui/src/com/android/providers/downloads/ui/DateSortedExpandableListAdapter.java
+++ b/ui/src/com/android/providers/downloads/ui/DateSortedExpandableListAdapter.java
@@ -66,6 +66,16 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
}
}
+ private class MyDataSetObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ buildMap();
+ for (DataSetObserver o : mObservers) {
+ o.onChanged();
+ }
+ }
+ }
+
public DateSortedExpandableListAdapter(Context context, Cursor cursor,
int dateIndex) {
mContext = context;
@@ -74,6 +84,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
mCursor = cursor;
mIdIndex = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID);
cursor.registerContentObserver(new ChangeObserver());
+ cursor.registerDataSetObserver(new MyDataSetObserver());
mDateIndex = dateIndex;
buildMap();
}
@@ -255,10 +266,6 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
return;
}
mCursor.requery();
- buildMap();
- for (DataSetObserver o : mObservers) {
- o.onChanged();
- }
}
public View getGroupView(int groupPosition, boolean isExpanded,
diff --git a/ui/src/com/android/providers/downloads/ui/DownloadAdapter.java b/ui/src/com/android/providers/downloads/ui/DownloadAdapter.java
index a79122a4..b868ffc4 100644
--- a/ui/src/com/android/providers/downloads/ui/DownloadAdapter.java
+++ b/ui/src/com/android/providers/downloads/ui/DownloadAdapter.java
@@ -96,8 +96,11 @@ public class DownloadAdapter extends CursorAdapter {
// Retrieve the icon for this download
retrieveAndSetIcon(convertView);
- // TODO: default text for null title?
- setTextForView(convertView, R.id.download_title, mCursor.getString(mTitleColumnId));
+ String title = mCursor.getString(mTitleColumnId);
+ if (title.isEmpty()) {
+ title = mResources.getString(R.string.missing_title);
+ }
+ setTextForView(convertView, R.id.download_title, title);
setTextForView(convertView, R.id.domain, mCursor.getString(mDescriptionColumnId));
setTextForView(convertView, R.id.size_text, getSizeText());
setTextForView(convertView, R.id.status_text, mResources.getString(getStatusStringId()));
diff --git a/ui/src/com/android/providers/downloads/ui/DownloadList.java b/ui/src/com/android/providers/downloads/ui/DownloadList.java
index 1b7c727e..dd9a6083 100644
--- a/ui/src/com/android/providers/downloads/ui/DownloadList.java
+++ b/ui/src/com/android/providers/downloads/ui/DownloadList.java
@@ -21,11 +21,14 @@ import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
+import android.database.ContentObserver;
import android.database.Cursor;
import android.net.DownloadManager;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.provider.Downloads;
import android.view.Menu;
import android.view.MenuInflater;
@@ -44,6 +47,7 @@ import android.widget.Toast;
import com.android.providers.downloads.ui.DownloadItem.DownloadSelectListener;
+import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
@@ -53,7 +57,7 @@ import java.util.Set;
*/
public class DownloadList extends Activity
implements OnChildClickListener, OnItemClickListener, DownloadSelectListener,
- OnClickListener {
+ OnClickListener, OnCancelListener {
private ExpandableListView mDateOrderedListView;
private ListView mSizeOrderedListView;
private View mEmptyView;
@@ -65,6 +69,7 @@ public class DownloadList extends Activity
private DateSortedDownloadAdapter mDateSortedAdapter;
private Cursor mSizeSortedCursor;
private DownloadAdapter mSizeSortedAdapter;
+ private MyContentObserver mContentObserver = new MyContentObserver();
private int mStatusColumnId;
private int mIdColumnId;
@@ -74,14 +79,34 @@ public class DownloadList extends Activity
private boolean mIsSortedBySize = false;
private Set<Long> mSelectedIds = new HashSet<Long>();
+ /**
+ * 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 mPendingDownloadId = null;
+ private AlertDialog mPendingDialog;
+
+ private class MyContentObserver extends ContentObserver {
+ public MyContentObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ handleDownloadsChanged();
+ }
+ }
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setupViews();
mDownloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
- mDateSortedCursor = mDownloadManager.query(new DownloadManager.Query());
- mSizeSortedCursor = mDownloadManager.query(new DownloadManager.Query()
+ DownloadManager.Query baseQuery = new DownloadManager.Query()
+ .setOnlyIncludeVisibleInDownloadsUi(true);
+ mDateSortedCursor = mDownloadManager.query(baseQuery);
+ mSizeSortedCursor = mDownloadManager.query(baseQuery
.orderBy(DownloadManager.COLUMN_TOTAL_SIZE_BYTES,
DownloadManager.Query.ORDER_DESCENDING));
@@ -131,15 +156,28 @@ public class DownloadList extends Activity
mSelectionMenuView = (ViewGroup) findViewById(R.id.selection_menu);
mSelectionDeleteButton = (Button) findViewById(R.id.selection_delete);
mSelectionDeleteButton.setOnClickListener(this);
+
+ ((Button) findViewById(R.id.deselect_all)).setOnClickListener(this);
}
@Override
protected void onResume() {
super.onResume();
+ if (mDateSortedCursor != null) {
+ mDateSortedCursor.registerContentObserver(mContentObserver);
+ }
refresh();
}
@Override
+ protected void onPause() {
+ super.onPause();
+ if (mDateSortedCursor != null) {
+ mDateSortedCursor.unregisterContentObserver(mContentObserver);
+ }
+ }
+
+ @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("isSortedBySize", mIsSortedBySize);
@@ -237,11 +275,28 @@ public class DownloadList extends Activity
}
/**
+ * @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) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
Uri fileUri = Uri.parse(cursor.getString(mLocalUriColumnId));
+ if (!new File(fileUri.getPath()).exists()) {
+ showFailedDialog(cursor.getLong(mIdColumnId), R.string.dialog_file_missing_body);
+ return;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(fileUri, cursor.getString(mMediaTypeColumnId));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
@@ -255,11 +310,13 @@ public class DownloadList extends Activity
long id = cursor.getInt(mIdColumnId);
switch (cursor.getInt(mStatusColumnId)) {
case DownloadManager.STATUS_PENDING:
- new AlertDialog.Builder(this)
+ mPendingDownloadId = id;
+ mPendingDialog = new AlertDialog.Builder(this)
.setTitle(R.string.dialog_title_not_available)
- .setMessage("This file is queued for future download.")
+ .setMessage(R.string.dialog_queued_body)
.setPositiveButton(R.string.keep_queued_download, null)
.setNegativeButton(R.string.remove_download, getDeleteClickHandler(id))
+ .setOnCancelListener(this)
.show();
break;
@@ -273,16 +330,20 @@ public class DownloadList extends Activity
break;
case DownloadManager.STATUS_FAILED:
- new AlertDialog.Builder(this)
- .setTitle(R.string.dialog_title_not_available)
- .setMessage(getResources().getString(R.string.dialog_failed_body))
- .setPositiveButton(R.string.remove_download, getDeleteClickHandler(id))
- // TODO button to retry download
- .show();
+ showFailedDialog(id, R.string.dialog_failed_body);
break;
}
}
+ private void showFailedDialog(long downloadId, int dialogBodyResource) {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.dialog_title_not_available)
+ .setMessage(getResources().getString(dialogBodyResource))
+ .setPositiveButton(R.string.remove_download, getDeleteClickHandler(downloadId))
+ .setNegativeButton(R.string.retry_download, getRestartClickHandler(downloadId))
+ .show();
+ }
+
/**
* TODO use constants/shared code?
*/
@@ -377,7 +438,11 @@ public class DownloadList extends Activity
deleteDownload(downloadId);
}
clearSelection();
- return;
+ break;
+
+ case R.id.deselect_all:
+ clearSelection();
+ break;
}
}
@@ -406,4 +471,60 @@ public class DownloadList extends Activity
public boolean isDownloadSelected(long id) {
return mSelectedIds.contains(id);
}
+
+ /**
+ * Called when there's a change to the downloads database.
+ */
+ void handleDownloadsChanged() {
+ checkSelectionForDeletedEntries();
+
+ if (mPendingDownloadId != null && moveToDownload(mPendingDownloadId)) {
+ if (mDateSortedCursor.getInt(mStatusColumnId) != DownloadManager.STATUS_PENDING) {
+ mPendingDialog.cancel();
+ }
+ }
+ }
+
+ /**
+ * 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.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;
+ }
+
+ /**
+ * Called when a dialog for a pending download is canceled.
+ */
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mPendingDownloadId = null;
+ mPendingDialog = null;
+ }
}