Merge "Update mdpi resources with 2.0 style icons"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6ef43fa..22c721d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
     <uses-permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS"/>
     <uses-permission android:name="android.permission.BACKUP_DATA" />
+    <uses-permission android:name="android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS" />
 
     <application   android:name="Browser"
                    android:label="@string/application_name"
@@ -194,6 +195,13 @@
         <!-- Makes .BrowserActivity the search target for any activity in Browser -->
         <meta-data android:name="android.app.default_searchable" android:value=".BrowserActivity" />
 
+        <receiver android:name=".OpenDownloadReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"/>
+                <action android:name="android.intent.action.DELETE"/>
+                <data android:scheme="content" android:mimeType="vnd.android.cursor.item/download"/>
+            </intent-filter>
+        </receiver>
     </application>
 
 </manifest>
diff --git a/res/drawable-hdpi/arcs.png b/res/drawable-hdpi/arcs.png
new file mode 100644
index 0000000..9555a9f
--- /dev/null
+++ b/res/drawable-hdpi/arcs.png
Binary files differ
diff --git a/res/drawable-hdpi/textfield_voice_search.9.png b/res/drawable-hdpi/textfield_voice_search.9.png
index a1d59c6..d988493 100644
--- a/res/drawable-hdpi/textfield_voice_search.9.png
+++ b/res/drawable-hdpi/textfield_voice_search.9.png
Binary files differ
diff --git a/res/drawable-mdpi/arcs.png b/res/drawable-mdpi/arcs.png
new file mode 100644
index 0000000..5e0d949
--- /dev/null
+++ b/res/drawable-mdpi/arcs.png
Binary files differ
diff --git a/res/drawable-mdpi/textfield_voice_search.9.png b/res/drawable-mdpi/textfield_voice_search.9.png
index b56eaa2..5cf6592 100644
--- a/res/drawable-mdpi/textfield_voice_search.9.png
+++ b/res/drawable-mdpi/textfield_voice_search.9.png
Binary files differ
diff --git a/res/drawable/title_voice.xml b/res/drawable/title_voice.xml
new file mode 100644
index 0000000..0bb1ad3
--- /dev/null
+++ b/res/drawable/title_voice.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+            android:drawable="@*android:drawable/textfield_pressed" />
+    <item android:state_pressed="false"
+            android:drawable="@drawable/textfield_voice_search" />
+</selector>
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 8d471a2..ebb5246 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -479,6 +479,15 @@
                 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
                 || Intent.ACTION_WEB_SEARCH.equals(action)
                 || activateVoiceSearch) {
+            if (current.isInVoiceSearchMode()) {
+                String title = current.getVoiceDisplayTitle();
+                if (title != null && title.equals(intent.getStringExtra(
+                        SearchManager.QUERY))) {
+                    // The user submitted the same search as the last voice
+                    // search, so do nothing.
+                    return;
+                }
+            }
             // If this was a search request (e.g. search query directly typed into the address bar),
             // pass it on to the default web search provider.
             if (handleWebSearchIntent(intent)) {
diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java
index 2a3b69c..0f8f721 100644
--- a/src/com/android/browser/BrowserDownloadAdapter.java
+++ b/src/com/android/browser/BrowserDownloadAdapter.java
@@ -47,7 +47,6 @@
  */
 public class BrowserDownloadAdapter extends DateSortedExpandableListAdapter {
     
-    private int mFilenameColumnId;
     private int mTitleColumnId;
     private int mDescColumnId;
     private int mStatusColumnId;
@@ -58,7 +57,6 @@
 
     public BrowserDownloadAdapter(Context context, Cursor c, int index) {
         super(context, c, index);
-        mFilenameColumnId = c.getColumnIndexOrThrow(Downloads.Impl._DATA);
         mTitleColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE);
         mDescColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESCRIPTION);
         mStatusColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS);
@@ -112,14 +110,7 @@
         TextView tv = (TextView) convertView.findViewById(R.id.download_title);
         String title = getString(mTitleColumnId);
         if (title == null) {
-            String fullFilename = getString(mFilenameColumnId);
-            if (fullFilename == null) {
-                title = r.getString(R.string.download_unknown_filename);
-            } else {
-                // We have a filename, so we can build a title from that
-                title = Downloads.Impl.createTitleFromFilename(context, fullFilename,
-                        getLong(0));
-            }
+            title = r.getString(R.string.download_unknown_filename);
         }
         tv.setText(title);
         
diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java
index bbc804d..5cace19 100644
--- a/src/com/android/browser/BrowserDownloadPage.java
+++ b/src/com/android/browser/BrowserDownloadPage.java
@@ -26,11 +26,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
-import android.database.DatabaseUtils;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Downloads;
-import android.provider.MediaStore;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.LayoutInflater;
@@ -68,11 +66,14 @@
         mListView = (ExpandableListView) findViewById(android.R.id.list);
         mListView.setEmptyView(findViewById(R.id.empty));
         mDownloadCursor = managedQuery(Downloads.Impl.CONTENT_URI,
-                new String [] {"_id", Downloads.Impl.COLUMN_TITLE, Downloads.Impl.COLUMN_STATUS,
-                Downloads.Impl.COLUMN_TOTAL_BYTES, Downloads.Impl.COLUMN_CURRENT_BYTES,
-                Downloads.Impl._DATA, Downloads.Impl.COLUMN_DESCRIPTION,
-                Downloads.Impl.COLUMN_MIME_TYPE, Downloads.Impl.COLUMN_LAST_MODIFICATION,
-                Downloads.Impl.COLUMN_VISIBILITY},
+                new String [] {Downloads.Impl._ID, Downloads.Impl.COLUMN_TITLE,
+                Downloads.Impl.COLUMN_STATUS, Downloads.Impl.COLUMN_TOTAL_BYTES,
+                Downloads.Impl.COLUMN_CURRENT_BYTES,
+                Downloads.Impl.COLUMN_DESCRIPTION,
+                Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
+                Downloads.Impl.COLUMN_LAST_MODIFICATION,
+                Downloads.Impl.COLUMN_VISIBILITY,
+                Downloads.Impl.COLUMN_MIME_TYPE},
                 null, Downloads.Impl.COLUMN_LAST_MODIFICATION + " DESC");
         
         // only attach everything to the listbox if we can access
@@ -108,7 +109,7 @@
             }
         }
     }
