blob: bbc804d5f0709f6afe9fac6762bf1c913682238b [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;
Leon Scroggins9be17332010-01-14 15:44:06 -050029import android.database.DatabaseUtils;
The Android Open Source Project0c908882009-03-03 19:32:16 -080030import android.net.Uri;
31import android.os.Bundle;
32import android.provider.Downloads;
Leon Scroggins9be17332010-01-14 15:44:06 -050033import android.provider.MediaStore;
The Android Open Source Project0c908882009-03-03 19:32:16 -080034import android.view.ContextMenu;
35import android.view.ContextMenu.ContextMenuInfo;
36import android.view.LayoutInflater;
37import android.view.Menu;
38import android.view.MenuItem;
39import android.view.MenuInflater;
40import android.view.View;
41import android.view.ViewGroup.LayoutParams;
42import android.widget.AdapterView;
Leon Scroggins24837452010-01-13 13:43:35 -050043import android.widget.ExpandableListView;
The Android Open Source Project0c908882009-03-03 19:32:16 -080044
45import java.io.File;
46import java.util.List;
47
48/**
49 * View showing the user's current browser downloads
50 */
Leon Scroggins24837452010-01-13 13:43:35 -050051public class BrowserDownloadPage extends ExpandableListActivity {
The Android Open Source Project0c908882009-03-03 19:32:16 -080052
Leon Scroggins24837452010-01-13 13:43:35 -050053 private ExpandableListView mListView;
The Android Open Source Project0c908882009-03-03 19:32:16 -080054 private Cursor mDownloadCursor;
55 private BrowserDownloadAdapter mDownloadAdapter;
56 private int mStatusColumnId;
57 private int mIdColumnId;
58 private int mTitleColumnId;
Leon Scroggins24837452010-01-13 13:43:35 -050059 private long mContextMenuPosition;
The Android Open Source Project0c908882009-03-03 19:32:16 -080060
61 @Override
62 public void onCreate(Bundle icicle) {
63 super.onCreate(icicle);
64 setContentView(R.layout.browser_downloads_page);
65
66 setTitle(getText(R.string.download_title));
67
Leon Scroggins24837452010-01-13 13:43:35 -050068 mListView = (ExpandableListView) findViewById(android.R.id.list);
Mihai Preda83817df2009-04-28 14:24:47 +020069 mListView.setEmptyView(findViewById(R.id.empty));
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080070 mDownloadCursor = managedQuery(Downloads.Impl.CONTENT_URI,
71 new String [] {"_id", Downloads.Impl.COLUMN_TITLE, Downloads.Impl.COLUMN_STATUS,
72 Downloads.Impl.COLUMN_TOTAL_BYTES, Downloads.Impl.COLUMN_CURRENT_BYTES,
73 Downloads.Impl._DATA, Downloads.Impl.COLUMN_DESCRIPTION,
74 Downloads.Impl.COLUMN_MIME_TYPE, Downloads.Impl.COLUMN_LAST_MODIFICATION,
75 Downloads.Impl.COLUMN_VISIBILITY},
76 null, Downloads.Impl.COLUMN_LAST_MODIFICATION + " DESC");
The Android Open Source Project0c908882009-03-03 19:32:16 -080077
78 // only attach everything to the listbox if we can access
79 // the download database. Otherwise, just show it empty
80 if (mDownloadCursor != null) {
81 mStatusColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080082 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS);
The Android Open Source Project0c908882009-03-03 19:32:16 -080083 mIdColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080084 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl._ID);
The Android Open Source Project0c908882009-03-03 19:32:16 -080085 mTitleColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080086 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE);
The Android Open Source Project0c908882009-03-03 19:32:16 -080087
88 // Create a list "controller" for the data
89 mDownloadAdapter = new BrowserDownloadAdapter(this,
Leon Scroggins24837452010-01-13 13:43:35 -050090 mDownloadCursor, mDownloadCursor.getColumnIndexOrThrow(
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -080091 Downloads.Impl.COLUMN_LAST_MODIFICATION));
Mihai Preda83817df2009-04-28 14:24:47 +020092
Leon Scroggins24837452010-01-13 13:43:35 -050093 setListAdapter(mDownloadAdapter);
The Android Open Source Project0c908882009-03-03 19:32:16 -080094 mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
95 mListView.setOnCreateContextMenuListener(this);
Leon Scroggins24837452010-01-13 13:43:35 -050096
The Android Open Source Project0c908882009-03-03 19:32:16 -080097 Intent intent = getIntent();
Leon Scroggins24837452010-01-13 13:43:35 -050098 final int groupToShow = intent == null || intent.getData() == null
99 ? 0 : checkStatus(ContentUris.parseId(intent.getData()));
100 if (mDownloadAdapter.getGroupCount() > groupToShow) {
101 mListView.post(new Runnable() {
102 public void run() {
103 if (mDownloadAdapter.getGroupCount() > groupToShow) {
104 mListView.expandGroup(groupToShow);
105 }
106 }
107 });
The Android Open Source Project0c908882009-03-03 19:32:16 -0800108 }
109 }
110 }
111
112 @Override
113 public boolean onCreateOptionsMenu(Menu menu) {
114 if (mDownloadCursor != null) {
115 MenuInflater inflater = getMenuInflater();
116 inflater.inflate(R.menu.downloadhistory, menu);
117 }
118 return true;
119 }
120
121 @Override
122 public boolean onPrepareOptionsMenu(Menu menu) {
123 boolean showCancel = getCancelableCount() > 0;
124 menu.findItem(R.id.download_menu_cancel_all).setEnabled(showCancel);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800125 return super.onPrepareOptionsMenu(menu);
126 }
127
128 @Override
129 public boolean onOptionsItemSelected(MenuItem item) {
130 switch (item.getItemId()) {
131 case R.id.download_menu_cancel_all:
132 promptCancelAll();
133 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800134 }
135 return false;
136 }
137
Leon Scroggins9be17332010-01-14 15:44:06 -0500138 /**
139 * Remove the file from the list of downloads.
140 * @param id Unique ID of the download to remove.
141 */
142 private void clearFromDownloads(long id) {
143 getContentResolver().delete(ContentUris.withAppendedId(
144 Downloads.Impl.CONTENT_URI, id), null, null);
145 }
146
147 /**
148 * Remove the file from the SD card
149 * @param filename Name of the file to delete.
150 * @param mimetype Mimetype of the file to delete.
151 * @return boolean True on success, false on failure.
152 */
153 private boolean deleteFile(String filename, String mimetype) {
154 Uri uri;
155 if (mimetype.startsWith("image")) {
156 uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
157 } else if (mimetype.startsWith("audio")) {
158 uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
159 } else if (mimetype.startsWith("video")) {
160 uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
161 } else {
162 File file = new File(filename);
163 return file.delete();
164 }
165 return getContentResolver().delete(uri, MediaStore.MediaColumns.DATA
166 + " = " + DatabaseUtils.sqlEscapeString(filename), null) > 0;
167 }
168
The Android Open Source Project0c908882009-03-03 19:32:16 -0800169 @Override
170 public boolean onContextItemSelected(MenuItem item) {
Leon Scroggins24837452010-01-13 13:43:35 -0500171 if (!mDownloadAdapter.moveCursorToPackedChildPosition(
172 mContextMenuPosition)) {
173 return false;
174 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800175 switch (item.getItemId()) {
176 case R.id.download_menu_open:
177 hideCompletedDownload();
178 openCurrentDownload();
179 return true;
Leon Scroggins9be17332010-01-14 15:44:06 -0500180
181 case R.id.download_menu_delete:
182 int filenameColumnId =
Jean-Baptiste Queru34e60c42010-01-26 16:01:52 -0800183 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl._DATA);
Leon Scroggins9be17332010-01-14 15:44:06 -0500184 final String filename = mDownloadCursor.getString(
185 filenameColumnId);
186 int mimetypeColumnId = mDownloadCursor.getColumnIndexOrThrow(
187 Downloads.Impl.COLUMN_MIME_TYPE);
188 final String mimetype = mDownloadCursor.getString(
189 mimetypeColumnId);
190 final long id = mDownloadCursor.getLong(mIdColumnId);
191 new AlertDialog.Builder(this)
192 .setTitle(R.string.download_delete_file)
193 .setIcon(android.R.drawable.ic_dialog_alert)
194 .setMessage(filename)
195 .setNegativeButton(R.string.cancel, null)
196 .setPositiveButton(R.string.ok,
197 new DialogInterface.OnClickListener() {
198 public void onClick(DialogInterface dialog,
199 int whichButton) {
200 if (deleteFile(filename, mimetype)) {
201 clearFromDownloads(id);
202 }
203 }
204 })
205 .show();
206 break;
207
The Android Open Source Project0c908882009-03-03 19:32:16 -0800208 case R.id.download_menu_clear:
209 case R.id.download_menu_cancel:
Leon Scroggins9be17332010-01-14 15:44:06 -0500210 clearFromDownloads(mDownloadCursor.getLong(mIdColumnId));
The Android Open Source Project0c908882009-03-03 19:32:16 -0800211 return true;
212 }
213 return false;
214 }
215
216 @Override
217 public void onCreateContextMenu(ContextMenu menu, View v,
218 ContextMenuInfo menuInfo) {
219 if (mDownloadCursor != null) {
Leon Scroggins24837452010-01-13 13:43:35 -0500220 ExpandableListView.ExpandableListContextMenuInfo info
221 = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
222 long packedPosition = info.packedPosition;
223 // Only show a context menu for the child views
224 if (!mDownloadAdapter.moveCursorToPackedChildPosition(
225 packedPosition)) {
226 return;
227 }
228 mContextMenuPosition = packedPosition;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800229 menu.setHeaderTitle(mDownloadCursor.getString(mTitleColumnId));
230
231 MenuInflater inflater = getMenuInflater();
232 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800233 if (Downloads.Impl.isStatusSuccess(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800234 inflater.inflate(R.menu.downloadhistorycontextfinished, menu);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800235 } else if (Downloads.Impl.isStatusError(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800236 inflater.inflate(R.menu.downloadhistorycontextfailed, menu);
237 } else {
238 inflater.inflate(R.menu.downloadhistorycontextrunning, menu);
239 }
240 }
Leon Scroggins24837452010-01-13 13:43:35 -0500241 super.onCreateContextMenu(menu, v, menuInfo);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800242 }
243
244 /**
245 * This function is called to check the status of the download and if it
246 * has an error show an error dialog.
247 * @param id Row id of the download to check
Leon Scroggins24837452010-01-13 13:43:35 -0500248 * @return Group which contains the download
The Android Open Source Project0c908882009-03-03 19:32:16 -0800249 */
Leon Scroggins24837452010-01-13 13:43:35 -0500250 private int checkStatus(final long id) {
251 int groupToShow = mDownloadAdapter.groupFromChildId(id);
252 if (-1 == groupToShow) return 0;
253 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800254 if (!Downloads.Impl.isStatusError(status)) {
Leon Scroggins24837452010-01-13 13:43:35 -0500255 return groupToShow;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800256 }
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800257 if (status == Downloads.Impl.STATUS_FILE_ERROR) {
Leon Scroggins24837452010-01-13 13:43:35 -0500258 String title = mDownloadCursor.getString(mTitleColumnId);
259 if (title == null || title.length() == 0) {
260 title = getString(R.string.download_unknown_filename);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800261 }
Leon Scroggins24837452010-01-13 13:43:35 -0500262 String msg = getString(R.string.download_file_error_dlg_msg, title);
263 new AlertDialog.Builder(this)
264 .setTitle(R.string.download_file_error_dlg_title)
265 .setIcon(android.R.drawable.ic_popup_disk_full)
266 .setMessage(msg)
267 .setPositiveButton(R.string.ok, null)
268 .setNegativeButton(R.string.retry,
269 new DialogInterface.OnClickListener() {
270 public void onClick(DialogInterface dialog,
271 int whichButton) {
272 resumeDownload(id);
273 }
274 })
275 .show();
276 } else {
277 new AlertDialog.Builder(this)
278 .setTitle(R.string.download_failed_generic_dlg_title)
279 .setIcon(R.drawable.ssl_icon)
280 .setMessage(BrowserDownloadAdapter.getErrorText(status))
281 .setPositiveButton(R.string.ok, null)
282 .show();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800283 }
Leon Scroggins24837452010-01-13 13:43:35 -0500284 return groupToShow;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800285 }
286
287 /**
288 * Resume a given download
289 * @param id Row id of the download to resume
290 */
291 private void resumeDownload(final long id) {
292 // the relevant functionality doesn't exist in the download manager
293 }
294
295 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800296 * Return the number of items in the list that can be canceled.
297 * @return count
298 */
299 private int getCancelableCount() {
300 // Count the number of items that will be canceled.
301 int count = 0;
302 if (mDownloadCursor != null) {
303 for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
304 mDownloadCursor.moveToNext()) {
305 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800306 if (!Downloads.Impl.isStatusCompleted(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800307 count++;
308 }
309 }
310 }
311
312 return count;
313 }
314
315 /**
316 * Prompt the user if they would like to clear the download history
317 */
318 private void promptCancelAll() {
319 int count = getCancelableCount();
320
321 // If there is nothing to do, just return
322 if (count == 0) {
323 return;
324 }
325
326 // Don't show the dialog if there is only one download
327 if (count == 1) {
328 cancelAllDownloads();
329 return;
330 }
331 String msg =
332 getString(R.string.download_cancel_dlg_msg, count);
333 new AlertDialog.Builder(this)
334 .setTitle(R.string.download_cancel_dlg_title)
335 .setIcon(R.drawable.ssl_icon)
336 .setMessage(msg)
337 .setPositiveButton(R.string.ok,
338 new DialogInterface.OnClickListener() {
339 public void onClick(DialogInterface dialog,
340 int whichButton) {
341 cancelAllDownloads();
342 }
343 })
344 .setNegativeButton(R.string.cancel, null)
345 .show();
346 }
347
348 /**
349 * Cancel all downloads. As canceled downloads are not
350 * listed, we removed them from the db. Removing a download
351 * record, cancels the download.
352 */
353 private void cancelAllDownloads() {
354 if (mDownloadCursor.moveToFirst()) {
355 StringBuilder where = new StringBuilder();
356 boolean firstTime = true;
357 while (!mDownloadCursor.isAfterLast()) {
358 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800359 if (!Downloads.Impl.isStatusCompleted(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800360 if (firstTime) {
361 firstTime = false;
362 } else {
363 where.append(" OR ");
364 }
365 where.append("( ");
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800366 where.append(Downloads.Impl._ID);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800367 where.append(" = '");
368 where.append(mDownloadCursor.getLong(mIdColumnId));
369 where.append("' )");
370 }
371 mDownloadCursor.moveToNext();
372 }
373 if (!firstTime) {
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800374 getContentResolver().delete(Downloads.Impl.CONTENT_URI,
The Android Open Source Project0c908882009-03-03 19:32:16 -0800375 where.toString(), null);
376 }
377 }
378 }
379
380 private int getClearableCount() {
381 int count = 0;
382 if (mDownloadCursor.moveToFirst()) {
383 while (!mDownloadCursor.isAfterLast()) {
384 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800385 if (Downloads.Impl.isStatusCompleted(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800386 count++;
387 }
388 mDownloadCursor.moveToNext();
389 }
390 }
391 return count;
392 }
Leon Scroggins4cf7de02010-01-21 10:05:59 -0500393
The Android Open Source Project0c908882009-03-03 19:32:16 -0800394 /**
395 * Open the content where the download db cursor currently is
396 */
397 private void openCurrentDownload() {
398 int filenameColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800399 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl._DATA);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800400 String filename = mDownloadCursor.getString(filenameColumnId);
401 int mimetypeColumnId =
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800402 mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800403 String mimetype = mDownloadCursor.getString(mimetypeColumnId);
404 Uri path = Uri.parse(filename);
405 // If there is no scheme, then it must be a file
406 if (path.getScheme() == null) {
407 path = Uri.fromFile(new File(filename));
408 }
409 Intent intent = new Intent(Intent.ACTION_VIEW);
410 intent.setDataAndType(path, mimetype);
411 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
412 try {
413 startActivity(intent);
414 } catch (ActivityNotFoundException ex) {
415 new AlertDialog.Builder(this)
Leon Scrogginscf79ca62010-01-21 16:17:56 -0500416 .setTitle(R.string.download_no_application_title)
The Android Open Source Project0c908882009-03-03 19:32:16 -0800417 .setIcon(R.drawable.ssl_icon)
418 .setMessage(R.string.download_no_application)
419 .setPositiveButton(R.string.ok, null)
420 .show();
421 }
422 }
423
Leon Scroggins24837452010-01-13 13:43:35 -0500424 @Override
425 public boolean onChildClick(ExpandableListView parent, View v,
426 int groupPosition, int childPosition, long id) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800427 // Open the selected item
Leon Scroggins24837452010-01-13 13:43:35 -0500428 mDownloadAdapter.moveCursorToChildPosition(groupPosition,
429 childPosition);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800430
431 hideCompletedDownload();
432
433 int status = mDownloadCursor.getInt(mStatusColumnId);
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800434 if (Downloads.Impl.isStatusSuccess(status)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800435 // Open it if it downloaded successfully
436 openCurrentDownload();
437 } else {
438 // Check to see if there is an error.
439 checkStatus(id);
440 }
Leon Scroggins24837452010-01-13 13:43:35 -0500441 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800442 }
443
444 /**
445 * hides the notification for the download pointed by mDownloadCursor
446 * if the download has completed.
447 */
448 private void hideCompletedDownload() {
449 int status = mDownloadCursor.getInt(mStatusColumnId);
450
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800451 int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(
452 Downloads.Impl.COLUMN_VISIBILITY);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800453 int visibility = mDownloadCursor.getInt(visibilityColumn);
454
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800455 if (Downloads.Impl.isStatusCompleted(status) &&
456 visibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800457 ContentValues values = new ContentValues();
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800458 values.put(Downloads.Impl.COLUMN_VISIBILITY, Downloads.Impl.VISIBILITY_VISIBLE);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800459 getContentResolver().update(
Jean-Baptiste Queru1e5bad92010-01-14 16:09:03 -0800460 ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI,
The Android Open Source Project0c908882009-03-03 19:32:16 -0800461 mDownloadCursor.getLong(mIdColumnId)), values, null, null);
462 }
463 }
464}