blob: 22e0e652c3a79a917391392a25e3cd8e6a38e694 [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
19import android.app.Activity;
20import android.app.AlertDialog;
21import 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;
41import android.widget.ListView;
42import android.widget.AdapterView.OnItemClickListener;
43
44import java.io.File;
45import java.util.List;
46
47/**
48 * View showing the user's current browser downloads
49 */
50public class BrowserDownloadPage extends Activity
51 implements View.OnCreateContextMenuListener, OnItemClickListener {
52
53 private ListView mListView;
54 private Cursor mDownloadCursor;
55 private BrowserDownloadAdapter mDownloadAdapter;
56 private int mStatusColumnId;
57 private int mIdColumnId;
58 private int mTitleColumnId;
59 private int mContextMenuPosition;
60
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
68 mListView = (ListView) findViewById(R.id.list);
Mihai Preda83817df2009-04-28 14:24:47 +020069 mListView.setEmptyView(findViewById(R.id.empty));
The Android Open Source Project0c908882009-03-03 19:32:16 -080070
71 mDownloadCursor = managedQuery(Downloads.CONTENT_URI,
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -070072 new String [] {"_id", Downloads.COLUMN_TITLE, Downloads.COLUMN_STATUS,
73 Downloads.COLUMN_TOTAL_BYTES, Downloads.COLUMN_CURRENT_BYTES,
74 Downloads._DATA, Downloads.COLUMN_DESCRIPTION,
75 Downloads.COLUMN_MIME_TYPE, Downloads.COLUMN_LAST_MODIFICATION,
76 Downloads.COLUMN_VISIBILITY},
The Android Open Source Project0c908882009-03-03 19:32:16 -080077 null, null);
78
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 Queru3dc09b22009-03-31 16:49:44 -070083 mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
The Android Open Source Project0c908882009-03-03 19:32:16 -080084 mIdColumnId =
85 mDownloadCursor.getColumnIndexOrThrow(Downloads._ID);
86 mTitleColumnId =
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -070087 mDownloadCursor.getColumnIndexOrThrow(Downloads.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,
91 R.layout.browser_download_item, mDownloadCursor);
Mihai Preda83817df2009-04-28 14:24:47 +020092
The Android Open Source Project0c908882009-03-03 19:32:16 -080093 mListView.setAdapter(mDownloadAdapter);
94 mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
95 mListView.setOnCreateContextMenuListener(this);
96 mListView.setOnItemClickListener(this);
97
98 Intent intent = getIntent();
99 if (intent != null && intent.getData() != null) {
100 int position = checkStatus(
101 ContentUris.parseId(intent.getData()));
102 if (position >= 0) {
103 mListView.setSelection(position);
104 }
105 }
106 }
107 }
108
109 @Override
110 public boolean onCreateOptionsMenu(Menu menu) {
111 if (mDownloadCursor != null) {
112 MenuInflater inflater = getMenuInflater();
113 inflater.inflate(R.menu.downloadhistory, menu);
114 }
115 return true;
116 }
117
118 @Override
119 public boolean onPrepareOptionsMenu(Menu menu) {
120 boolean showCancel = getCancelableCount() > 0;
121 menu.findItem(R.id.download_menu_cancel_all).setEnabled(showCancel);
122
123 boolean showClear = getClearableCount() > 0;
124 menu.findItem(R.id.download_menu_clear_all).setEnabled(showClear);
125 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;
134
135 case R.id.download_menu_clear_all:
136 promptClearList();
137 return true;
138 }
139 return false;
140 }
141
142 @Override
143 public boolean onContextItemSelected(MenuItem item) {
144 mDownloadCursor.moveToPosition(mContextMenuPosition);
145 switch (item.getItemId()) {
146 case R.id.download_menu_open:
147 hideCompletedDownload();
148 openCurrentDownload();
149 return true;
150
151 case R.id.download_menu_clear:
152 case R.id.download_menu_cancel:
153 getContentResolver().delete(
154 ContentUris.withAppendedId(Downloads.CONTENT_URI,
155 mDownloadCursor.getLong(mIdColumnId)), null, null);
156 return true;
157 }
158 return false;
159 }
160
161 @Override
162 public void onCreateContextMenu(ContextMenu menu, View v,
163 ContextMenuInfo menuInfo) {
164 if (mDownloadCursor != null) {
165 AdapterView.AdapterContextMenuInfo info =
166 (AdapterView.AdapterContextMenuInfo) menuInfo;
167 mDownloadCursor.moveToPosition(info.position);
168 mContextMenuPosition = info.position;
169 menu.setHeaderTitle(mDownloadCursor.getString(mTitleColumnId));
170
171 MenuInflater inflater = getMenuInflater();
172 int status = mDownloadCursor.getInt(mStatusColumnId);
173 if (Downloads.isStatusSuccess(status)) {
174 inflater.inflate(R.menu.downloadhistorycontextfinished, menu);
175 } else if (Downloads.isStatusError(status)) {
176 inflater.inflate(R.menu.downloadhistorycontextfailed, menu);
177 } else {
178 inflater.inflate(R.menu.downloadhistorycontextrunning, menu);
179 }
180 }
181 }
182
183 /**
184 * This function is called to check the status of the download and if it
185 * has an error show an error dialog.
186 * @param id Row id of the download to check
187 * @return position of item
188 */
189 int checkStatus(final long id) {
190 int position = -1;
191 for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
192 mDownloadCursor.moveToNext()) {
193 if (id == mDownloadCursor.getLong(mIdColumnId)) {
194 position = mDownloadCursor.getPosition();
195 break;
196 }
197
198 }
199 if (!mDownloadCursor.isAfterLast()) {
200 int status = mDownloadCursor.getInt(mStatusColumnId);
201 if (!Downloads.isStatusError(status)) {
202 return position;
203 }
204
205 if (status == Downloads.STATUS_FILE_ERROR) {
206 String title = mDownloadCursor.getString(mTitleColumnId);
207 if (title == null || title.length() == 0) {
208 title = getString(R.string.download_unknown_filename);
209 }
210 String msg = getString(R.string.download_file_error_dlg_msg,
211 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();
232 }
233 }
234 return position;
235 }
236
237 /**
238 * Resume a given download
239 * @param id Row id of the download to resume
240 */
241 private void resumeDownload(final long id) {
242 // the relevant functionality doesn't exist in the download manager
243 }
244
245 /**
246 * Prompt the user if they would like to clear the download history
247 */
248 private void promptClearList() {
249 new AlertDialog.Builder(this)
250 .setTitle(R.string.download_clear_dlg_title)
251 .setIcon(R.drawable.ssl_icon)
252 .setMessage(R.string.download_clear_dlg_msg)
253 .setPositiveButton(R.string.ok,
254 new DialogInterface.OnClickListener() {
255 public void onClick(DialogInterface dialog,
256 int whichButton) {
257 clearAllDownloads();
258 }
259 })
260 .setNegativeButton(R.string.cancel, null)
261 .show();
262 }
263
264 /**
265 * Return the number of items in the list that can be canceled.
266 * @return count
267 */
268 private int getCancelableCount() {
269 // Count the number of items that will be canceled.
270 int count = 0;
271 if (mDownloadCursor != null) {
272 for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
273 mDownloadCursor.moveToNext()) {
274 int status = mDownloadCursor.getInt(mStatusColumnId);
275 if (!Downloads.isStatusCompleted(status)) {
276 count++;
277 }
278 }
279 }
280
281 return count;
282 }
283
284 /**
285 * Prompt the user if they would like to clear the download history
286 */
287 private void promptCancelAll() {
288 int count = getCancelableCount();
289
290 // If there is nothing to do, just return
291 if (count == 0) {
292 return;
293 }
294
295 // Don't show the dialog if there is only one download
296 if (count == 1) {
297 cancelAllDownloads();
298 return;
299 }
300 String msg =
301 getString(R.string.download_cancel_dlg_msg, count);
302 new AlertDialog.Builder(this)
303 .setTitle(R.string.download_cancel_dlg_title)
304 .setIcon(R.drawable.ssl_icon)
305 .setMessage(msg)
306 .setPositiveButton(R.string.ok,
307 new DialogInterface.OnClickListener() {
308 public void onClick(DialogInterface dialog,
309 int whichButton) {
310 cancelAllDownloads();
311 }
312 })
313 .setNegativeButton(R.string.cancel, null)
314 .show();
315 }
316
317 /**
318 * Cancel all downloads. As canceled downloads are not
319 * listed, we removed them from the db. Removing a download
320 * record, cancels the download.
321 */
322 private void cancelAllDownloads() {
323 if (mDownloadCursor.moveToFirst()) {
324 StringBuilder where = new StringBuilder();
325 boolean firstTime = true;
326 while (!mDownloadCursor.isAfterLast()) {
327 int status = mDownloadCursor.getInt(mStatusColumnId);
328 if (!Downloads.isStatusCompleted(status)) {
329 if (firstTime) {
330 firstTime = false;
331 } else {
332 where.append(" OR ");
333 }
334 where.append("( ");
335 where.append(Downloads._ID);
336 where.append(" = '");
337 where.append(mDownloadCursor.getLong(mIdColumnId));
338 where.append("' )");
339 }
340 mDownloadCursor.moveToNext();
341 }
342 if (!firstTime) {
343 getContentResolver().delete(Downloads.CONTENT_URI,
344 where.toString(), null);
345 }
346 }
347 }
348
349 private int getClearableCount() {
350 int count = 0;
351 if (mDownloadCursor.moveToFirst()) {
352 while (!mDownloadCursor.isAfterLast()) {
353 int status = mDownloadCursor.getInt(mStatusColumnId);
354 if (Downloads.isStatusCompleted(status)) {
355 count++;
356 }
357 mDownloadCursor.moveToNext();
358 }
359 }
360 return count;
361 }
362
363 /**
364 * Clear all stopped downloads, ie canceled (though should not be
365 * there), error and success download items.
366 */
367 private void clearAllDownloads() {
368 if (mDownloadCursor.moveToFirst()) {
369 StringBuilder where = new StringBuilder();
370 boolean firstTime = true;
371 while (!mDownloadCursor.isAfterLast()) {
372 int status = mDownloadCursor.getInt(mStatusColumnId);
373 if (Downloads.isStatusCompleted(status)) {
374 if (firstTime) {
375 firstTime = false;
376 } else {
377 where.append(" OR ");
378 }
379 where.append("( ");
380 where.append(Downloads._ID);
381 where.append(" = '");
382 where.append(mDownloadCursor.getLong(mIdColumnId));
383 where.append("' )");
384 }
385 mDownloadCursor.moveToNext();
386 }
387 if (!firstTime) {
388 getContentResolver().delete(Downloads.CONTENT_URI,
389 where.toString(), null);
390 }
391 }
392 }
393
394 /**
395 * Open the content where the download db cursor currently is
396 */
397 private void openCurrentDownload() {
398 int filenameColumnId =
399 mDownloadCursor.getColumnIndexOrThrow(Downloads._DATA);
400 String filename = mDownloadCursor.getString(filenameColumnId);
401 int mimetypeColumnId =
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -0700402 mDownloadCursor.getColumnIndexOrThrow(Downloads.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)
416 .setTitle(R.string.download_failed_generic_dlg_title)
417 .setIcon(R.drawable.ssl_icon)
418 .setMessage(R.string.download_no_application)
419 .setPositiveButton(R.string.ok, null)
420 .show();
421 }
422 }
423
424 /*
425 * (non-Javadoc)
426 * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView, android.view.View, int, long)
427 */
428 public void onItemClick(AdapterView parent, View view, int position,
429 long id) {
430 // Open the selected item
431 mDownloadCursor.moveToPosition(position);
432
433 hideCompletedDownload();
434
435 int status = mDownloadCursor.getInt(mStatusColumnId);
436 if (Downloads.isStatusSuccess(status)) {
437 // Open it if it downloaded successfully
438 openCurrentDownload();
439 } else {
440 // Check to see if there is an error.
441 checkStatus(id);
442 }
443 }
444
445 /**
446 * hides the notification for the download pointed by mDownloadCursor
447 * if the download has completed.
448 */
449 private void hideCompletedDownload() {
450 int status = mDownloadCursor.getInt(mStatusColumnId);
451
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -0700452 int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800453 int visibility = mDownloadCursor.getInt(visibilityColumn);
454
455 if (Downloads.isStatusCompleted(status) &&
456 visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
457 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -0700458 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800459 getContentResolver().update(
460 ContentUris.withAppendedId(Downloads.CONTENT_URI,
461 mDownloadCursor.getLong(mIdColumnId)), values, null, null);
462 }
463 }
464}