-        
+
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         if (mDownloadCursor != null) {
@@ -144,28 +145,6 @@
                 Downloads.Impl.CONTENT_URI, id), null, null);
     }
 
-    /**
-     * Remove the file from the SD card
-     * @param filename Name of the file to delete.
-     * @param mimetype Mimetype of the file to delete.
-     * @return boolean True on success, false on failure.
-     */
-    private boolean deleteFile(String filename, String mimetype) {
-        Uri uri;
-        if (mimetype.startsWith("image")) {
-            uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
-        } else if (mimetype.startsWith("audio")) {
-            uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
-        } else if (mimetype.startsWith("video")) {
-            uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
-        } else {
-            File file = new File(filename);
-            return file.delete();
-        }
-        return getContentResolver().delete(uri, MediaStore.MediaColumns.DATA
-                + " = " + DatabaseUtils.sqlEscapeString(filename), null) > 0;
-    }
-
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         if (!mDownloadAdapter.moveCursorToPackedChildPosition(
@@ -175,31 +154,20 @@
         switch (item.getItemId()) {
             case R.id.download_menu_open:
                 hideCompletedDownload();
-                openCurrentDownload();
+                openOrDeleteCurrentDownload(false);
                 return true;
 
             case R.id.download_menu_delete:
-                int filenameColumnId =
-                        mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl._DATA);
-                final String filename = mDownloadCursor.getString(
-                        filenameColumnId);
-                int mimetypeColumnId = mDownloadCursor.getColumnIndexOrThrow(
-                        Downloads.Impl.COLUMN_MIME_TYPE);
-                final String mimetype = mDownloadCursor.getString(
-                        mimetypeColumnId);
-                final long id = mDownloadCursor.getLong(mIdColumnId);
                 new AlertDialog.Builder(this)
                         .setTitle(R.string.download_delete_file)
                         .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(filename)
+                        .setMessage(mDownloadCursor.getString(mTitleColumnId))
                         .setNegativeButton(R.string.cancel, null)
                         .setPositiveButton(R.string.ok,
                                 new DialogInterface.OnClickListener() {
                                     public void onClick(DialogInterface dialog,
                                             int whichButton) {
-                                        if (deleteFile(filename, mimetype)) {
-                                            clearFromDownloads(id);
-                                        }
+                                        openOrDeleteCurrentDownload(true);
                                     }
                                 })
                         .show();
@@ -392,33 +360,22 @@
     }
 
     /**
-     * Open the content where the download db cursor currently is
+     * Open or delete content where the download db cursor currently is.  Sends
+     * an Intent to perform the action.
+     * @param delete If true, delete the content.  Otherwise open it.
      */
-    private void openCurrentDownload() {
-        int filenameColumnId = 
-                mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl._DATA);
-        String filename = mDownloadCursor.getString(filenameColumnId);
-        int mimetypeColumnId =
-                mDownloadCursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE);
-        String mimetype = mDownloadCursor.getString(mimetypeColumnId);
-        Uri path = Uri.parse(filename);
-        // If there is no scheme, then it must be a file
-        if (path.getScheme() == null) {
-            path = Uri.fromFile(new File(filename));
-        }
-        Intent intent = new Intent(Intent.ACTION_VIEW);
-        intent.setDataAndType(path, mimetype);
-        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        try {
-            startActivity(intent);
-        } catch (ActivityNotFoundException ex) {
-            new AlertDialog.Builder(this)
-                    .setTitle(R.string.download_no_application_title)
-                    .setIcon(R.drawable.ssl_icon)
-                    .setMessage(R.string.download_no_application)
-                    .setPositiveButton(R.string.ok, null)
-                    .show();
-        }
+    private void openOrDeleteCurrentDownload(boolean delete) {
+        int packageColumnId = mDownloadCursor.getColumnIndexOrThrow(
+                Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
+        String packageName = mDownloadCursor.getString(packageColumnId);
+        Intent intent = new Intent(delete ? Intent.ACTION_DELETE
+                : Downloads.Impl.ACTION_NOTIFICATION_CLICKED);
+        Uri contentUri = ContentUris.withAppendedId(
+                Downloads.Impl.CONTENT_URI,
+                mDownloadCursor.getLong(mIdColumnId));
+        intent.setData(contentUri);
+        intent.setPackage(packageName);
+        sendBroadcast(intent);
     }
 
     @Override
@@ -433,7 +390,7 @@
         int status = mDownloadCursor.getInt(mStatusColumnId);
         if (Downloads.Impl.isStatusSuccess(status)) {
             // Open it if it downloaded successfully
-            openCurrentDownload();
+            openOrDeleteCurrentDownload(false);
         } else {
             // Check to see if there is an error.
             checkStatus(id);
diff --git a/src/com/android/browser/OpenDownloadReceiver.java b/src/com/android/browser/OpenDownloadReceiver.java
new file mode 100644
index 0000000..498afc0
--- /dev/null
+++ b/src/com/android/browser/OpenDownloadReceiver.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.browser;
+
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.provider.Downloads;
+import android.provider.MediaStore;
+import android.widget.Toast;
+
+import java.io.File;
+
+/**
+ * This {@link BroadcastReceiver} handles {@link Intent}s to open and delete
+ * files downloaded by the Browser.
+ */
+public class OpenDownloadReceiver extends BroadcastReceiver {
+    public void onReceive(Context context, Intent intent) {
+        ContentResolver cr = context.getContentResolver();
+        Uri data = intent.getData();
+        Cursor cursor = cr.query(data,
+                new String[] { Downloads.Impl._ID, Downloads.Impl._DATA,
+                Downloads.Impl.COLUMN_MIME_TYPE }, null, null, null);
+        if (cursor.moveToFirst()) {
+            String filename = cursor.getString(1);
+            String mimetype = cursor.getString(2);
+            String action = intent.getAction();
+            if (Downloads.ACTION_NOTIFICATION_CLICKED.equals(action)) {
+                Intent launchIntent = new Intent(Intent.ACTION_VIEW);
+                Uri path = Uri.parse(filename);
+                // If there is no scheme, then it must be a file
+                if (path.getScheme() == null) {
+                    path = Uri.fromFile(new File(filename));
+                }
+                launchIntent.setDataAndType(path, mimetype);
+                launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                try {
+                    context.startActivity(launchIntent);
+                } catch (ActivityNotFoundException ex) {
+                    Toast.makeText(context,
+                            R.string.download_no_application_title,
+                            Toast.LENGTH_LONG).show();
+                }
+            } else if (Intent.ACTION_DELETE.equals(action)) {
+                if (deleteFile(cr, filename, mimetype)) {
+                    cr.delete(data, null, null);
+                }
+            }
+        }
+        cursor.close();
+    }
+
+    /**
+     * Remove the file from the SD card
+     * @param cr ContentResolver used to delete the file.
+     * @param filename Name of the file to delete.
+     * @param mimetype Mimetype of the file to delete.
+     * @return boolean True on success, false on failure.
+     */
+    // FIXME: Once there are receivers in other packages to delete downloaded
+    // files, this should be moved to a common place so mutiple packages can
+    // share the code.
+    private boolean deleteFile(ContentResolver cr, String filename,
+            String mimetype) {
+        Uri uri;
+        if (mimetype.startsWith("image")) {
+            uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+        } else if (mimetype.startsWith("audio")) {
+            uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+        } else if (mimetype.startsWith("video")) {
+            uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+        } else {
+            File file = new File(filename);
+            return file.delete();
+        }
+        return cr.delete(uri, MediaStore.MediaColumns.DATA + " = "
+                + DatabaseUtils.sqlEscapeString(filename), null) > 0;
+    }
+}
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
index 743af9b..14ac2ae 100644
--- a/src/com/android/browser/TitleBar.java
+++ b/src/com/android/browser/TitleBar.java
@@ -32,6 +32,10 @@
 import android.os.Handler;
 import android.os.Message;
 import android.speech.RecognizerIntent;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.ImageSpan;
 import android.util.TypedValue;
 import android.view.ContextMenu;
 import android.view.LayoutInflater;
@@ -69,6 +73,7 @@
     private boolean         mInVoiceMode;
     private Drawable        mVoiceModeBackground;
     private Drawable        mNormalBackground;
+    private ImageSpan       mArcsSpan;
 
     private static int LONG_PRESS = 1;
 
@@ -113,8 +118,10 @@
         mStopDrawable = resources.getDrawable(R.drawable.ic_btn_stop_v2);
         mBookmarkDrawable = mRtButton.getDrawable();
         mVoiceModeBackground = resources.getDrawable(
-                R.drawable.textfield_voice_search);
+                R.drawable.title_voice);
         mNormalBackground = mTitleBg.getBackground();
+        mArcsSpan = new ImageSpan(context, R.drawable.arcs,
+                ImageSpan.ALIGN_BASELINE);
     }
 
     private class MyHandler extends Handler {
@@ -193,7 +200,7 @@
                     mHandler.removeMessages(LONG_PRESS);
                     if (mInVoiceMode) {
                         mBrowserActivity.showVoiceSearchResults(
-                                mTitle.getText().toString());
+                                mTitle.getText().toString().trim());
                     } else {
                         mBrowserActivity.onSearchRequested();
                     }
