blob: e2b11a69b28b51dfa2ea74197803904a592a130d [file] [log] [blame]
The Android Open Source Projectba6d7b82008-10-21 07:00:00 -07001/*
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);
69 LayoutInflater factory = LayoutInflater.from(this);
70 View v = factory.inflate(R.layout.no_downloads, null);
71 addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
72 LayoutParams.FILL_PARENT));
73 mListView.setEmptyView(v);
74
75 mDownloadCursor = managedQuery(Downloads.CONTENT_URI,
76 new String [] {"_id", Downloads.TITLE, Downloads.STATUS,
77 Downloads.TOTAL_BYTES, Downloads.CURRENT_BYTES,
78 Downloads.FILENAME, Downloads.DESCRIPTION,
79 Downloads.MIMETYPE, Downloads.LAST_MODIFICATION,
80 Downloads.VISIBILITY},
81 null, null);
82
83 // only attach everything to the listbox if we can access
84 // the download database. Otherwise, just show it empty
85 if (mDownloadCursor != null) {
86 mStatusColumnId =
87 mDownloadCursor.getColumnIndexOrThrow(Downloads.STATUS);
88 mIdColumnId =
89 mDownloadCursor.getColumnIndexOrThrow(Downloads._ID);
90 mTitleColumnId =
91 mDownloadCursor.getColumnIndexOrThrow(Downloads.TITLE);
92
93 // Create a list "controller" for the data
94 mDownloadAdapter = new BrowserDownloadAdapter(this,
95 R.layout.browser_download_item, mDownloadCursor);
96
97 mListView.setAdapter(mDownloadAdapter);
98 mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
99 mListView.setOnCreateContextMenuListener(this);
100 mListView.setOnItemClickListener(this);
101
102 Intent intent = getIntent();
103 if (intent != null && intent.getData() != null) {
104 int position = checkStatus(
105 ContentUris.parseId(intent.getData()));
106 if (position >= 0) {
107 mListView.setSelection(position);
108 }
109 }
110 }
111 }
112
113 @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);
126
127 boolean showClear = getClearableCount() > 0;
128 menu.findItem(R.id.download_menu_clear_all).setEnabled(showClear);
129 return super.onPrepareOptionsMenu(menu);
130 }
131
132 @Override
133 public boolean onOptionsItemSelected(MenuItem item) {
134 switch (item.getItemId()) {
135 case R.id.download_menu_cancel_all:
136 promptCancelAll();
137 return true;
138
139 case R.id.download_menu_clear_all:
140 promptClearList();
141 return true;
142 }
143 return false;
144 }
145
146 @Override
147 public boolean onContextItemSelected(MenuItem item) {
148 mDownloadCursor.moveToPosition(mContextMenuPosition);
149 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(
158 ContentUris.withAppendedId(Downloads.CONTENT_URI,
159 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) {
169 AdapterView.AdapterContextMenuInfo info =
170 (AdapterView.AdapterContextMenuInfo) menuInfo;
171 mDownloadCursor.moveToPosition(info.position);
172 mContextMenuPosition = info.position;
173
174 MenuInflater inflater = getMenuInflater();
175 int status = mDownloadCursor.getInt(mStatusColumnId);
176 if (Downloads.isStatusSuccess(status)) {
177 inflater.inflate(R.menu.downloadhistorycontextfinished, menu);
178 } else if (Downloads.isStatusError(status)) {
179 inflater.inflate(R.menu.downloadhistorycontextfailed, menu);
180 } else {
181 inflater.inflate(R.menu.downloadhistorycontextrunning, menu);
182 }
183 }
184 }
185
186 /**
187 * This function is called to check the status of the download and if it
188 * has an error show an error dialog.
189 * @param id Row id of the download to check
190 * @return position of item
191 */
192 int checkStatus(final long id) {
193 int position = -1;
194 for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
195 mDownloadCursor.moveToNext()) {
196 if (id == mDownloadCursor.getLong(mIdColumnId)) {
197 position = mDownloadCursor.getPosition();
198 break;
199 }
200
201 }
202 if (!mDownloadCursor.isAfterLast()) {
203 int status = mDownloadCursor.getInt(mStatusColumnId);
204 if (!Downloads.isStatusError(status)) {
205 return position;
206 }
207
208 if (status == Downloads.STATUS_FILE_ERROR) {
209 String title = mDownloadCursor.getString(mTitleColumnId);
210 if (title == null || title.length() == 0) {
211 title = getString(R.string.download_unknown_filename);
212 }
213 String msg = getString(R.string.download_file_error_dlg_msg,
214 title);
215 new AlertDialog.Builder(this)
216 .setTitle(R.string.download_file_error_dlg_title)
217 .setIcon(android.R.drawable.ic_popup_disk_full)
218 .setMessage(msg)
219 .setPositiveButton(R.string.ok, null)
220 .setNegativeButton(R.string.retry,
221 new DialogInterface.OnClickListener() {
222 public void onClick(DialogInterface dialog,
223 int whichButton) {
224 resumeDownload(id);
225 }
226 })
227 .show();
228 } else {
229 new AlertDialog.Builder(this)
230 .setTitle(R.string.download_failed_generic_dlg_title)
231 .setIcon(R.drawable.ssl_icon)
232 .setMessage(BrowserDownloadAdapter.getErrorText(status))
233 .setPositiveButton(R.string.ok, null)
234 .show();
235 }
236 }
237 return position;
238 }
239
240 /**
241 * Resume a given download
242 * @param id Row id of the download to resume
243 */
244 private void resumeDownload(final long id) {
245 Uri record = ContentUris.withAppendedId(Downloads.CONTENT_URI, id);
246 ContentValues values = new ContentValues();
247 values.put(Downloads.CONTROL, Downloads.CONTROL_RUN);
248 getContentResolver().update(record, values, null, null);
249 }
250
251 /**
252 * Prompt the user if they would like to clear the download history
253 */
254 private void promptClearList() {
255 new AlertDialog.Builder(this)
256 .setTitle(R.string.download_clear_dlg_title)
257 .setIcon(R.drawable.ssl_icon)
258 .setMessage(R.string.download_clear_dlg_msg)
259 .setPositiveButton(R.string.ok,
260 new DialogInterface.OnClickListener() {
261 public void onClick(DialogInterface dialog,
262 int whichButton) {
263 clearAllDownloads();
264 }
265 })
266 .setNegativeButton(R.string.cancel, null)
267 .show();
268 }
269
270 /**
271 * Return the number of items in the list that can be canceled.
272 * @return count
273 */
274 private int getCancelableCount() {
275 // Count the number of items that will be canceled.
276 int count = 0;
277 if (mDownloadCursor != null) {
278 for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
279 mDownloadCursor.moveToNext()) {
280 int status = mDownloadCursor.getInt(mStatusColumnId);
281 if (!Downloads.isStatusCompleted(status)) {
282 count++;
283 }
284 }
285 }
286
287 return count;
288 }
289
290 /**
291 * Prompt the user if they would like to clear the download history
292 */
293 private void promptCancelAll() {
294 int count = getCancelableCount();
295
296 // If there is nothing to do, just return
297 if (count == 0) {
298 return;
299 }
300
301 // Don't show the dialog if there is only one download
302 if (count == 1) {
303 cancelAllDownloads();
304 return;
305 }
306 String msg =
307 getString(R.string.download_cancel_dlg_msg, count);
308 new AlertDialog.Builder(this)
309 .setTitle(R.string.download_cancel_dlg_title)
310 .setIcon(R.drawable.ssl_icon)
311 .setMessage(msg)
312 .setPositiveButton(R.string.ok,
313 new DialogInterface.OnClickListener() {
314 public void onClick(DialogInterface dialog,
315 int whichButton) {
316 cancelAllDownloads();
317 }
318 })
319 .setNegativeButton(R.string.cancel, null)
320 .show();
321 }
322
323 /**
324 * Cancel all downloads. As canceled downloads are not
325 * listed, we removed them from the db. Removing a download
326 * record, cancels the download.
327 */
328 private void cancelAllDownloads() {
329 if (mDownloadCursor.moveToFirst()) {
330 StringBuffer where = new StringBuffer();
331 boolean firstTime = true;
332 while (!mDownloadCursor.isAfterLast()) {
333 int status = mDownloadCursor.getInt(mStatusColumnId);
334 if (!Downloads.isStatusCompleted(status)) {
335 if (firstTime) {
336 firstTime = false;
337 } else {
338 where.append(" OR ");
339 }
340 where.append("( ");
341 where.append(Downloads._ID);
342 where.append(" = ");
343 where.append(mDownloadCursor.getLong(mIdColumnId));
344 where.append(" )");
345 }
346 mDownloadCursor.moveToNext();
347 }
348 if (!firstTime) {
349 getContentResolver().delete(Downloads.CONTENT_URI,
350 where.toString(), null);
351 }
352 }
353 }
354
355 private int getClearableCount() {
356 int count = 0;
357 if (mDownloadCursor.moveToFirst()) {
358 while (!mDownloadCursor.isAfterLast()) {
359 int status = mDownloadCursor.getInt(mStatusColumnId);
360 if (Downloads.isStatusCompleted(status)) {
361 count++;
362 }
363 mDownloadCursor.moveToNext();
364 }
365 }
366 return count;
367 }
368
369 /**
370 * Clear all stopped downloads, ie canceled (though should not be
371 * there), error and success download items.
372 */
373 private void clearAllDownloads() {
374 if (mDownloadCursor.moveToFirst()) {
375 StringBuffer where = new StringBuffer();
376 boolean firstTime = true;
377 while (!mDownloadCursor.isAfterLast()) {
378 int status = mDownloadCursor.getInt(mStatusColumnId);
379 if (Downloads.isStatusCompleted(status)) {
380 if (firstTime) {
381 firstTime = false;
382 } else {
383 where.append(" OR ");
384 }
385 where.append("( ");
386 where.append(Downloads._ID);
387 where.append(" = ");
388 where.append(mDownloadCursor.getLong(mIdColumnId));
389 where.append(" )");
390 }
391 mDownloadCursor.moveToNext();
392 }
393 if (!firstTime) {
394 getContentResolver().delete(Downloads.CONTENT_URI,
395 where.toString(), null);
396 }
397 }
398 }
399
400 /**
401 * Open the content where the download db cursor currently is
402 */
403 private void openCurrentDownload() {
404 int filenameColumnId =
405 mDownloadCursor.getColumnIndexOrThrow(Downloads.FILENAME);
406 String filename = mDownloadCursor.getString(filenameColumnId);
407 int mimetypeColumnId =
408 mDownloadCursor.getColumnIndexOrThrow(Downloads.MIMETYPE);
409 String mimetype = mDownloadCursor.getString(mimetypeColumnId);
410 Uri path = Uri.parse(filename);
411 // If there is no scheme, then it must be a file
412 if (path.getScheme() == null) {
413 path = Uri.fromFile(new File(filename));
414 }
415 Intent intent = new Intent(Intent.ACTION_VIEW);
416 intent.setDataAndType(path, mimetype);
417 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
418 try {
419 startActivity(intent);
420 } catch (ActivityNotFoundException ex) {
421 new AlertDialog.Builder(this)
422 .setTitle(R.string.download_failed_generic_dlg_title)
423 .setIcon(R.drawable.ssl_icon)
424 .setMessage(R.string.download_no_application)
425 .setPositiveButton(R.string.ok, null)
426 .show();
427 }
428 }
429
430 /*
431 * (non-Javadoc)
432 * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView, android.view.View, int, long)
433 */
434 public void onItemClick(AdapterView parent, View view, int position,
435 long id) {
436 // Open the selected item
437 mDownloadCursor.moveToPosition(position);
438
439 hideCompletedDownload();
440
441 int status = mDownloadCursor.getInt(mStatusColumnId);
442 if (Downloads.isStatusSuccess(status)) {
443 // Open it if it downloaded successfully
444 openCurrentDownload();
445 } else {
446 // Check to see if there is an error.
447 checkStatus(id);
448 }
449 }
450
451 /**
452 * hides the notification for the download pointed by mDownloadCursor
453 * if the download has completed.
454 */
455 private void hideCompletedDownload() {
456 int status = mDownloadCursor.getInt(mStatusColumnId);
457
458 int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.VISIBILITY);
459 int visibility = mDownloadCursor.getInt(visibilityColumn);
460
461 if (Downloads.isStatusCompleted(status) &&
462 visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
463 ContentValues values = new ContentValues();
464 values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE);
465 getContentResolver().update(
466 ContentUris.withAppendedId(Downloads.CONTENT_URI,
467 mDownloadCursor.getLong(mIdColumnId)), values, null, null);
468 }
469 }
470}