blob: 979e9f14c3cfff012a6d8cf0ba9998afd182f724 [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,
69 new String [] {"_id", Downloads.Impl.COLUMN_TITLE, Downloads.Impl.COLUMN_STATUS,
70 Downloads.Impl.COLUMN_TOTAL_BYTES, Downloads.Impl.COLUMN_CURRENT_BYTES,
71 Downloads.Impl._DATA, Downloads.Impl.COLUMN_DESCRIPTION,
72 Downloads.Impl.COLUMN_MIME_TYPE, Downloads.Impl.COLUMN_LAST_MODIFICATION,
73 Downloads.Impl.COLUMN_VISIBILITY},
74 null, Downloads.Impl.COLUMN_LAST_MODIFICATION + " DESC");
The Android Open Source Project0c908882009-03-03 19:32:16 -080075
76 // only attach everything to the listbox if we can access
77 // the download database. Otherwise, just show it empty
78 if (mDownloadCursor != null) {
79 mStatusColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080080 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS);
The Android Open Source Project0c908882009-03-03 19:32:16 -080081 mIdColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080082 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl._ID);
The Android Open Source Project0c908882009-03-03 19:32:16 -080083 mTitleColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080084 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE);
The Android Open Source Project0c908882009-03-03 19:32:16 -080085
86 // Create a list "controller" for the data
87 mDownloadAdapter = new BrowserDownloadAdapter(this,
Leon Scroggins24837452010-01-13 13:43:35 -050088 mDownloadCursor, mDownloadCursor.getColumnIndexOrThrow(
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080089 Downloads.Impl.COLUMN_LAST_MODIFICATION));
Mihai Preda83817df2009-04-28 14:24:47 +020090
Leon Scroggins24837452010-01-13 13:43:35 -050091 setListAdapter(mDownloadAdapter);
The Android Open Source Project0c908882009-03-03 19:32:16 -080092 mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
93 mListView.setOnCreateContextMenuListener(this);
Leon Scroggins24837452010-01-13 13:43:35 -050094
The Android Open Source Project0c908882009-03-03 19:32:16 -080095 Intent intent = getIntent();
Leon Scroggins24837452010-01-13 13:43:35 -050096 final int groupToShow = intent == null || intent.getData() == null
97 ? 0 : checkStatus(ContentUris.parseId(intent.getData()));
98 if (mDownloadAdapter.getGroupCount() > groupToShow) {
99 mListView.post(new Runnable() {
100 public void run() {
101 if (mDownloadAdapter.getGroupCount() > groupToShow) {
102 mListView.expandGroup(groupToShow);
103 }
104 }
105 });
The Android Open Source Project0c908882009-03-03 19:32:16 -0800106 }
107 }
108 }
109
110 @Override
111 public boolean onCreateOptionsMenu(Menu menu) {
112 if (mDownloadCursor != null) {
113 MenuInflater inflater = getMenuInflater();
114 inflater.inflate(R.menu.downloadhistory, menu);
115 }
116 return true;
117 }
118
119 @Override
120 public boolean onPrepareOptionsMenu(Menu menu) {
121 boolean showCancel = getCancelableCount() > 0;
122 menu.findItem(R.id.download_menu_cancel_all).setEnabled(showCancel);
123
124 boolean showClear = getClearableCount() > 0;
125 menu.findItem(R.id.download_menu_clear_all).setEnabled(showClear);
126 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;
135
136 case R.id.download_menu_clear_all:
137 promptClearList();
138 return true;
139 }
140 return false;
141 }
142
143 @Override
144 public boolean onContextItemSelected(MenuItem item) {
Leon Scroggins24837452010-01-13 13:43:35 -0500145 if (!mDownloadAdapter.moveCursorToPackedChildPosition(
146 mContextMenuPosition)) {
147 return false;
148 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800149 switch (item.getItemId()) {
150 case R.id.download_menu_open:
151 hideCompletedDownload();
152 openCurrentDownload();
153 return true;
154
155 case R.id.download_menu_clear:
156 case R.id.download_menu_cancel:
157 getContentResolver().delete(
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800158 ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI,
The Android Open Source Project0c908882009-03-03 19:32:16 -0800159 mDownloadCursor.getLong(mIdColumnId)), null, null);
160 return true;
161 }
162 return false;
163 }
164
165 @Override
166 public void onCreateContextMenu(ContextMenu menu, View v,
167 ContextMenuInfo menuInfo) {
168 if (mDownloadCursor != null) {
Leon Scroggins24837452010-01-13 13:43:35 -0500169 ExpandableListView.ExpandableListContextMenuInfo info
170 = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
171 long packedPosition = info.packedPosition;
172 // Only show a context menu for the child views
173 if (!mDownloadAdapter.moveCursorToPackedChildPosition(
174 packedPosition)) {
175 return;
176 }
177 mContextMenuPosition = packedPosition;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800178 menu.setHeaderTitle(mDownloadCursor.getString(mTitleColumnId));
179
180 MenuInflater inflater = getMenuInflater();
181 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800182 if (Downloads.Impl.isStatusSuccess(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800183 inflater.inflate(R.menu.downloadhistorycontextfinished, menu);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800184 } else if (Downloads.Impl.isStatusError(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800185 inflater.inflate(R.menu.downloadhistorycontextfailed, menu);
186 } else {
187 inflater.inflate(R.menu.downloadhistorycontextrunning, menu);
188 }
189 }
Leon Scroggins24837452010-01-13 13:43:35 -0500190 super.onCreateContextMenu(menu, v, menuInfo);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800191 }
192
193 /**
194 * This function is called to check the status of the download and if it
195 * has an error show an error dialog.
196 * @param id Row id of the download to check
Leon Scroggins24837452010-01-13 13:43:35 -0500197 * @return Group which contains the download
The Android Open Source Project0c908882009-03-03 19:32:16 -0800198 */
Leon Scroggins24837452010-01-13 13:43:35 -0500199 private int checkStatus(final long id) {
200 int groupToShow = mDownloadAdapter.groupFromChildId(id);
201 if (-1 == groupToShow) return 0;
202 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800203 if (!Downloads.Impl.isStatusError(status)) {
Leon Scroggins24837452010-01-13 13:43:35 -0500204 return groupToShow;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800205 }
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800206 if (status == Downloads.Impl.STATUS_FILE_ERROR) {
Leon Scroggins24837452010-01-13 13:43:35 -0500207 String title = mDownloadCursor.getString(mTitleColumnId);
208 if (title == null || title.length() == 0) {
209 title = getString(R.string.download_unknown_filename);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800210 }
Leon Scroggins24837452010-01-13 13:43:35 -0500211 String msg = getString(R.string.download_file_error_dlg_msg, title);
212 new AlertDialog.Builder(this)
213 .setTitle(R.string.download_file_error_dlg_title)
214 .setIcon(android.R.drawable.ic_popup_disk_full)
215 .setMessage(msg)
216 .setPositiveButton(R.string.ok, null)
217 .setNegativeButton(R.string.retry,
218 new DialogInterface.OnClickListener() {
219 public void onClick(DialogInterface dialog,
220 int whichButton) {
221 resumeDownload(id);
222 }
223 })
224 .show();
225 } else {
226 new AlertDialog.Builder(this)
227 .setTitle(R.string.download_failed_generic_dlg_title)
228 .setIcon(R.drawable.ssl_icon)
229 .setMessage(BrowserDownloadAdapter.getErrorText(status))
230 .setPositiveButton(R.string.ok, null)
231 .show();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800232 }
Leon Scroggins24837452010-01-13 13:43:35 -0500233 return groupToShow;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800234 }
235
236 /**
237 * Resume a given download
238 * @param id Row id of the download to resume
239 */
240 private void resumeDownload(final long id) {
241 // the relevant functionality doesn't exist in the download manager
242 }
243
244 /**
245 * Prompt the user if they would like to clear the download history
246 */
247 private void promptClearList() {
248 new AlertDialog.Builder(this)
249 .setTitle(R.string.download_clear_dlg_title)
250 .setIcon(R.drawable.ssl_icon)
251 .setMessage(R.string.download_clear_dlg_msg)
252 .setPositiveButton(R.string.ok,
253 new DialogInterface.OnClickListener() {
254 public void onClick(DialogInterface dialog,
255 int whichButton) {
256 clearAllDownloads();
257 }
258 })
259 .setNegativeButton(R.string.cancel, null)
260 .show();
261 }
262
263 /**
264 * 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 }
361
362 /**
363 * Clear all stopped downloads, ie canceled (though should not be
364 * there), error and success download items.
365 */
366 private void clearAllDownloads() {
367 if (mDownloadCursor.moveToFirst()) {
368 StringBuilder where = new StringBuilder();
369 boolean firstTime = true;
370 while (!mDownloadCursor.isAfterLast()) {
371 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800372 if (Downloads.Impl.isStatusCompleted(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800373 if (firstTime) {
374 firstTime = false;
375 } else {
376 where.append(" OR ");
377 }
378 where.append("( ");
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800379 where.append(Downloads.Impl._ID);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800380 where.append(" = '");
381 where.append(mDownloadCursor.getLong(mIdColumnId));
382 where.append("' )");
383 }
384 mDownloadCursor.moveToNext();
385 }
386 if (!firstTime) {
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800387 getContentResolver().delete(Downloads.Impl.CONTENT_URI,
The Android Open Source Project0c908882009-03-03 19:32:16 -0800388 where.toString(), null);
389 }
390 }
391 }
392
393 /**
394 * Open the content where the download db cursor currently is
395 */
396 private void openCurrentDownload() {
397 int filenameColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800398 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl._DATA);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800399 String filename = mDownloadCursor.getString(filenameColumnId);
400 int mimetypeColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800401 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800402 String mimetype = mDownloadCursor.getString(mimetypeColumnId);
403 Uri path = Uri.parse(filename);
404 // If there is no scheme, then it must be a file
405 if (path.getScheme() == null) {
406 path = Uri.fromFile(new File(filename));
407 }
408 Intent intent = new Intent(Intent.ACTION_VIEW);
409 intent.setDataAndType(path, mimetype);
410 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
411 try {
412 startActivity(intent);
413 } catch (ActivityNotFoundException ex) {
414 new AlertDialog.Builder(this)
415 .setTitle(R.string.download_failed_generic_dlg_title)
416 .setIcon(R.drawable.ssl_icon)
417 .setMessage(R.string.download_no_application)
418 .setPositiveButton(R.string.ok, null)
419 .show();
420 }
421 }
422
Leon Scroggins24837452010-01-13 13:43:35 -0500423 @Override
424 public boolean onChildClick(ExpandableListView parent, View v,
425 int groupPosition, int childPosition, long id) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800426 // Open the selected item
Leon Scroggins24837452010-01-13 13:43:35 -0500427 mDownloadAdapter.moveCursorToChildPosition(groupPosition,
428 childPosition);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800429
430 hideCompletedDownload();
431
432 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800433 if (Downloads.Impl.isStatusSuccess(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800434 // Open it if it downloaded successfully
435 openCurrentDownload();
436 } else {
437 // Check to see if there is an error.
438 checkStatus(id);
439 }
Leon Scroggins24837452010-01-13 13:43:35 -0500440 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800441 }
442
443 /**
444 * hides the notification for the download pointed by mDownloadCursor
445 * if the download has completed.
446 */
447 private void hideCompletedDownload() {
448 int status = mDownloadCursor.getInt(mStatusColumnId);
449
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800450 int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(
451 Downloads.Impl.COLUMN_VISIBILITY);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800452 int visibility = mDownloadCursor.getInt(visibilityColumn);
453
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800454 if (Downloads.Impl.isStatusCompleted(status) &&
455 visibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800456 ContentValues values = new ContentValues();
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800457 values.put(Downloads.Impl.COLUMN_VISIBILITY, Downloads.Impl.VISIBILITY_VISIBLE);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800458 getContentResolver().update(
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800459 ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI,
The Android Open Source Project0c908882009-03-03 19:32:16 -0800460 mDownloadCursor.getLong(mIdColumnId)), values, null, null);
461 }
462 }
463}