@@ -236,6 +243,7 @@
         if (mInVoiceMode) {
             rightButtonDrawable = mVoiceDrawable;
             titleDrawable = mVoiceModeBackground;
+            mTitle.setEllipsize(null);
         } else {
             titleDrawable = mNormalBackground;
             if (mInLoad) {
@@ -243,7 +251,9 @@
             } else {
                 rightButtonDrawable = mBookmarkDrawable;
             }
+            mTitle.setEllipsize(TextUtils.TruncateAt.END);
         }
+        mTitle.setSingleLine(!mInVoiceMode);
         mTitleBg.setBackgroundDrawable(titleDrawable);
         mRtButton.setImageDrawable(rightButtonDrawable);
     }
@@ -300,7 +310,18 @@
         if (title == null) {
             mTitle.setText(R.string.title_bar_loading);
         } else {
-            mTitle.setText(title);
+            if (mInVoiceMode) {
+                // Add two spaces.  The second one will be replaced with an
+                // image, and the first one will put space between it and the
+                // text
+                SpannableString spannable = new SpannableString(title + "  ");
+                int end = spannable.length();
+                spannable.setSpan(mArcsSpan, end - 1, end,
+                        Spanned.SPAN_MARK_POINT);
+                mTitle.setText(spannable);
+            } else {
+                mTitle.setText(title);
+            }
         }
     }