blob: 5cace19f4e77f8bee535436123a2ec1624db7eae [file] [log] [blame]
The Android Open Source Project0c908882009-03-03 19:32:16 -08001/*
2 * Copyright (C) 2007 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.browser;
18
The Android Open Source Project0c908882009-03-03 19:32:16 -080019import android.app.AlertDialog;
Leon Scroggins24837452010-01-13 13:43:35 -050020import android.app.ExpandableListActivity;
The Android Open Source Project0c908882009-03-03 19:32:16 -080021import android.content.ActivityNotFoundException;
22import android.content.ContentValues;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.content.ContentUris;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.database.Cursor;
29import android.net.Uri;
30import android.os.Bundle;
31import android.provider.Downloads;
32import android.view.ContextMenu;
33import android.view.ContextMenu.ContextMenuInfo;
34import android.view.LayoutInflater;
35import android.view.Menu;
36import android.view.MenuItem;
37import android.view.MenuInflater;
38import android.view.View;
39import android.view.ViewGroup.LayoutParams;
40import android.widget.AdapterView;
Leon Scroggins24837452010-01-13 13:43:35 -050041import android.widget.ExpandableListView;
The Android Open Source Project0c908882009-03-03 19:32:16 -080042
43import java.io.File;
44import java.util.List;
45
46/**
47 * View showing the user's current browser downloads
48 */
Leon Scroggins24837452010-01-13 13:43:35 -050049public class BrowserDownloadPage extends ExpandableListActivity {
The Android Open Source Project0c908882009-03-03 19:32:16 -080050
Leon Scroggins24837452010-01-13 13:43:35 -050051 private ExpandableListView mListView;
The Android Open Source Project0c908882009-03-03 19:32:16 -080052 private Cursor mDownloadCursor;
53 private BrowserDownloadAdapter mDownloadAdapter;
54 private int mStatusColumnId;
55 private int mIdColumnId;
56 private int mTitleColumnId;
Leon Scroggins24837452010-01-13 13:43:35 -050057 private long mContextMenuPosition;
The Android Open Source Project0c908882009-03-03 19:32:16 -080058
59 @Override
60 public void onCreate(Bundle icicle) {
61 super.onCreate(icicle);
62 setContentView(R.layout.browser_downloads_page);
63
64 setTitle(getText(R.string.download_title));
65
Leon Scroggins24837452010-01-13 13:43:35 -050066 mListView = (ExpandableListView) findViewById(android.R.id.list);
Mihai Preda83817df2009-04-28 14:24:47 +020067 mListView.setEmptyView(findViewById(R.id.empty));
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080068 mDownloadCursor = managedQuery(Downloads.Impl.CONTENT_URI,
Leon Scrogginsfedc4932010-01-26 14:15:01 -050069 new String [] {Downloads.Impl._ID, Downloads.Impl.COLUMN_TITLE,
70 Downloads.Impl.COLUMN_STATUS, Downloads.Impl.COLUMN_TOTAL_BYTES,
71 Downloads.Impl.COLUMN_CURRENT_BYTES,
72 Downloads.Impl.COLUMN_DESCRIPTION,
73 Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
74 Downloads.Impl.COLUMN_LAST_MODIFICATION,
75 Downloads.Impl.COLUMN_VISIBILITY,
76 Downloads.Impl.COLUMN_MIME_TYPE},
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080077 null, Downloads.Impl.COLUMN_LAST_MODIFICATION + " DESC");
The Android Open Source Project0c908882009-03-03 19:32:16 -080078
79 // only attach everything to the listbox if we can access
80 // the download database. Otherwise, just show it empty
81 if (mDownloadCursor != null) {
82 mStatusColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080083 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS);
The Android Open Source Project0c908882009-03-03 19:32:16 -080084 mIdColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080085 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl._ID);
The Android Open Source Project0c908882009-03-03 19:32:16 -080086 mTitleColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080087 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE);
The Android Open Source Project0c908882009-03-03 19:32:16 -080088
89 // Create a list "controller" for the data
90 mDownloadAdapter = new BrowserDownloadAdapter(this,
Leon Scroggins24837452010-01-13 13:43:35 -050091 mDownloadCursor, mDownloadCursor.getColumnIndexOrThrow(
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080092 Downloads.Impl.COLUMN_LAST_MODIFICATION));
Mihai Preda83817df2009-04-28 14:24:47 +020093
Leon Scroggins24837452010-01-13 13:43:35 -050094 setListAdapter(mDownloadAdapter);
The Android Open Source Project0c908882009-03-03 19:32:16 -080095 mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
96 mListView.setOnCreateContextMenuListener(this);
Leon Scroggins24837452010-01-13 13:43:35 -050097
The Android Open Source Project0c908882009-03-03 19:32:16 -080098 Intent intent = getIntent();
Leon Scroggins24837452010-01-13 13:43:35 -050099 final int groupToShow = intent == null || intent.getData() == null
100 ? 0 : checkStatus(ContentUris.parseId(intent.getData()));
101 if (mDownloadAdapter.getGroupCount() > groupToShow) {
102 mListView.post(new Runnable() {
103 public void run() {
104 if (mDownloadAdapter.getGroupCount() > groupToShow) {
105 mListView.expandGroup(groupToShow);
106 }
107 }
108 });
The Android Open Source Project0c908882009-03-03 19:32:16 -0800109 }
110 }
111 }
Leon Scrogginsfedc4932010-01-26 14:15:01 -0500112
The Android Open Source Project0c908882009-03-03 19:32:16 -0800113 @Override
114 public boolean onCreateOptionsMenu(Menu menu) {
115 if (mDownloadCursor != null) {
116 MenuInflater inflater = getMenuInflater();
117 inflater.inflate(R.menu.downloadhistory, menu);
118 }
119 return true;
120 }
121
122 @Override
123 public boolean onPrepareOptionsMenu(Menu menu) {
124 boolean showCancel = getCancelableCount() > 0;
125 menu.findItem(R.id.download_menu_cancel_all).setEnabled(showCancel);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800126 return super.onPrepareOptionsMenu(menu);
127 }
128
129 @Override
130 public boolean onOptionsItemSelected(MenuItem item) {
131 switch (item.getItemId()) {
132 case R.id.download_menu_cancel_all:
133 promptCancelAll();
134 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800135 }
136 return false;
137 }
138
Leon Scroggins9be17332010-01-14 15:44:06 -0500139 /**
140 * Remove the file from the list of downloads.
141 * @param id Unique ID of the download to remove.
142 */
143 private void clearFromDownloads(long id) {
144 getContentResolver().delete(ContentUris.withAppendedId(
145 Downloads.Impl.CONTENT_URI, id), null, null);
146 }
147
The Android Open Source Project0c908882009-03-03 19:32:16 -0800148 @Override
149 public boolean onContextItemSelected(MenuItem item) {
Leon Scroggins24837452010-01-13 13:43:35 -0500150 if (!mDownloadAdapter.moveCursorToPackedChildPosition(
151 mContextMenuPosition)) {
152 return false;
153 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800154 switch (item.getItemId()) {
155 case R.id.download_menu_open:
156 hideCompletedDownload();
Leon Scrogginsfedc4932010-01-26 14:15:01 -0500157 openOrDeleteCurrentDownload(false);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800158 return true;
Leon Scroggins9be17332010-01-14 15:44:06 -0500159
160 case R.id.download_menu_delete:
Leon Scroggins9be17332010-01-14 15:44:06 -0500161 new AlertDialog.Builder(this)
162 .setTitle(R.string.download_delete_file)
163 .setIcon(android.R.drawable.ic_dialog_alert)
Leon Scrogginsfedc4932010-01-26 14:15:01 -0500164 .setMessage(mDownloadCursor.getString(mTitleColumnId))
Leon Scroggins9be17332010-01-14 15:44:06 -0500165 .setNegativeButton(R.string.cancel, null)
166 .setPositiveButton(R.string.ok,
167 new DialogInterface.OnClickListener() {
168 public void onClick(DialogInterface dialog,
169 int whichButton) {
Leon Scrogginsfedc4932010-01-26 14:15:01 -0500170 openOrDeleteCurrentDownload(true);
Leon Scroggins9be17332010-01-14 15:44:06 -0500171 }
172 })
173 .show();
174 break;
175
The Android Open Source Project0c908882009-03-03 19:32:16 -0800176 case R.id.download_menu_clear:
177 case R.id.download_menu_cancel:
Leon Scroggins9be17332010-01-14 15:44:06 -0500178 clearFromDownloads(mDownloadCursor.getLong(mIdColumnId));
The Android Open Source Project0c908882009-03-03 19:32:16 -0800179 return true;
180 }
181 return false;
182 }
183
184 @Override
185 public void onCreateContextMenu(ContextMenu menu, View v,
186 ContextMenuInfo menuInfo) {
187 if (mDownloadCursor != null) {
Leon Scroggins24837452010-01-13 13:43:35 -0500188 ExpandableListView.ExpandableListContextMenuInfo info
189 = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
190 long packedPosition = info.packedPosition;
191 // Only show a context menu for the child views
192 if (!mDownloadAdapter.moveCursorToPackedChildPosition(
193 packedPosition)) {
194 return;
195 }
196 mContextMenuPosition = packedPosition;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800197 menu.setHeaderTitle(mDownloadCursor.getString(mTitleColumnId));
198
199 MenuInflater inflater = getMenuInflater();
200 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800201 if (Downloads.Impl.isStatusSuccess(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800202 inflater.inflate(R.menu.downloadhistorycontextfinished, menu);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800203 } else if (Downloads.Impl.isStatusError(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800204 inflater.inflate(R.menu.downloadhistorycontextfailed, menu);
205 } else {
206 inflater.inflate(R.menu.downloadhistorycontextrunning, menu);
207 }
208 }
Leon Scroggins24837452010-01-13 13:43:35 -0500209 super.onCreateContextMenu(menu, v, menuInfo);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800210 }
211
212 /**
213 * This function is called to check the status of the download and if it
214 * has an error show an error dialog.
215 * @param id Row id of the download to check
Leon Scroggins24837452010-01-13 13:43:35 -0500216 * @return Group which contains the download
The Android Open Source Project0c908882009-03-03 19:32:16 -0800217 */
Leon Scroggins24837452010-01-13 13:43:35 -0500218 private int checkStatus(final long id) {
219 int groupToShow = mDownloadAdapter.groupFromChildId(id);
220 if (-1 == groupToShow) return 0;
221 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800222 if (!Downloads.Impl.isStatusError(status)) {
Leon Scroggins24837452010-01-13 13:43:35 -0500223 return groupToShow;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800224 }
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800225 if (status == Downloads.Impl.STATUS_FILE_ERROR) {
Leon Scroggins24837452010-01-13 13:43:35 -0500226 String title = mDownloadCursor.getString(mTitleColumnId);
227 if (title == null || title.length() == 0) {
228 title = getString(R.string.download_unknown_filename);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800229 }
Leon Scroggins24837452010-01-13 13:43:35 -0500230 String msg = getString(R.string.download_file_error_dlg_msg, title);
231 new AlertDialog.Builder(this)
232 .setTitle(R.string.download_file_error_dlg_title)
233 .setIcon(android.R.drawable.ic_popup_disk_full)
234 .setMessage(msg)
235 .setPositiveButton(R.string.ok, null)
236 .setNegativeButton(R.string.retry,
237 new DialogInterface.OnClickListener() {
238 public void onClick(DialogInterface dialog,
239 int whichButton) {
240 resumeDownload(id);
241 }
242 })
243 .show();
244 } else {
245 new AlertDialog.Builder(this)
246 .setTitle(R.string.download_failed_generic_dlg_title)
247 .setIcon(R.drawable.ssl_icon)
248 .setMessage(BrowserDownloadAdapter.getErrorText(status))
249 .setPositiveButton(R.string.ok, null)
250 .show();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800251 }
Leon Scroggins24837452010-01-13 13:43:35 -0500252 return groupToShow;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800253 }
254
255 /**
256 * Resume a given download
257 * @param id Row id of the download to resume
258 */
259 private void resumeDownload(final long id) {
260 // the relevant functionality doesn't exist in the download manager
261 }
262
263 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800264 * Return the number of items in the list that can be canceled.
265 * @return count
266 */
267 private int getCancelableCount() {
268 // Count the number of items that will be canceled.
269 int count = 0;
270 if (mDownloadCursor != null) {
271 for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
272 mDownloadCursor.moveToNext()) {
273 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800274 if (!Downloads.Impl.isStatusCompleted(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800275 count++;
276 }
277 }
278 }
279
280 return count;
281 }
282
283 /**
284 * Prompt the user if they would like to clear the download history
285 */
286 private void promptCancelAll() {
287 int count = getCancelableCount();
288
289 // If there is nothing to do, just return
290 if (count == 0) {
291 return;
292 }
293
294 // Don't show the dialog if there is only one download
295 if (count == 1) {
296 cancelAllDownloads();
297 return;
298 }
299 String msg =
300 getString(R.string.download_cancel_dlg_msg, count);
301 new AlertDialog.Builder(this)
302 .setTitle(R.string.download_cancel_dlg_title)
303 .setIcon(R.drawable.ssl_icon)
304 .setMessage(msg)
305 .setPositiveButton(R.string.ok,
306 new DialogInterface.OnClickListener() {
307 public void onClick(DialogInterface dialog,
308 int whichButton) {
309 cancelAllDownloads();
310 }
311 })
312 .setNegativeButton(R.string.cancel, null)
313 .show();
314 }
315
316 /**
317 * Cancel all downloads. As canceled downloads are not
318 * listed, we removed them from the db. Removing a download
319 * record, cancels the download.
320 */
321 private void cancelAllDownloads() {
322 if (mDownloadCursor.moveToFirst()) {
323 StringBuilder where = new StringBuilder();
324 boolean firstTime = true;
325 while (!mDownloadCursor.isAfterLast()) {
326 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800327 if (!Downloads.Impl.isStatusCompleted(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800328 if (firstTime) {
329 firstTime = false;
330 } else {
331 where.append(" OR ");
332 }
333 where.append("( ");
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800334 where.append(Downloads.Impl._ID);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800335 where.append(" = '");
336 where.append(mDownloadCursor.getLong(mIdColumnId));
337 where.append("' )");
338 }
339 mDownloadCursor.moveToNext();
340 }
341 if (!firstTime) {
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800342 getContentResolver().delete(Downloads.Impl.CONTENT_URI,
The Android Open Source Project0c908882009-03-03 19:32:16 -0800343 where.toString(), null);
344 }
345 }
346 }
347
348 private int getClearableCount() {
349 int count = 0;
350 if (mDownloadCursor.moveToFirst()) {
351 while (!mDownloadCursor.isAfterLast()) {
352 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800353 if (Downloads.Impl.isStatusCompleted(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800354 count++;
355 }
356 mDownloadCursor.moveToNext();
357 }
358 }
359 return count;
360 }
Leon Scroggins4cf7de02010-01-21 10:05:59 -0500361
The Android Open Source Project0c908882009-03-03 19:32:16 -0800362 /**
Leon Scrogginsfedc4932010-01-26 14:15:01 -0500363 * Open or delete content where the download db cursor currently is. Sends
364 * an Intent to perform the action.
365 * @param delete If true, delete the content. Otherwise open it.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800366 */
Leon Scrogginsfedc4932010-01-26 14:15:01 -0500367 private void openOrDeleteCurrentDownload(boolean delete) {
368 int packageColumnId = mDownloadCursor.getColumnIndexOrThrow(
369 Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
370 String packageName = mDownloadCursor.getString(packageColumnId);
371 Intent intent = new Intent(delete ? Intent.ACTION_DELETE
372 : Downloads.Impl.ACTION_NOTIFICATION_CLICKED);
373 Uri contentUri = ContentUris.withAppendedId(
374 Downloads.Impl.CONTENT_URI,
375 mDownloadCursor.getLong(mIdColumnId));
376 intent.setData(contentUri);
377 intent.setPackage(packageName);
378 sendBroadcast(intent);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800379 }
380
Leon Scroggins24837452010-01-13 13:43:35 -0500381 @Override
382 public boolean onChildClick(ExpandableListView parent, View v,
383 int groupPosition, int childPosition, long id) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800384 // Open the selected item
Leon Scroggins24837452010-01-13 13:43:35 -0500385 mDownloadAdapter.moveCursorToChildPosition(groupPosition,
386 childPosition);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800387
388 hideCompletedDownload();
389
390 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800391 if (Downloads.Impl.isStatusSuccess(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800392 // Open it if it downloaded successfully
Leon Scrogginsfedc4932010-01-26 14:15:01 -0500393 openOrDeleteCurrentDownload(false);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800394 } else {
395 // Check to see if there is an error.
396 checkStatus(id);
397 }
Leon Scroggins24837452010-01-13 13:43:35 -0500398 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800399 }
400
401 /**
402 * hides the notification for the download pointed by mDownloadCursor
403 * if the download has completed.
404 */
405 private void hideCompletedDownload() {
406 int status = mDownloadCursor.getInt(mStatusColumnId);
407
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800408 int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(
409 Downloads.Impl.COLUMN_VISIBILITY);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800410 int visibility = mDownloadCursor.getInt(visibilityColumn);
411
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800412 if (Downloads.Impl.isStatusCompleted(status) &&
413 visibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800414 ContentValues values = new ContentValues();
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800415 values.put(Downloads.Impl.COLUMN_VISIBILITY, Downloads.Impl.VISIBILITY_VISIBLE);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800416 getContentResolver().update(
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800417 ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI,
The Android Open Source Project0c908882009-03-03 19:32:16 -0800418 mDownloadCursor.getLong(mIdColumnId)), values, null, null);
419 }
420 }
421}