blob: 4397337b03a04bfbf91f59e0a4598f44861a0faf [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);
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._DATA, 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 menu.setHeaderTitle(mDownloadCursor.getString(mTitleColumnId));
174
175 MenuInflater inflater = getMenuInflater();
176 int status = mDownloadCursor.getInt(mStatusColumnId);
177 if (Downloads.isStatusSuccess(status)) {
178 inflater.inflate(R.menu.downloadhistorycontextfinished, menu);
179 } else if (Downloads.isStatusError(status)) {
180 inflater.inflate(R.menu.downloadhistorycontextfailed, menu);
181 } else {
182 inflater.inflate(R.menu.downloadhistorycontextrunning, menu);
183 }
184 }
185 }
186
187 /**
188 * This function is called to check the status of the download and if it
189 * has an error show an error dialog.
190 * @param id Row id of the download to check
191 * @return position of item
192 */
193 int checkStatus(final long id) {
194 int position = -1;
195 for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
196 mDownloadCursor.moveToNext()) {
197 if (id == mDownloadCursor.getLong(mIdColumnId)) {
198 position = mDownloadCursor.getPosition();
199 break;
200 }
201
202 }
203 if (!mDownloadCursor.isAfterLast()) {
204 int status = mDownloadCursor.getInt(mStatusColumnId);
205 if (!Downloads.isStatusError(status)) {
206 return position;
207 }
208
209 if (status == Downloads.STATUS_FILE_ERROR) {
210 String title = mDownloadCursor.getString(mTitleColumnId);
211 if (title == null || title.length() == 0) {
212 title = getString(R.string.download_unknown_filename);
213 }
214 String msg = getString(R.string.download_file_error_dlg_msg,
215 title);
216 new AlertDialog.Builder(this)
217 .setTitle(R.string.download_file_error_dlg_title)
218 .setIcon(android.R.drawable.ic_popup_disk_full)
219 .setMessage(msg)
220 .setPositiveButton(R.string.ok, null)
221 .setNegativeButton(R.string.retry,
222 new DialogInterface.OnClickListener() {
223 public void onClick(DialogInterface dialog,
224 int whichButton) {
225 resumeDownload(id);
226 }
227 })
228 .show();
229 } else {
230 new AlertDialog.Builder(this)
231 .setTitle(R.string.download_failed_generic_dlg_title)
232 .setIcon(R.drawable.ssl_icon)
233 .setMessage(BrowserDownloadAdapter.getErrorText(status))
234 .setPositiveButton(R.string.ok, null)
235 .show();
236 }
237 }
238 return position;
239 }
240
241 /**
242 * Resume a given download
243 * @param id Row id of the download to resume
244 */
245 private void resumeDownload(final long id) {
246 // the relevant functionality doesn't exist in the download manager
247 }
248
249 /**
250 * Prompt the user if they would like to clear the download history
251 */
252 private void promptClearList() {
253 new AlertDialog.Builder(this)
254 .setTitle(R.string.download_clear_dlg_title)
255 .setIcon(R.drawable.ssl_icon)
256 .setMessage(R.string.download_clear_dlg_msg)
257 .setPositiveButton(R.string.ok,
258 new DialogInterface.OnClickListener() {
259 public void onClick(DialogInterface dialog,
260 int whichButton) {
261 clearAllDownloads();
262 }
263 })
264 .setNegativeButton(R.string.cancel, null)
265 .show();
266 }
267
268 /**
269 * Return the number of items in the list that can be canceled.
270 * @return count
271 */
272 private int getCancelableCount() {
273 // Count the number of items that will be canceled.
274 int count = 0;
275 if (mDownloadCursor != null) {
276 for (mDownloadCursor.moveToFirst(); !mDownloadCursor.isAfterLast();
277 mDownloadCursor.moveToNext()) {
278 int status = mDownloadCursor.getInt(mStatusColumnId);
279 if (!Downloads.isStatusCompleted(status)) {
280 count++;
281 }
282 }
283 }
284
285 return count;
286 }
287
288 /**
289 * Prompt the user if they would like to clear the download history
290 */
291 private void promptCancelAll() {
292 int count = getCancelableCount();
293
294 // If there is nothing to do, just return
295 if (count == 0) {
296 return;
297 }
298
299 // Don't show the dialog if there is only one download
300 if (count == 1) {
301 cancelAllDownloads();
302 return;
303 }
304 String msg =
305 getString(R.string.download_cancel_dlg_msg, count);
306 new AlertDialog.Builder(this)
307 .setTitle(R.string.download_cancel_dlg_title)
308 .setIcon(R.drawable.ssl_icon)
309 .setMessage(msg)
310 .setPositiveButton(R.string.ok,
311 new DialogInterface.OnClickListener() {
312 public void onClick(DialogInterface dialog,
313 int whichButton) {
314 cancelAllDownloads();
315 }
316 })
317 .setNegativeButton(R.string.cancel, null)
318 .show();
319 }
320
321 /**
322 * Cancel all downloads. As canceled downloads are not
323 * listed, we removed them from the db. Removing a download
324 * record, cancels the download.
325 */
326 private void cancelAllDownloads() {
327 if (mDownloadCursor.moveToFirst()) {
328 StringBuilder where = new StringBuilder();
329 boolean firstTime = true;
330 while (!mDownloadCursor.isAfterLast()) {
331 int status = mDownloadCursor.getInt(mStatusColumnId);
332 if (!Downloads.isStatusCompleted(status)) {
333 if (firstTime) {
334 firstTime = false;
335 } else {
336 where.append(" OR ");
337 }
338 where.append("( ");
339 where.append(Downloads._ID);
340 where.append(" = '");
341 where.append(mDownloadCursor.getLong(mIdColumnId));
342 where.append("' )");
343 }
344 mDownloadCursor.moveToNext();
345 }
346 if (!firstTime) {
347 getContentResolver().delete(Downloads.CONTENT_URI,
348 where.toString(), null);
349 }
350 }
351 }
352
353 private int getClearableCount() {
354 int count = 0;
355 if (mDownloadCursor.moveToFirst()) {
356 while (!mDownloadCursor.isAfterLast()) {
357 int status = mDownloadCursor.getInt(mStatusColumnId);
358 if (Downloads.isStatusCompleted(status)) {
359 count++;
360 }
361 mDownloadCursor.moveToNext();
362 }
363 }
364 return count;
365 }
366
367 /**
368 * Clear all stopped downloads, ie canceled (though should not be
369 * there), error and success download items.
370 */
371 private void clearAllDownloads() {
372 if (mDownloadCursor.moveToFirst()) {
373 StringBuilder where = new StringBuilder();
374 boolean firstTime = true;
375 while (!mDownloadCursor.isAfterLast()) {
376 int status = mDownloadCursor.getInt(mStatusColumnId);
377 if (Downloads.isStatusCompleted(status)) {
378 if (firstTime) {
379 firstTime = false;
380 } else {
381 where.append(" OR ");
382 }
383 where.append("( ");
384 where.append(Downloads._ID);
385 where.append(" = '");
386 where.append(mDownloadCursor.getLong(mIdColumnId));
387 where.append("' )");
388 }
389 mDownloadCursor.moveToNext();
390 }
391 if (!firstTime) {
392 getContentResolver().delete(Downloads.CONTENT_URI,
393 where.toString(), null);
394 }
395 }
396 }
397
398 /**
399 * Open the content where the download db cursor currently is
400 */
401 private void openCurrentDownload() {
402 int filenameColumnId =
403 mDownloadCursor.getColumnIndexOrThrow(Downloads._DATA);
404 String filename = mDownloadCursor.getString(filenameColumnId);
405 int mimetypeColumnId =
406 mDownloadCursor.getColumnIndexOrThrow(Downloads.MIMETYPE);
407 String mimetype = mDownloadCursor.getString(mimetypeColumnId);
408 Uri path = Uri.parse(filename);
409 // If there is no scheme, then it must be a file
410 if (path.getScheme() == null) {
411 path = Uri.fromFile(new File(filename));
412 }
413 Intent intent = new Intent(Intent.ACTION_VIEW);
414 intent.setDataAndType(path, mimetype);
415 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
416 try {
417 startActivity(intent);
418 } catch (ActivityNotFoundException ex) {
419 new AlertDialog.Builder(this)
420 .setTitle(R.string.download_failed_generic_dlg_title)
421 .setIcon(R.drawable.ssl_icon)
422 .setMessage(R.string.download_no_application)
423 .setPositiveButton(R.string.ok, null)
424 .show();
425 }
426 }
427
428 /*
429 * (non-Javadoc)
430 * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView, android.view.View, int, long)
431 */
432 public void onItemClick(AdapterView parent, View view, int position,
433 long id) {
434 // Open the selected item
435 mDownloadCursor.moveToPosition(position);
436
437 hideCompletedDownload();
438
439 int status = mDownloadCursor.getInt(mStatusColumnId);
440 if (Downloads.isStatusSuccess(status)) {
441 // Open it if it downloaded successfully
442 openCurrentDownload();
443 } else {
444 // Check to see if there is an error.
445 checkStatus(id);
446 }
447 }
448
449 /**
450 * hides the notification for the download pointed by mDownloadCursor
451 * if the download has completed.
452 */
453 private void hideCompletedDownload() {
454 int status = mDownloadCursor.getInt(mStatusColumnId);
455
456 int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.VISIBILITY);
457 int visibility = mDownloadCursor.getInt(visibilityColumn);
458
459 if (Downloads.isStatusCompleted(status) &&
460 visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
461 ContentValues values = new ContentValues();
462 values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE);
463 getContentResolver().update(
464 ContentUris.withAppendedId(Downloads.CONTENT_URI,
465 mDownloadCursor.getLong(mIdColumnId)), values, null, null);
466 }
467 }
468}