am ca758ab0: (-s ours) am b9f57353: Import revised translations. DO NOT MERGE
* commit 'ca758ab0d131ca11ffc91d39255a9155ee657570':
Import revised translations. DO NOT MERGE
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f88dfd9..9ddc7b5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -35,6 +35,7 @@
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<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="com.android.launcher.permission.INSTALL_SHORTCUT"/>
@@ -165,7 +166,7 @@
</activity>
<activity android:name="AddBookmarkPage" android:label="Save bookmark"
- android:theme="@style/Dialog"
+ android:theme="@style/DialogWhenLarge"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden|adjustPan">
<intent-filter>
@@ -193,6 +194,9 @@
android:name=".widget.BookmarkThumbnailWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />
+ <receiver
+ android:name=".widget.BookmarkWidgetProxy"
+ android:exported="false" />
<!-- Makes .BrowserActivity the search target for any activity in Browser -->
<meta-data android:name="android.app.default_searchable" android:value=".BrowserActivity" />
diff --git a/res/drawable-hdpi/bookmarks_widget_thumb_selector_focused.9.png b/res/drawable-hdpi/bookmarks_widget_thumb_selector_focused.9.png
new file mode 100644
index 0000000..9983890
--- /dev/null
+++ b/res/drawable-hdpi/bookmarks_widget_thumb_selector_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi/bookmarks_widget_thumb_selector_longpressed.9.png b/res/drawable-hdpi/bookmarks_widget_thumb_selector_longpressed.9.png
new file mode 100644
index 0000000..34704ef
--- /dev/null
+++ b/res/drawable-hdpi/bookmarks_widget_thumb_selector_longpressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/bookmarks_widget_thumb_selector_focused.9.png b/res/drawable-mdpi/bookmarks_widget_thumb_selector_focused.9.png
new file mode 100644
index 0000000..5bcc7c8
--- /dev/null
+++ b/res/drawable-mdpi/bookmarks_widget_thumb_selector_focused.9.png
Binary files differ
diff --git a/res/drawable-mdpi/qc_background_normal.png b/res/drawable-mdpi/qc_background_normal.png
new file mode 100644
index 0000000..539b45d
--- /dev/null
+++ b/res/drawable-mdpi/qc_background_normal.png
Binary files differ
diff --git a/res/drawable-mdpi/qc_background_selected.png b/res/drawable-mdpi/qc_background_selected.png
new file mode 100644
index 0000000..c0a6efc
--- /dev/null
+++ b/res/drawable-mdpi/qc_background_selected.png
Binary files differ
diff --git a/res/drawable/bookmark_thumb_selector.xml b/res/drawable/bookmark_thumb_selector.xml
index 59d9405..d4a12a3 100644
--- a/res/drawable/bookmark_thumb_selector.xml
+++ b/res/drawable/bookmark_thumb_selector.xml
@@ -16,6 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+ <item android:state_focused="true" android:state_pressed="false" android:drawable="@drawable/bookmarks_widget_thumb_selector_focused" />
<item android:state_pressed="true" android:drawable="@drawable/bookmark_thumb_selector_transition" />
<item android:drawable="@android:color/transparent" />
</selector>
diff --git a/res/layout-land/http_authentication.xml b/res/layout-land/http_authentication.xml
deleted file mode 100644
index 3fa7e4f..0000000
--- a/res/layout-land/http_authentication.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:orientation="vertical" >
-
- <TableLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="12dip"
- android:gravity="center_horizontal" >
-
- <TableRow>
- <TextView android:id="@+id/username_view"
- android:text="@string/username"
- android:gravity="right"
- android:layout_marginLeft="20dip" />
-
- <EditText android:id="@+id/username_edit"
- android:scrollHorizontally="true"
- android:autoText="false"
- android:capitalize="none"
- android:gravity="fill_horizontal"
- android:layout_weight="1"
- android:layout_marginLeft="10dip"
- android:layout_marginRight="20dip"
- android:layout_marginBottom="12dip" />
- </TableRow>
-
- <TableRow>
- <TextView android:id="@+id/password_view"
- android:text="@string/password"
- android:gravity="right"
- android:layout_marginLeft="20dip" />
-
- <EditText android:id="@+id/password_edit"
- android:scrollHorizontally="true"
- android:autoText="false"
- android:capitalize="none"
- android:gravity="fill_horizontal"
- android:layout_weight="1"
- android:layout_marginLeft="10dip"
- android:layout_marginRight="20dip"
- android:layout_marginBottom="12dip"
- android:password="true" />
- </TableRow>
- </TableLayout>
-
-</LinearLayout>
diff --git a/res/layout-xlarge/browser_add_bookmark.xml b/res/layout-xlarge/browser_add_bookmark.xml
new file mode 100644
index 0000000..14edecf
--- /dev/null
+++ b/res/layout-xlarge/browser_add_bookmark.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/add_bookmark_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ >
+
+ <include layout="@layout/browser_add_bookmark_content" />
+
+</LinearLayout>
diff --git a/res/layout/bookmarkthumbnailwidget_item.xml b/res/layout/bookmarkthumbnailwidget_item.xml
index b67b386..3247806 100644
--- a/res/layout/bookmarkthumbnailwidget_item.xml
+++ b/res/layout/bookmarkthumbnailwidget_item.xml
@@ -21,7 +21,7 @@
android:layout_height="wrap_content">
<ImageView
android:id="@+id/thumb"
- android:src="@drawable/browser_thumbnail"
+ android:src="@drawable/thumbnail_bookmarks_widget_no_bookmark_holo"
android:layout_width="match_parent"
android:layout_height="@dimen/widgetThumbnailHeight"
android:scaleType="centerCrop"
diff --git a/res/layout/browser_add_bookmark.xml b/res/layout/browser_add_bookmark.xml
index f5c09a9..3720790 100644
--- a/res/layout/browser_add_bookmark.xml
+++ b/res/layout/browser_add_bookmark.xml
@@ -4,9 +4,9 @@
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.
@@ -15,209 +15,10 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/add_bookmark_width"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="vertical"
>
- <RelativeLayout android:id="@+id/crumb_holder"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:gravity="center_vertical"
- android:visibility="gone"
- android:paddingLeft="5dip"
- android:paddingRight="5dip"
- >
- <com.android.browser.BreadCrumbView android:id="@+id/crumbs"
- android:layout_width="wrap_content"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:layout_alignParentLeft="true"
- android:layout_toLeftOf="@+id/add_divider"
- android:layout_centerVertical="true"
- />
- <TextView
- android:id="@+id/add_new_folder"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_alignBaseline="@+id/crumbs"
- android:drawableLeft="@drawable/ic_add_string"
- android:gravity="center_vertical"
- android:text="@string/new_folder"
- android:visibility="gone"
- android:layout_centerVertical="true"
- android:textAppearance="?android:attr/textAppearanceMedium" />
- <ImageView android:id="@+id/add_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toLeftOf="@+id/add_new_folder"
- android:src="@drawable/crumb_divider"
- android:layout_centerVertical="true"
- />
- </RelativeLayout>
- <LinearLayout android:id="@+id/title_holder"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:paddingLeft="5dip"
- android:paddingRight="5dip"
- >
- <TextView android:id="@+id/fake_title"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:gravity="center_vertical"
- android:drawableLeft="@drawable/ic_bookmark_on_holo_dark"
- android:text="@string/bookmark_this_page"
- android:textAppearance="?android:attr/textAppearanceMedium" />
- <ImageView android:id="@+id/remove_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:src="@drawable/crumb_divider"
- android:layout_centerVertical="true"
- android:visibility="gone"
- />
- <TextView android:id="@+id/remove"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:gravity="center_vertical"
- android:text="@string/remove"
- android:drawableLeft="@drawable/trashcan"
- android:visibility="gone"
- android:textAppearance="?android:attr/textAppearanceMedium" />
- </LinearLayout>
- <View android:id="@+id/titleDivider"
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:gravity="fill_horizontal"
- android:background="?android:attr/colorForeground"
- />
-
- <TableLayout android:id="@+id/default_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/folder_selector_height"
- android:layout_weight="1"
- android:stretchColumns="1"
- android:shrinkColumns="1"
- android:paddingTop="20dip"
- android:paddingLeft="20dip"
- android:paddingRight="20dip" >
- <TableRow>
- <TextView
- android:id="@+id/titleText"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_marginBottom="40dip"
- android:text="@string/name"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <EditText
- android:id="@+id/title"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_marginRight="20dip"
- android:layout_marginLeft="20dip"
- android:gravity="fill_horizontal"
- android:inputType="textCapSentences"
- android:ellipsize="end"
- android:textAppearance="?android:attr/textAppearanceMedium" />
- </TableRow>
-
- <TableRow
- android:id="@+id/row_address">
- <TextView
- android:id="@+id/addressText"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:text="@string/location"
- android:gravity="left"
- android:layout_marginBottom="40dip"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <EditText
- android:id="@+id/address"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_marginRight="20dip"
- android:layout_marginLeft="20dip"
- android:hint="@string/http"
- android:gravity="fill_horizontal"
- android:inputType="textUri"
- android:ellipsize="end"
- android:textAppearance="?android:attr/textAppearanceMedium" />
- </TableRow>
- <TableRow>
- <TextView
- android:id="@+id/add_to"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:text="@string/containing_folder"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <view class="com.android.browser.addbookmark.FolderSpinner"
- android:id="@+id/folder"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_marginRight="20dip"
- android:layout_marginLeft="20dip"
- android:spinnerMode="dropdown"
- android:gravity="center_vertical"
- />
- </TableRow>
- </TableLayout>
-
- <LinearLayout android:id="@+id/folder_selector"
- android:layout_width="match_parent"
- android:layout_height="@dimen/folder_selector_height"
- android:orientation="vertical"
- android:visibility="gone"
- >
-
- <view class="com.android.browser.AddBookmarkPage$CustomListView"
- android:id="@+id/list"
- android:layout_marginLeft="16dip"
- android:layout_marginRight="16dip"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
- <TextView
- android:id="@+id/empty"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:layout_marginLeft="16dip"
- android:layout_marginTop="16dip"
- android:text="@string/no_subfolders"
- android:textStyle="italic"
- android:textAppearance="?android:attr/textAppearanceMedium" />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="54dip"
- android:orientation="horizontal"
- android:paddingTop="4dip"
- android:paddingLeft="2dip"
- android:paddingRight="2dip" >
- <Button android:id="@+id/OK"
- android:text="@string/save"
- android:layout_width="0dip"
- android:layout_gravity="left"
- android:layout_weight="1"
- android:maxLines="2"
- android:layout_height="wrap_content" />
- <Button android:id="@+id/cancel"
- android:text="@string/do_not_save"
- android:layout_width="0dip"
- android:layout_gravity="right"
- android:layout_weight="1"
- android:maxLines="2"
- android:layout_height="wrap_content" />
- </LinearLayout>
+ <include layout="@layout/browser_add_bookmark_content" />
</LinearLayout>
diff --git a/res/layout/browser_add_bookmark_content.xml b/res/layout/browser_add_bookmark_content.xml
new file mode 100644
index 0000000..6ee7a5a
--- /dev/null
+++ b/res/layout/browser_add_bookmark_content.xml
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <RelativeLayout android:id="@+id/crumb_holder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:visibility="gone"
+ android:paddingLeft="5dip"
+ android:paddingRight="5dip"
+ >
+ <com.android.browser.BreadCrumbView android:id="@+id/crumbs"
+ android:layout_width="wrap_content"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_alignParentLeft="true"
+ android:layout_toLeftOf="@+id/add_divider"
+ android:layout_centerVertical="true"
+ />
+ <TextView
+ android:id="@+id/add_new_folder"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignBaseline="@+id/crumbs"
+ android:drawableLeft="@drawable/ic_add_string"
+ android:gravity="center_vertical"
+ android:text="@string/new_folder"
+ android:visibility="gone"
+ android:layout_centerVertical="true"
+ android:layout_alignTop="@+id/crumbs"
+ android:layout_alignBottom="@+id/crumbs"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ <ImageView android:id="@+id/add_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toLeftOf="@+id/add_new_folder"
+ android:src="@drawable/crumb_divider"
+ android:layout_centerVertical="true"
+ />
+ </RelativeLayout>
+ <LinearLayout android:id="@+id/title_holder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingLeft="5dip"
+ android:paddingRight="5dip"
+ >
+ <TextView android:id="@+id/fake_title"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:gravity="center_vertical"
+ android:drawableLeft="@drawable/ic_bookmark_on_holo_dark"
+ android:text="@string/bookmark_this_page"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ <ImageView android:id="@+id/remove_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/crumb_divider"
+ android:layout_centerVertical="true"
+ android:visibility="gone"
+ />
+ <TextView android:id="@+id/remove"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:gravity="center_vertical"
+ android:text="@string/remove"
+ android:drawableLeft="@drawable/trashcan"
+ android:visibility="gone"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </LinearLayout>
+ <View android:id="@+id/titleDivider"
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:gravity="fill_horizontal"
+ android:background="?android:attr/colorForeground"
+ />
+
+ <TableLayout android:id="@+id/default_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:stretchColumns="1"
+ android:shrinkColumns="1"
+ android:paddingTop="10dip"
+ android:paddingLeft="20dip"
+ android:paddingRight="20dip" >
+ <TableRow>
+ <TextView
+ android:id="@+id/titleText"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginBottom="30dip"
+ android:text="@string/name"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <EditText
+ android:id="@+id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="20dip"
+ android:layout_marginLeft="20dip"
+ android:gravity="fill_horizontal"
+ android:inputType="textCapSentences"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </TableRow>
+
+ <TableRow
+ android:id="@+id/row_address">
+ <TextView
+ android:id="@+id/addressText"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/location"
+ android:gravity="left"
+ android:layout_marginBottom="20dip"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <EditText
+ android:id="@+id/address"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="20dip"
+ android:layout_marginLeft="20dip"
+ android:hint="@string/http"
+ android:gravity="fill_horizontal"
+ android:inputType="textUri"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </TableRow>
+ <TableRow>
+ <TextView
+ android:id="@+id/add_to"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/containing_folder"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <view class="com.android.browser.addbookmark.FolderSpinner"
+ android:id="@+id/folder"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="20dip"
+ android:layout_marginLeft="20dip"
+ android:spinnerMode="dropdown"
+ android:gravity="center_vertical"
+ />
+ </TableRow>
+ </TableLayout>
+
+ <LinearLayout android:id="@+id/folder_selector"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/folder_selector_height"
+ android:orientation="vertical"
+ android:visibility="gone"
+ >
+
+ <view class="com.android.browser.AddBookmarkPage$CustomListView"
+ android:id="@+id/list"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+ <TextView
+ android:id="@+id/empty"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_marginLeft="16dip"
+ android:layout_marginTop="16dip"
+ android:text="@string/no_subfolders"
+ android:textStyle="italic"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="54dip"
+ android:orientation="horizontal"
+ android:paddingTop="4dip"
+ android:paddingLeft="2dip"
+ android:paddingRight="2dip" >
+ <Button android:id="@+id/OK"
+ android:text="@string/save"
+ android:layout_width="0dip"
+ android:layout_gravity="left"
+ android:layout_weight="1"
+ android:maxLines="2"
+ android:layout_height="wrap_content" />
+ <Button android:id="@+id/cancel"
+ android:text="@string/do_not_save"
+ android:layout_width="0dip"
+ android:layout_gravity="right"
+ android:layout_weight="1"
+ android:maxLines="2"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+</merge>
diff --git a/res/layout/http_authentication.xml b/res/layout/http_authentication.xml
index cee3a42..856c45c 100644
--- a/res/layout/http_authentication.xml
+++ b/res/layout/http_authentication.xml
@@ -14,49 +14,48 @@
limitations under the License.
-->
-<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center_horizontal"
android:orientation="vertical"
>
<TextView
- android:id="@+id/username_view"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/username"
- android:gravity="left"
android:layout_marginTop="12dip"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip" />
<EditText
android:id="@+id/username_edit"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
android:scrollHorizontally="true"
android:inputType="text"
- android:gravity="fill_horizontal"
- android:layout_weight="1"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
- android:layout_marginBottom="12dip" />
+ android:layout_marginBottom="12dip"
+ android:singleLine="true"
+ android:imeOptions="actionNext" />
<TextView
- android:id="@+id/password_view"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/password"
- android:gravity="left"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip" />
<EditText
android:id="@+id/password_edit"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
android:scrollHorizontally="true"
android:inputType="textPassword"
- android:gravity="fill_horizontal"
- android:layout_weight="1"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
- android:layout_marginBottom="12dip" />
-</TableLayout>
+ android:layout_marginBottom="12dip"
+ android:singleLine="true"
+ android:imeOptions="actionDone" />
+</LinearLayout>
diff --git a/res/layout/suggestion_item.xml b/res/layout/suggestion_item.xml
index b85911f..c08ba84 100644
--- a/res/layout/suggestion_item.xml
+++ b/res/layout/suggestion_item.xml
@@ -57,8 +57,7 @@
style="@style/SuggestionLineSmall"
android:singleLine="true"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textColor="@color/urlTextColor" />
+ android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<ImageView
diff --git a/res/layout/title_bar.xml b/res/layout/title_bar.xml
index 9bfba35..99ac04d 100644
--- a/res/layout/title_bar.xml
+++ b/res/layout/title_bar.xml
@@ -18,77 +18,81 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingLeft="8dip"
- android:paddingRight="12dip"
- android:paddingTop="2dip"
- android:paddingBottom="1dip"
- android:background="@drawable/search_plate_browser" >
-
- <ProgressBar android:id="@+id/progress_horizontal"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="match_parent"
- android:layout_height="5dip"
- android:layout_marginLeft="1dip"
- android:max="100"
- />
+ android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:background="@drawable/bg_urlbar"
+ android:paddingLeft="4dip"
+ android:paddingRight="4dip"
+ android:paddingTop="2dip"
+ android:paddingBottom="1dip"
>
<LinearLayout android:id="@+id/title_bg"
- android:background="@drawable/title_text"
android:layout_width="0dip"
android:layout_weight="1.0"
- android:layout_height="wrap_content"
+ android:layout_height="48dip"
android:layout_marginBottom="4dip"
android:gravity="center_vertical"
android:orientation="horizontal"
+ android:background="@drawable/url_background"
>
<ImageView android:id="@+id/favicon"
android:layout_width="20dip"
android:layout_height="20dip"
- android:layout_marginLeft="3dip"
/>
<ImageView android:id="@+id/lock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="6dip"
+ android:layout_marginLeft="3dip"
android:visibility="gone"
/>
- <TextView
- android:id="@+id/title"
- android:layout_height="wrap_content"
+ <com.android.browser.UrlInputView
+ android:id="@+id/url_input"
+ android:focusable="true"
android:layout_width="0dip"
android:layout_weight="1.0"
+ android:layout_height="match_parent"
android:layout_marginLeft="3dip"
- android:gravity="center_vertical"
+ android:paddingLeft="0dip"
+ android:paddingRight="0dip"
+ android:background="@null"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:hint="@string/search_hint"
android:singleLine="true"
android:ellipsize="end"
- />
+ android:lines="1"
+ android:scrollHorizontally="true"
+ android:inputType="textUri"
+ android:imeOptions="actionGo"
+ style="@style/Suggestions" />
</LinearLayout>
- <ImageView android:id="@+id/stop"
- android:background="@drawable/stop_background"
+ <ImageButton
+ android:id="@+id/stop"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginBottom="4dip"
- android:src="@drawable/ic_btn_stop_v2"
+ style="@style/HoloButton"
+ android:src="@drawable/ic_stop_holo_dark"
android:visibility="gone"
/>
- <ImageView
- android:id="@+id/rt_btn"
+ <ImageButton
+ android:id="@+id/bookmark"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginLeft="-2dip"
- android:layout_marginTop="-6.5dip"
- android:layout_marginBottom="-2dip"
- android:layout_marginRight="-5dip"
- android:scaleType="center"
- android:background="@drawable/btn_bookmark"
- android:src="@drawable/ic_bookmark_on_holo_dark"
+ style="@style/HoloButton"
+ android:src="@drawable/btn_imageview_star"
/>
</LinearLayout>
+
+ <com.android.browser.PageProgressView
+ android:id="@+id/progress_horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="22dip"
+ android:background="@null"
+ android:src="@drawable/progress"
+ android:layout_marginTop="-11dip"
+ android:visibility="gone" />
</LinearLayout>
diff --git a/res/layout/url_bar.xml b/res/layout/url_bar.xml
index 526e44c..f2b32c4 100644
--- a/res/layout/url_bar.xml
+++ b/res/layout/url_bar.xml
@@ -12,7 +12,6 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
@@ -52,19 +51,12 @@
android:orientation="horizontal"
android:background="@drawable/url_background">
<ImageView
- android:id="@+id/web_icon"
+ android:id="@+id/url_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/ic_web_holo_dark"
style="@style/HoloIcon" />
<ImageView
- android:id="@+id/voice_icon"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:src="@drawable/ic_search_holo_dark"
- style="@style/HoloIcon"
- android:visibility="gone" />
- <ImageView
android:id="@+id/lock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
@@ -72,7 +64,6 @@
android:visibility="gone" />
<com.android.browser.UrlInputView
android:id="@+id/url_focused"
- android:focusable="true"
android:layout_width="0dip"
android:layout_weight="1.0"
android:layout_height="match_parent"
diff --git a/res/menu-xlarge/browser.xml b/res/menu-xlarge/browser.xml
index 1b52c9a..be4a521 100644
--- a/res/menu-xlarge/browser.xml
+++ b/res/menu-xlarge/browser.xml
@@ -32,6 +32,8 @@
android:title="@string/share_page"
android:icon="@drawable/ic_share_holo_dark"
android:alphabeticShortcut="s" />
+ <item android:id="@+id/save_webarchive_menu_id"
+ android:title="@string/menu_save_webarchive" />
<item android:id="@+id/page_info_menu_id"
android:title="@string/page_info"
android:icon="@drawable/ic_pageinfo_holo_dark"
diff --git a/res/menu/browser.xml b/res/menu/browser.xml
index beaa8f3..abe3716 100644
--- a/res/menu/browser.xml
+++ b/res/menu/browser.xml
@@ -45,6 +45,8 @@
android:title="@string/share_page"
android:icon="@drawable/ic_share_holo_dark"
android:alphabeticShortcut="s" />
+ <item android:id="@+id/save_webarchive_menu_id"
+ android:title="@string/menu_save_webarchive" />
<item android:id="@+id/page_info_menu_id"
android:title="@string/page_info"
android:icon="@drawable/ic_pageinfo_holo_dark"
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index fd97a7f..23ebc1d 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"المتصفح"</string>
<string name="choose_upload" msgid="3649366287575002063">"اختر ملفًا لتحميله"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"نافذة جديدة"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"نافذة جديدة للتصفح المتخفي"</string>
<string name="active_tabs" msgid="3050623868203544623">"نظام التشغيل Windows"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"بطاقة SD غير متوفرة"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"وحدة تخزين USB مشغولة. للسماح بالتنزيلات، حدد \"إيقاف تشغيل وحدة تخزين USB\" في التنبيه."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"بطاقة SD مشغولة. للسماح بالتنزيلات، حدد \"إيقاف تشغيل تخزين USB\" في التنبيه."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"يمكن تنزيل عناوين URL لـ \"http\" أو \"https\" فقط."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"لا يمكن فتح الملف"</string>
<string name="retry" msgid="1835923075542266721">"إعادة المحاولة"</string>
<string name="no_downloads" msgid="3947445710685021498">"سجل التنزيل فارغ."</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 993d1d1..e42f9c8 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Браузър"</string>
<string name="choose_upload" msgid="3649366287575002063">"Избор на файл за качване"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Нов прозорец"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Нов прозорец „инкогнито“"</string>
<string name="active_tabs" msgid="3050623868203544623">"Прозорци"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Няма SD карта"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB хранилището е заето. За да разрешите изтегляния, изберете „Изключване на USB хранилището“ в известието."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD картата е заета. За да разрешите изтегляния, изберете „Изключване на USB устройството за съхранение“ в известието."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Могат да се изтеглят само URL адреси от тип „http“ или „https“."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Файлът не може да се отвори"</string>
<string name="retry" msgid="1835923075542266721">"Повторен опит"</string>
<string name="no_downloads" msgid="3947445710685021498">"Историята на изтеглянията е празна."</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 3e33f4c..7f2a73d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Navegador"</string>
<string name="choose_upload" msgid="3649366287575002063">"Trieu un fitxer per penjar-lo"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Finestra nova"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nova finestra d\'incògnit"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"La targeta SD no està disponible"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"L\'emmagatzematge USB està ocupat. Per permetre les baixades, selecciona \"Desactiva l\'emmagatzematge USB\" a la notificació."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La targeta SD està ocupada. Per permetre les baixades, seleccioneu \"Desactiva l\'emmagatzematge d\'USB\" a la notificació."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Només pot baixar URL \"http\" o \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"No pot obrir el fitxer"</string>
<string name="retry" msgid="1835923075542266721">"Torna-ho a provar"</string>
<string name="no_downloads" msgid="3947445710685021498">"L\'historial de baixades és buit."</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index c83ae1f..4901ce7 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Prohlížeč"</string>
<string name="choose_upload" msgid="3649366287575002063">"Zvolit soubor, který chcete nahrát."</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nové okno"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nové anonymní okno"</string>
<string name="active_tabs" msgid="3050623868203544623">"Okna"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Karta SD není dostupná"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Úložiště USB je zaneprázdněno. Chcete-li povolit stahování, vyberte v oznámení možnost Vypnout úložiště USB."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Karta SD je zaneprázdněna. Chcete-li povolit stahování, vyberte v oznámení možnost Vypnout úložiště USB."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Lze stahovat pouze adresy URL začínající jako http nebo https."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Soubor nelze otevřít"</string>
<string name="retry" msgid="1835923075542266721">"Zkusit znovu"</string>
<string name="no_downloads" msgid="3947445710685021498">"Historie stahování je prázdná."</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 74c1333..9681bc0 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Browser"</string>
<string name="choose_upload" msgid="3649366287575002063">"Vælg fil til upload"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nyt vindue"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nyt inkognitovindue"</string>
<string name="active_tabs" msgid="3050623868203544623">"Vinduer"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-kortet er ikke tilgængeligt"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-lager er optaget. Vælg \"Slå USB-lager fra\" i meddelelsen for at tillade downloads."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-kortet er optaget. Vælg \"Slå USB-lagring fra\" i meddelelsen for at tillade downloads."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Kan kun downloade webadresser, der starter med \"http\" eller https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Kan ikke åbne filen"</string>
<string name="retry" msgid="1835923075542266721">"Prøv igen"</string>
<string name="no_downloads" msgid="3947445710685021498">"Downloadoversigten er tom."</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 2c7a909..7e82309 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Browser"</string>
<string name="choose_upload" msgid="3649366287575002063">"Datei zum Hochladen auswählen"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Neues Fenster"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Neues Inkognito-Fenster"</string>
<string name="active_tabs" msgid="3050623868203544623">"Fenster"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-Karte nicht verfügbar"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Der USB-Speicher ist ausgelastet. Wählen Sie in der Benachrichtigung \"USB-Speicher deaktivieren\", um Downloads zuzulassen."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Die SD-Karte wird benutzt. Wählen Sie in der Benachrichtigung \"USB-Speicher deaktivieren\" aus, um Downloads zuzulassen."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Nur Download von URLs mit \"http\" oder \"https\" möglich"</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Datei kann nicht geöffnet werden."</string>
<string name="retry" msgid="1835923075542266721">"Wiederholen"</string>
<string name="no_downloads" msgid="3947445710685021498">"Downloadverlauf ist leer."</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index b43d812..7bf3974 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Internet"</string>
<string name="choose_upload" msgid="3649366287575002063">"Επιλογή αρχείου για μεταφόρτωση"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Νέο παράθυρο"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Νέο παράθυρο για ανώνυμη περιήγηση"</string>
<string name="active_tabs" msgid="3050623868203544623">"Παράθυρα"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Η κάρτα SD δεν είναι διαθέσιμη"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Ο χώρος αποθήκευσης USB είναι απασχολημένος. Για να επιτρέψετε τις λήψεις, επιλέξτε \"Απενεργοποίηση χώρου αποθήκευσης USB\" στην ειδοποίηση."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Η κάρτα SD είναι απασχολημένη. Για να επιτρέψετε τις λήψεις, επιλέξτε \"Απενεργοποίηση χώρου αποθήκευσης USB\" στην ειδοποίηση."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Είναι δυνατή η λήψη μόνο διευθύνσεων URL \"http\" ή \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Δεν είναι δυνατό το άνοιγμα του αρχείου"</string>
<string name="retry" msgid="1835923075542266721">"Επανάληψη"</string>
<string name="no_downloads" msgid="3947445710685021498">"Το ιστορικό λήψεων του προγράμματος περιήγησης είναι κενό."</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index e2bb5fe..23eba01 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Browser"</string>
<string name="choose_upload" msgid="3649366287575002063">"Choose file for upload"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"New window"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"New incognito window"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD card unavailable"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"The USB storage is busy. To allow downloads, select \"Turn off USB storage\" in the notification."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"The SD card is busy. To allow downloads, select \"Turn off USB storage\" in the notification."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Can only download \"http\" or \"https\" URLs."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Cannot open file"</string>
<string name="retry" msgid="1835923075542266721">"Retry"</string>
<string name="no_downloads" msgid="3947445710685021498">"Download history is empty."</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 829e5c4..8643cb6 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Navegador"</string>
<string name="choose_upload" msgid="3649366287575002063">"Elegir el archivo para cargar"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Ventana nueva"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nueva ventana de incógnito"</string>
<string name="active_tabs" msgid="3050623868203544623">"Ventanas"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Tarjeta SD no disponible"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"El almacenamiento USB está ocupado. Para permitir descargas, selecciona \"Desactivar almacenamiento USB\" en la notificación."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La tarjeta SD está llena. Para permitir descargas, selecciona \"Desactivar almacenamiento USB\" en la notificación."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Solo se pueden descargar URL de \"http\" o de \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"No se puede abrir el archivo."</string>
<string name="retry" msgid="1835923075542266721">"Intentar nuevamente"</string>
<string name="no_downloads" msgid="3947445710685021498">"El historial de descarga está vacío."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 6da6458..629f320 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Navegador"</string>
<string name="choose_upload" msgid="3649366287575002063">"Seleccionar archivo para subir"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nueva ventana"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nueva ventana de incógnito"</string>
<string name="active_tabs" msgid="3050623868203544623">"Ventanas"</string>
@@ -217,9 +219,9 @@
<string name="pref_privacy_location_title" msgid="7458378016606081067">"Ubicación"</string>
<string name="pref_privacy_enable_geolocation" msgid="1395040170290765686">"Habilitar ubicación"</string>
<string name="pref_privacy_enable_geolocation_summary" msgid="8437020934664306205">"Permitir que los sitios soliciten acceso a tu ubicación"</string>
- <string name="pref_privacy_clear_geolocation_access" msgid="6649680770030042980">"Deshabilitar ubicación"</string>
- <string name="pref_privacy_clear_geolocation_access_summary" msgid="7750143359497314679">"Deshabilitar acceso a la ubicación para todos los sitios web"</string>
- <string name="pref_privacy_clear_geolocation_access_dlg" msgid="7327063124488827244">"Deshabilitar acceso a la ubicación para todos los sitios web"</string>
+ <string name="pref_privacy_clear_geolocation_access" msgid="6649680770030042980">"Permitir acceso a la ubicación"</string>
+ <string name="pref_privacy_clear_geolocation_access_summary" msgid="7750143359497314679">"Permitir que todos los sitios web accedan a la ubicación"</string>
+ <string name="pref_privacy_clear_geolocation_access_dlg" msgid="7327063124488827244">"Permitir que todos los sitios web accedan a la ubicación"</string>
<string name="pref_security_passwords_title" msgid="5734190542383756711">"Contraseñas"</string>
<string name="pref_security_remember_passwords" msgid="6492957683454529549">"Recordar contraseñas"</string>
<string name="pref_security_remember_passwords_summary" msgid="256388703356349137">"Guardar nombres de usuario y contraseñas de sitios web"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Tarjeta SD no disponible"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"El almacenamiento USB está ocupado. Para permitir descargas, selecciona \"Desactivar almacenamiento USB\" en la notificación."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La tarjeta SD está ocupada. Para permitir descargas, selecciona \"Desactivar almacenamiento USB\" en la notificación."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Solo se pueden descargar URL del tipo \"http\" o \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"No se puede abrir el archivo"</string>
<string name="retry" msgid="1835923075542266721">"Reintentar"</string>
<string name="no_downloads" msgid="3947445710685021498">"El historial de descargas está vacío."</string>
@@ -346,10 +349,10 @@
<string name="geolocation_permissions_prompt_remember" msgid="3118526300707348308">"Recordar preferencia"</string>
<string name="geolocation_permissions_prompt_toast_allowed" msgid="987286072035125498">"Este sitio puede acceder a tu ubicación. Para modificar esta opción, accede a \"Ajustes -> Ajustes del sitio web\"."</string>
<string name="geolocation_permissions_prompt_toast_disallowed" msgid="7695100950212692515">"Este sitio no puede acceder a tu ubicación. Para modificar esta opción, accede a \"Ajustes -> Ajustes del sitio web\"."</string>
- <string name="geolocation_settings_page_title" msgid="1745477985097536528">"Deshabilitar ubicación"</string>
+ <string name="geolocation_settings_page_title" msgid="1745477985097536528">"Permitir acceso a la ubicación"</string>
<string name="geolocation_settings_page_summary_allowed" msgid="9180251524290811398">"Este sitio puede acceder actualmente a tu ubicación."</string>
<string name="geolocation_settings_page_summary_not_allowed" msgid="4589649082203102544">"Este sitio no puede acceder actualmente a tu ubicación."</string>
- <string name="geolocation_settings_page_dialog_title" msgid="1549842043381347668">"Deshabilitar ubicación"</string>
+ <string name="geolocation_settings_page_dialog_title" msgid="1549842043381347668">"Permitir acceso ubicación"</string>
<string name="geolocation_settings_page_dialog_message" msgid="7586671987576403993">"Se permitirá que este sitio web acceda a tu ubicación."</string>
<string name="geolocation_settings_page_dialog_ok_button" msgid="4789434178048077287">"Permitir acceso"</string>
<string name="geolocation_settings_page_dialog_cancel_button" msgid="7941036504673409747">"Cancelar"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 71f9aae..b38f037 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"مرورگر"</string>
<string name="choose_upload" msgid="3649366287575002063">"انتخاب فایل برای آپلود"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"پنجره جدید"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"پنجره ناشناخته جدید"</string>
<string name="active_tabs" msgid="3050623868203544623">"پنجره ها"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"کارت SD موجود نیست"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"حافظه USB مشغول است. برای اجازه به دانلودها، \"خاموش کردن حافظه USB\" را در اعلان انتخاب کنید."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"کارت SD مشغول است. برای اجازه به دانلودها، \"خاموش کردن ذخیره USB\" را در اعلان انتخاب کنید."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"فقط می تواند URL های \"http\" یا \"https\" را دانلود کند."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"فایل باز نمی شود"</string>
<string name="retry" msgid="1835923075542266721">"امتحان مجدد"</string>
<string name="no_downloads" msgid="3947445710685021498">"سابقه دانلود خالی است."</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 6bfe70e..2ed9782 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Selain"</string>
<string name="choose_upload" msgid="3649366287575002063">"Valitse lähetettävä tiedosto"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Uusi ikkuna"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Uusi incognito-ikkuna"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-kortti ei käytettävissä"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-tallennustila on varattu. Voit sallia lataukset valitsemalla ilmoituksessa Poista USB-tallennustila käytöstä."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-kortti on varattu. Voit sallia lataukset valitsemalla ilmoituksessa \"Poista USB-tallennustila käytöstä\"."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Vain http- tai https-URL-osoitteista voi ladata"</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Tiedostoa ei voi avata"</string>
<string name="retry" msgid="1835923075542266721">"Yritä uudelleen"</string>
<string name="no_downloads" msgid="3947445710685021498">"Lataushistoria on tyhjä."</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index e511420..f190a26 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Navigateur"</string>
<string name="choose_upload" msgid="3649366287575002063">"Choisir le fichier à importer"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nouvelle fenêtre"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Fenêtre de navigation privée"</string>
<string name="active_tabs" msgid="3050623868203544623">"Fenêtres"</string>
@@ -159,7 +161,7 @@
<string name="pref_general_title" msgid="1946872771219249323">"Général"</string>
<string name="pref_general_sync_title" msgid="3138637035975860324">"Synchronisation"</string>
<string name="pref_general_autofill_title" msgid="64638897890112873">"Saisie automatique"</string>
- <string name="pref_personal_sync_with_chrome" msgid="1695182180332194033">"Synchroniser avec Google Chrome"</string>
+ <string name="pref_personal_sync_with_chrome" msgid="1695182180332194033">"Synchroniser avec Google Voice"</string>
<string name="pref_personal_sync_with_chrome_summary" msgid="7414133931827321055">"Partager les favoris et d\'autres données entre le navigateur Android et Google Chrome"</string>
<string name="pref_personal_google_account" msgid="952360133341490071">"Compte Google"</string>
<string name="pref_personal_sync_bookmarks" msgid="59237515966184432">"Synchroniser les favoris"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Carte SD non disponible"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"La mémoire de stockage USB est occupée. Pour autoriser les téléchargements, sélectionnez l\'option \"Désactiver le stockage USB\" dans la notification."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La carte SD est occupée. Pour autoriser les téléchargements, sélectionnez l\'option \"Désactiver le stockage USB\" dans la notification."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Vous ne pouvez télécharger que des URL de type \"http\" et \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Fichier impossible à ouvrir"</string>
<string name="retry" msgid="1835923075542266721">"Réessayer"</string>
<string name="no_downloads" msgid="3947445710685021498">"L\'historique de téléchargement est vide."</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 21d76bb..e523e85 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Preglednik"</string>
<string name="choose_upload" msgid="3649366287575002063">"Odaberite datoteku za prijenos"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Novi prozor"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Novi prozor anonimno"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD kartica nije dostupna"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Memorija USB je zauzeta. Kako biste omogućili preuzimanja, odaberite \"Isključi USB pohranjivanje\" u obavijesti."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD kartica je zauzeta. Kako biste omogućili preuzimanja, odaberite \"Isključi USB pohranjivanje\" u obavijesti."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Mogu se preuzeti samo \"http\" ili \"https\" URL-ovi."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Datoteka se ne može otvoriti"</string>
<string name="retry" msgid="1835923075542266721">"Pokušaj ponovo"</string>
<string name="no_downloads" msgid="3947445710685021498">"Povijest preuzimanja je prazna."</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 496bd20..6265158 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Böngésző"</string>
<string name="choose_upload" msgid="3649366287575002063">"Válassza ki a feltölteni kívánt fájlt"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Új ablak"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Új inkognitóablak"</string>
<string name="active_tabs" msgid="3050623868203544623">"Ablakok"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Az SD-kártya nem érhető el"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Az USB-tár dolgozik. A letöltések engedélyezéséhez válassza az \"USB-tár kikapcsolása\" lehetőséget."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Az SD-kártya dolgozik. A letöltések engedélyezéséhez válassza az \"USB-tár kikapcsolása\" lehetőséget."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Csak \"http\" vagy \"https\" URL-ek letöltése lehetséges."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"A fájlt nem lehet megnyitni"</string>
<string name="retry" msgid="1835923075542266721">"Újra"</string>
<string name="no_downloads" msgid="3947445710685021498">"A letöltési előzmények listája üres."</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 206edba..66d5dd4 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Peramban"</string>
<string name="choose_upload" msgid="3649366287575002063">"Pilih berkas untuk diunggah"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Jendela baru"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Jendela penyamaran baru"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Kartu SD tidak tersedia"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Penyimpanan USB sibuk. Untuk mengizinkan pengunduhan, pilih \"Matikan penyimpanan USB\" pada pemberitahuan."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Kartu SD sibuk. Untuk mengizinkan unduhan, pilih \"Matikan penyimpanan USB\" pada pemberitahuan."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Hanya dapat mengunduh URL \"http\" atau \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Tidak dapat membuka berkas"</string>
<string name="retry" msgid="1835923075542266721">"Coba Lagi"</string>
<string name="no_downloads" msgid="3947445710685021498">"Riwayat unduhan kosong."</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 32b84af..8d85223 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Browser"</string>
<string name="choose_upload" msgid="3649366287575002063">"Scegli il file per il caricamento"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nuova finestra"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nuova finestra in incognito"</string>
<string name="active_tabs" msgid="3050623868203544623">"Finestre"</string>
@@ -83,10 +85,10 @@
<string name="remove_bookmark" msgid="8407495852801410891">"Elimina segnalibro"</string>
<string name="remove_from_bookmarks" msgid="4374080666576982775">"Rimuovi dai segnalibri"</string>
<string name="remove_history_item" msgid="5021424935726728618">"Rimuovi da cronologia"</string>
- <string name="set_as_homepage" msgid="4752937379414905560">"Imposta come pag. inziale"</string>
+ <string name="set_as_homepage" msgid="4752937379414905560">"Imposta come home page"</string>
<string name="bookmark_saved" msgid="2766434679871317557">"Salvato nei segnalibri."</string>
<string name="bookmark_not_saved" msgid="700600955089376724">"Impossibile salvare il preferito."</string>
- <string name="homepage_set" msgid="8768087280310966395">"Pagina iniziale impostata."</string>
+ <string name="homepage_set" msgid="8768087280310966395">"Home page impostata."</string>
<string name="bookmark_needs_title" msgid="6245900436119218187">"Inserisci un nome per il segnalibro."</string>
<string name="bookmark_needs_url" msgid="7809876865972755158">"Inserisci un URL per il segnalibro."</string>
<string name="bookmark_url_not_valid" msgid="6719785633980202419">"URL non valido."</string>
@@ -147,7 +149,7 @@
<item msgid="8547442717307793863">"Non attivo"</item>
</string-array>
<string name="pref_content_open_in_background_summary" msgid="1737664075721181678">"Apri le nuove finestre dietro la finestra corrente"</string>
- <string name="pref_content_homepage" msgid="6082437160778559806">"Imposta pagina iniziale"</string>
+ <string name="pref_content_homepage" msgid="6082437160778559806">"Imposta home page"</string>
<string name="pref_content_search_engine" msgid="1620101310821644144">"Imposta motore di ricerca"</string>
<string name="pref_content_search_engine_summary" msgid="5162667665858487316">"Seleziona un motore di ricerca"</string>
<string name="pref_set_homepage_to" msgid="7196350233061395098">"Imposta su..."</string>
@@ -271,8 +273,8 @@
<string name="pref_lab_title" msgid="5571091610359629423">"Labs"</string>
<string name="pref_lab_quick_controls" msgid="2105979166017257647">"Controlli rapidi"</string>
<string name="pref_lab_quick_controls_summary" msgid="8025196176636589803">"Fai scorrere il pollice dal bordo sinistro/destro per accedere ai controlli rapidi"</string>
- <string name="pref_lab_most_visited_homepage" msgid="547134501893835512">"Più visitati in pagina iniziale"</string>
- <string name="pref_lab_most_visited_homepage_summary" msgid="6857702350834122532">"Imposta pagina iniziale che mostri le pagine più visitate."</string>
+ <string name="pref_lab_most_visited_homepage" msgid="547134501893835512">"Home page più visitata"</string>
+ <string name="pref_lab_most_visited_homepage_summary" msgid="6857702350834122532">"Imposta la tua home page per mostrare le pagine più visitate."</string>
<string name="browserFrameNetworkErrorLabel" msgid="126892350904924893">"Problema di connettività dati"</string>
<string name="browserFrameFileErrorLabel" msgid="8063691502792670367">"Problemi con il file"</string>
<string name="browserFrameFormResubmitLabel" msgid="2685923472682180360">"Conferma"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Scheda SD non disponibile"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"L\'archivio USB è già in uso. Per consentire i download, seleziona \"Disattiva archivio USB\" nella notifica."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La scheda SD è piena. Per consentire i download, seleziona \"Disattiva archivio USB\" nella notifica."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"È possibile scaricare soltanto URL \"http\" o \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Impossibile aprire il file"</string>
<string name="retry" msgid="1835923075542266721">"Riprova"</string>
<string name="no_downloads" msgid="3947445710685021498">"La cronologia download è vuota."</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 47a3e20..136480b 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"דפדפן"</string>
<string name="choose_upload" msgid="3649366287575002063">"בחר קובץ להעלאה"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"חלון חדש"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"חלון חדש של גלישה בסתר"</string>
<string name="active_tabs" msgid="3050623868203544623">"חלונות"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"כרטיס SD לא זמין"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"אמצעי אחסון מסוג USB אינו פנוי. כדי לאפשר הורדות, בחר \"כבה אמצעי אחסון מסוג USB\" בהתראה."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"כרטיס ה-SD אינו פנוי. כדי לאפשר הורדות, בחר \"כבה אחסון USB\" בהתראה."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"ניתן להוריד רק כתובות אתרים המתחילות ב-\"http\" או \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"אין אפשרות לפתוח את הקובץ"</string>
<string name="retry" msgid="1835923075542266721">"נסה שוב"</string>
<string name="no_downloads" msgid="3947445710685021498">"היסטוריית ההורדות ריקה."</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index d45b0ef..11047e5 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"ブラウザ"</string>
<string name="choose_upload" msgid="3649366287575002063">"アップロードするファイルを選択"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"新しいウィンドウ"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"新しいシークレットウインドウ"</string>
<string name="active_tabs" msgid="3050623868203544623">"ウィンドウ"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SDカードは利用できません"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USBストレージは使用中です。[USBストレージをOFFにする]を選択してからダウンロードしてください。"</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SDカードは使用中です。[USBストレージをOFFにする]を選択してからダウンロードしてください。"</string>
+ <string name="cannot_download" msgid="8150552478556798780">"ダウンロードできるのは「http」または「https」のURLのみです。"</string>
<string name="download_no_application_title" msgid="1286056729168874295">"ファイルを開けません"</string>
<string name="retry" msgid="1835923075542266721">"やり直す"</string>
<string name="no_downloads" msgid="3947445710685021498">"ダウンロード履歴はありません。"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 5aad619..4c3d79e 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"인터넷"</string>
<string name="choose_upload" msgid="3649366287575002063">"업로드할 파일 선택"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"새 창"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"새 시크릿 창"</string>
<string name="active_tabs" msgid="3050623868203544623">"창"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD 카드를 사용할 수 없음"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB 저장소가 사용 중입니다. 다운로드를 허용하려면 알림에서 \'USB 저장소 사용 안함\'을 선택하세요."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD 카드가 사용 중입니다. 다운로드를 허용하려면 알림에서 \'USB 저장소 끄기\'를 선택하세요."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"\'http\' 또는 \'https\' URL만 다운로드할 수 있습니다."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"파일을 열 수 없음"</string>
<string name="retry" msgid="1835923075542266721">"다시 시도"</string>
<string name="no_downloads" msgid="3947445710685021498">"다운로드 기록이 비어 있습니다."</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 30bac67..c4166db 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Naršyklė"</string>
<string name="choose_upload" msgid="3649366287575002063">"Pasirinkti failą, kurį norite įkelti"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Naujas langas"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Naujas inkognito langas"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD kortelė negalima"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB atmintinė užimta. Jei norite leisti atsisiuntimus, pranešime pasirinkite „Išjungti USB atmintinę“."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD kortelė užimta. Jei norite leisti atsisiuntimus, pranešime pasirinkite „Išjungti USB saugyklą“."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Galima atsisiųsti tik „http“ ar „https“ prasidedančius URL."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Nepavyksta atidaryti failo"</string>
<string name="retry" msgid="1835923075542266721">"Bandyti dar kartą"</string>
<string name="no_downloads" msgid="3947445710685021498">"Atsisiuntimo istorija tuščia."</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 277d82b..de07288 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Pārlūkprogramma"</string>
<string name="choose_upload" msgid="3649366287575002063">"Izvēlieties augšupielādējamo failu"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Jauns logs"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Jauns inkognito logs"</string>
<string name="active_tabs" msgid="3050623868203544623">"Logi"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD karte nav pieejama"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB atmiņa ir aizņemta. Lai atļautu lejupielādes, paziņojumā atlasiet Izslēgt USB atmiņu."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD karte ir aizņemta. Lai atļautu lejupielādes, paziņojumā atlasiet “Izslēgt USB krātuvi”."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Var lejupielādēt tikai tādus vietrāžus URL, kuri sākas ar “http” vai “https”."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Nevar atvērt failu"</string>
<string name="retry" msgid="1835923075542266721">"Mēģināt vēlreiz"</string>
<string name="no_downloads" msgid="3947445710685021498">"Lejupielāžu vēsture ir tukša."</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 0584755..5d9dce8 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Nettleser"</string>
<string name="choose_upload" msgid="3649366287575002063">"Velg fil for opplasting"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Ny fane"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nytt inkognitovindu"</string>
<string name="active_tabs" msgid="3050623868203544623">"Vinduer"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Minnekort utilgjengelig"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-lagring er opptatt. Velg alternativet for å slå av USB-lagring i varselet for å tillate nedlastinger."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-kortet er opptatt. Velg alternativet for å slå av USB-lagring i varselet for å tillate nedlastinger."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Kan kun laste ned «http»- eller «https»-nettadresser."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Får ikke åpnet filen"</string>
<string name="retry" msgid="1835923075542266721">"Prøv igjen"</string>
<string name="no_downloads" msgid="3947445710685021498">"Nedlastingsloggen er tom."</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 6152107..a485415 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Browser"</string>
<string name="choose_upload" msgid="3649366287575002063">"Bestand selecteren voor uploaden"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nieuw venster"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nieuw incognitovenster"</string>
<string name="active_tabs" msgid="3050623868203544623">"Vensters"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-kaart niet beschikbaar"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"De USB-opslag wordt gebruikt. Als u downloads wilt toestaan, selecteert u \'USB-opslag uitschakelen\' in de melding."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"De SD-kaart wordt gebruikt. Als u downloads wilt toestaan, selecteert u \'USB-opslag uitschakelen\' in de melding."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Kan alleen URL\'s met \'http\' of \'https\' downloaden."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Kan bestand niet openen"</string>
<string name="retry" msgid="1835923075542266721">"Opnieuw proberen"</string>
<string name="no_downloads" msgid="3947445710685021498">"Downloadgeschiedenis is leeg."</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 3f8c0fd..100cfc3 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Internet"</string>
<string name="choose_upload" msgid="3649366287575002063">"Wybierz plik do przesłania"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nowe okno"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nowe okno incognito"</string>
<string name="active_tabs" msgid="3050623868203544623">"Okna"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Karta SD jest niedostępna"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Nośnik USB jest zajęty. Aby umożliwić pobieranie, wybierz w powiadomieniu opcję „Wyłącz nośnik USB”."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Karta SD jest zajęta. Aby umożliwić pobieranie, wybierz w powiadomieniu opcję „Wyłącz nośnik USB”."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Pobieranie jest możliwe tylko z adresów „http” lub „https”."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Nie można otworzyć pliku"</string>
<string name="retry" msgid="1835923075542266721">"Spróbuj ponownie"</string>
<string name="no_downloads" msgid="3947445710685021498">"Historia pobierania jest pusta."</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 5846b16..73edafe 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Navegad."</string>
<string name="choose_upload" msgid="3649366287575002063">"Escolher ficheiro a carregar"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nova janela"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nova janela de naveg. anónima"</string>
<string name="active_tabs" msgid="3050623868203544623">"Janelas"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Cartão SD não disponível"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"O armazenamento USB está ocupado. Para permitir as transferências, seleccione \"Desactivar armazenamento USB\" na notificação."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"O cartão SD está ocupado. Para permitir as transferências, seleccione \"Desactivar armazenamento USB\" na notificação."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Apenas é possível transferir URLs \"http\" ou \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Não é possível abrir o ficheiro"</string>
<string name="retry" msgid="1835923075542266721">"Tentar novamente"</string>
<string name="no_downloads" msgid="3947445710685021498">"O histórico de transferências está vazio."</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 86ddbff..3bf0afe 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Navegador"</string>
<string name="choose_upload" msgid="3649366287575002063">"Escolha o arquivo para envio"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nova janela"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nova janela anônima"</string>
<string name="active_tabs" msgid="3050623868203544623">"Janelas"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Cartão SD não disponível"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"O armazenamento USB está ocupado. Para permitir downloads, selecione \"Desativar o armazenamento USB\" na notificação."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"O cartão SD está ocupado. Para permitir downloads, selecione \"Desativar armazenamento USB\" na notificação."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Somente é possível fazer download de URLs \"http\" ou \"https\""</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Não é possível abrir o arquivo."</string>
<string name="retry" msgid="1835923075542266721">"Tentar novamente"</string>
<string name="no_downloads" msgid="3947445710685021498">"O histórico de downloads está vazio."</string>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
index 777e778..1982f20 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Navigatur"</string>
<string name="choose_upload" msgid="3649366287575002063">"Tscherner ina datoteca per importar"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nova fanestra"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nova fanestra incognito"</string>
<string name="active_tabs" msgid="3050623868203544623">"Fanestra"</string>
@@ -389,6 +391,8 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Carta SD betg disponibla"</string>
<!-- outdated translation 3473883538192835204 --> <string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"La carta SD è occupada. Tscherni «Deactivar la memoria USB» en l\'avis per permetter telechargiadas."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"La carta SD è occupada. Tscherni «Deactivar la memoria USB» en l\'avis per permetter telechargiadas."</string>
+ <!-- no translation found for cannot_download (8150552478556798780) -->
+ <skip />
<string name="download_no_application_title" msgid="1286056729168874295">"Impussibel dad avrir la datoteca"</string>
<string name="retry" msgid="1835923075542266721">"Repeter"</string>
<string name="no_downloads" msgid="3947445710685021498">"La cronologia da telechargiadas è vida."</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 1d22788..6d36fef 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Browser"</string>
<string name="choose_upload" msgid="3649366287575002063">"Alegeţi fişierul pentru a fi încărcat"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Fereastră nouă"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Fereastră incognito nouă"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Card SD nedisponibil"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Dispozitivul de stocare USB este ocupat. Pentru a permite descărcări, selectaţi în notificare opţiunea „Dezactivaţi stocarea USB”."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Cardul SD este ocupat. Pentru a permite descărcări, selectaţi în notificare opţiunea „Dezactivaţi stocarea pe USB”."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Puteţi descărca numai de la adrese URL care încep cu „http” sau „https”."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Fişierul nu poate fi deschis"</string>
<string name="retry" msgid="1835923075542266721">"Încercaţi din nou"</string>
<string name="no_downloads" msgid="3947445710685021498">"Istoricul de descărcări este gol."</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 82a416a..0cd3093 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Браузер"</string>
<string name="choose_upload" msgid="3649366287575002063">"Выберите файл для загрузки"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Новое окно"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Новое окно в режиме инкогнито"</string>
<string name="active_tabs" msgid="3050623868203544623">"Окна"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-карта недоступна"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-накопитель занят. Чтобы разрешить загрузки, выберите \"Выключить USB-накопитель\" в уведомлении."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-карта занята. Чтобы разрешить загрузки, выберите \"Отключить USB-накопитель\" в уведомлении."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Загрузка возможна только с URL, начинающихся с \"http\" или \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Невозможно открыть файл"</string>
<string name="retry" msgid="1835923075542266721">"Повторить попытку"</string>
<string name="no_downloads" msgid="3947445710685021498">"История загрузок пуста."</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index edd1253..ca012fc 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Prehliadač"</string>
<string name="choose_upload" msgid="3649366287575002063">"Zvoliť súbor, ktorý chcete odovzdať"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nové okno"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nové okno inkognito"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Karta SD nie je dostupná"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Ukladací priestor USB je zaneprázdnený. Ak chcete povoliť preberanie, vyberte v upozornení možnosť „Vypnúť ukladací priestor USB“."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Karta SD je zaneprázdnená. Ak chcete povoliť preberanie, vyberte v upozornení možnosť Vypnúť pamäť USB."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Môžete prevziať len adresy URL začínajúce na „http“ alebo „https“."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Súbor nemožno otvoriť"</string>
<string name="retry" msgid="1835923075542266721">"Skúsiť znova"</string>
<string name="no_downloads" msgid="3947445710685021498">"História preberania je prázdna."</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 212d883..cb4c7c2 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Brskalnik"</string>
<string name="choose_upload" msgid="3649366287575002063">"Izberite datoteko za prenos v strežnik"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Novo okno"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Novo okno brez belež. zgod."</string>
<string name="active_tabs" msgid="3050623868203544623">"Okna"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Kartica SD ni na voljo"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Pomnilnik USB je zaseden. Če želite omogočiti prenose, izberite »Izklopi pomnilnik USB« v območju za obvestila."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Kartica SD je zasedena. Če želite omogočiti prenose, izberite »Izklopi pomnilnik USB« v območju za obvestila."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Prenesti je možno le URL-je »http« ali »https«."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Ne morem odpreti datoteke"</string>
<string name="retry" msgid="1835923075542266721">"Poskusi znova"</string>
<string name="no_downloads" msgid="3947445710685021498">"Zgodovina prenosov je prazna."</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index cd76430..2bfa214 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Прегледач"</string>
<string name="choose_upload" msgid="3649366287575002063">"Одаберите датотеку за отпремање"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Нови прозор"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Нов прозор без архивирања"</string>
<string name="active_tabs" msgid="3050623868203544623">"Прозори"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD картица је недоступна"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB меморија је заузета. Да бисте дозволили преузимања, изаберите „Искључи USB меморију“ у обавештењу."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD картица је заузета. Да бисте омогућили преузимања, у оквиру обавештења изаберите „Искључи USB складиште“."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Могуће је преузети само URL адресе које садрже „http“ или „https“."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Није могуће отворити датотеку"</string>
<string name="retry" msgid="1835923075542266721">"Покушај поново"</string>
<string name="no_downloads" msgid="3947445710685021498">"Историја преузимања је празна."</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 6e23ccb..50c3bc9 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Webbläs."</string>
<string name="choose_upload" msgid="3649366287575002063">"Välj filen som du vill överföra"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Nytt fönster"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Nytt inkognitofönster"</string>
<string name="active_tabs" msgid="3050623868203544623">"Fönster"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD-kortet är inte tillgängligt"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB-lagringsenheten är upptagen. Om du vill tillåta hämtning väljer du Inaktivera USB-lagring i aviseringen."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD-kortet är upptaget. Om du vill tillåta hämtning väljer du Inaktivera USB-lagring i meddelandet."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Det går bara att hämta webbadresser som inleds med http eller https."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Det går inte att öppna filen"</string>
<string name="retry" msgid="1835923075542266721">"Försök igen"</string>
<string name="no_downloads" msgid="3947445710685021498">"Det finns ingen hämtningshistorik."</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index cd74d53..fc36d6b 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"เบราว์เซอร์"</string>
<string name="choose_upload" msgid="3649366287575002063">"เลือกไฟล์ที่จะอัปโหลด"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"หน้าต่างใหม่"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"หน้าต่างใหม่ที่ไม่ระบุตัวตน"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"การ์ด SD ใช้งานไม่ได้"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"ที่เก็บข้อมูล USB ไม่ว่าง หากต้องการอนุญาตให้ดาวน์โหลด ให้เลือก \"ปิดที่เก็บข้อมูล USB\" ในการแจ้งเตือน"</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"การ์ด SD ไม่ว่าง หากต้องการอนุญาตให้ดาวน์โหลด เลือก \"ปิดพื้นที่จัดเก็บ USB\" ในการแจ้งเตือน"</string>
+ <string name="cannot_download" msgid="8150552478556798780">"ดาวน์โหลดได้เฉพาะ URL \"http\" หรือ \"https\""</string>
<string name="download_no_application_title" msgid="1286056729168874295">"ไม่สามารถเปิดไฟล์ได้"</string>
<string name="retry" msgid="1835923075542266721">"ลองอีกครั้ง"</string>
<string name="no_downloads" msgid="3947445710685021498">"ประวัติการดาวน์โหลดว่างเปล่า"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 04ff6c2..ed7d581 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Browser"</string>
<string name="choose_upload" msgid="3649366287575002063">"Pumili ng file para sa pag-upload"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Bagong window"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Bagong incognito window"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Hindi available ang SD card."</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Abala ang SD card. Upang payagan ang mga pag-download, piliin ang \"I-off ang imbakan na USB\" sa notification."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Abala ang SD card. Upang payagan ang mga pag-download, piliin ang \"I-off ang USB storage\" sa notification."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Maaari lang mag-download ng mga URL ng \"http\" o \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Hindi mabuksan ang file"</string>
<string name="retry" msgid="1835923075542266721">"Subukang muli"</string>
<string name="no_downloads" msgid="3947445710685021498">"Walang laman ang kasaysayan ng pag-download."</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 9ca233a..3aa165d 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Tarayıcı"</string>
<string name="choose_upload" msgid="3649366287575002063">"Yükleme için dosya seçin"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Yeni pencere"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Yeni gizli pencere"</string>
<string name="active_tabs" msgid="3050623868203544623">"Pencereler"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD kart kullanılamıyor"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB depolama birimi meşgul. İndirme işlemlerine izin vermek için bildirim alanında \"USB depolama birimini kapat\"ı seçin."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD kart meşgul. İndirme işlemlerine izin vermek için bildirim alanında \"USB\'de depolamayı kapat\"ı seçin."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Yalnızca \"http\" veya \"https\" URL\'leri indirilebilir."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Dosya açılamıyor"</string>
<string name="retry" msgid="1835923075542266721">"Tekrar Dene"</string>
<string name="no_downloads" msgid="3947445710685021498">"İndirme geçmişi boş."</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index b774d03..1a76aa9 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Переглядач"</string>
<string name="choose_upload" msgid="3649366287575002063">"Вибер. файл для завантаж."</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Нове вікно"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Нове анонімне вікно"</string>
<string name="active_tabs" msgid="3050623868203544623">"Вікна"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Карта SD недоступна"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Носій USB зайнятий. Щоб дозволити завантаження, виберіть у сповіщенні \"Вимкнути носій USB\"."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Карта SD зайнята. Щоб дозвол. завантаж., виберіть у сповіщенні \"Вимкнути зберігання на USB\"."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Можна завантажувати лише URL-адреси \"http\" або \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Файл не відкривається"</string>
<string name="retry" msgid="1835923075542266721">"Повтор."</string>
<string name="no_downloads" msgid="3947445710685021498">"Історія завантажень порожня."</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index b24c8b4..23c1b6b 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"Trình duyệt"</string>
<string name="choose_upload" msgid="3649366287575002063">"Chọn tệp để tải lên"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"Cửa sổ mới"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"Cửa sổ ẩn danh mới"</string>
<string name="active_tabs" msgid="3050623868203544623">"Windows"</string>
@@ -317,6 +319,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"Thẻ SD không khả dụng"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"Thẻ SD đang bận. Để cho phép tải xuống, hãy chọn \"Tắt bộ bộ nhớ USB\" trong thông báo."</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"Thẻ SD đang bận. Để cho phép tải xuống, hãy chọn \"Tắt bộ lưu trữ USB\" trong thông báo."</string>
+ <string name="cannot_download" msgid="8150552478556798780">"Chỉ có thể tải xuống URL \"http\" hoặc \"https\"."</string>
<string name="download_no_application_title" msgid="1286056729168874295">"Không thể mở tệp"</string>
<string name="retry" msgid="1835923075542266721">"Thử lại"</string>
<string name="no_downloads" msgid="3947445710685021498">"Lịch sử tải xuống trống."</string>
diff --git a/res/values-xlarge/dimensions.xml b/res/values-xlarge/dimensions.xml
index 27c1ce2..613c133 100644
--- a/res/values-xlarge/dimensions.xml
+++ b/res/values-xlarge/dimensions.xml
@@ -17,6 +17,7 @@
<dimen name="widgetHorizontalSpacing">14dip</dimen>
<dimen name="widgetVerticalSpacing">12dip</dimen>
<dimen name="favicon_padded_size">28dip</dimen>
+ <dimen name="add_bookmark_width">500dip</dimen>
<!-- For the most visited page -->
<dimen name="mv_max_width">1010dp</dimen>
<dimen name="mv_item_width">231dp</dimen>
diff --git a/res/values-xlarge/integers.xml b/res/values-xlarge/integers.xml
new file mode 100644
index 0000000..abdafbf
--- /dev/null
+++ b/res/values-xlarge/integers.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- The number of lines in the suggestions dropdown in landscape -->
+ <integer name="max_suggest_lines_landscape">5</integer>
+ <!-- The number of lines in the suggestions dropdown in portrait -->
+ <integer name="max_suggest_lines_portrait">12</integer>
+</resources>
diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml
deleted file mode 100644
index 473b170..0000000
--- a/res/values-xlarge/styles.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- *
- * Copyright 2006,2007,2008 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.
- */
--->
-
-<resources>
- <style name="BrowserTheme" parent="@android:Theme.Holo">
- <item name="android:windowBackground">@color/white</item>
- <item name="android:colorBackground">#FFFFFFFF</item>
- <item name="android:windowActionBar">true</item>
- <item name="android:windowNoTitle">false</item>
- <item name="android:actionBarStyle">@style/ActionBarStyle</item>
- <item name="android:actionButtonStyle">@style/ActionButton</item>
- </style>
- <style name="Dialog" parent="@android:style/Theme.Holo.Dialog" >
- <item name="android:windowActionBar">false</item>
- </style>
- <style name="BookmarkTheme" parent="@android:Theme.Holo">
- <item name="android:windowActionBar">false</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:colorBackgroundCacheHint">@null</item>
- <item name="android:windowFrame">@null</item>
- <item name="android:windowContentOverlay">@null</item>
- <item name="android:windowIsFloating">true</item>
- <item name="android:backgroundDimEnabled">false</item>
- <item name="android:windowIsTranslucent">true</item>
- </style>
- <style name="ShortCutTheme" parent="@android:Theme.Holo">
- </style>
- <style name="ActionBarStyle">
- <item name="android:height">56dip</item>
- <item name="android:background">@drawable/bg_browsertabs</item>
- <item name="android:displayOptions"></item>
- </style>
- <style name="ActionButton">
- <item name="android:background">?android:attr/selectableItemBackground</item>
- </style>
- <style name="Suggestions" parent="@android:style/Widget.Holo.Light.AutoCompleteTextView">
- </style>
- <style name="SuggestionLineMedium"
- parent="@android:style/TextAppearance.Holo.Medium.Inverse">
- <item name="android:textSize">16sp</item>
- </style>
- <style name="SuggestionLineSmall"
- parent="@android:style/TextAppearance.Holo.Small.Inverse">
- <item name="android:textSize">12sp</item>
- </style>
-</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 16bee5c..3667e0c 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"浏览器"</string>
<string name="choose_upload" msgid="3649366287575002063">"选择要上传的文件"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"新窗口"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"新隐身窗口"</string>
<string name="active_tabs" msgid="3050623868203544623">"窗口"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"SD 卡不可用"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB 存储设备正忙。要允许下载,请在通知中选择“关闭 USB 存储设备”。"</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD 卡正忙。要允许下载,请在通知中选择“关闭 USB 存储设备”。"</string>
+ <string name="cannot_download" msgid="8150552478556798780">"只能从“http”或“https”网址下载。"</string>
<string name="download_no_application_title" msgid="1286056729168874295">"无法打开文件"</string>
<string name="retry" msgid="1835923075542266721">"重试"</string>
<string name="no_downloads" msgid="3947445710685021498">"下载历史记录为空。"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index e5df8ec..0a744ea 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -18,6 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name" msgid="1935869255545976415">"瀏覽器"</string>
<string name="choose_upload" msgid="3649366287575002063">"選擇要上載的檔案"</string>
+ <!-- no translation found for uploads_disabled (463761197575372994) -->
+ <skip />
<string name="new_tab" msgid="4505722538297295141">"新視窗"</string>
<string name="new_incognito_tab" msgid="5821404839654751753">"新的無痕式視窗"</string>
<string name="active_tabs" msgid="3050623868203544623">"視窗"</string>
@@ -316,6 +318,7 @@
<string name="download_sdcard_busy_dlg_title" product="default" msgid="6877712666046917741">"無法使用 SD 卡"</string>
<string name="download_sdcard_busy_dlg_msg" product="nosdcard" msgid="3979329954835690147">"USB 儲存裝置忙碌中。如要允許下載,請選取通知中的 [關閉 USB 儲存裝置]。"</string>
<string name="download_sdcard_busy_dlg_msg" product="default" msgid="3473883538192835204">"SD 記憶卡忙碌中。如要允許下載,請選取通知中的 [停用 USB 儲存裝置]。"</string>
+ <string name="cannot_download" msgid="8150552478556798780">"僅可下載「http」或「https」網址的檔案。"</string>
<string name="download_no_application_title" msgid="1286056729168874295">"無法開啟檔案"</string>
<string name="retry" msgid="1835923075542266721">"重試"</string>
<string name="no_downloads" msgid="3947445710685021498">"下載記錄是空的。"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 68b58f7..6fa0840 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -33,6 +33,6 @@
<color name="qc_slice_active">#E02090FF</color>
<color name="bookmarkWidgetFaviconBackground">#23ffffff</color>
<color name="bookmarkListFaviconBackground">#23ffffff</color>
- <color name="urlTextColor">#0E774A</color>
<color name="tabFaviconBackground">#FF555555</color>
+ <color name="tabFocusHighlight">#FF99CC00</color>
</resources>
diff --git a/res/values/dimensions.xml b/res/values/dimensions.xml
index 300714b..5054254 100644
--- a/res/values/dimensions.xml
+++ b/res/values/dimensions.xml
@@ -20,12 +20,13 @@
<dimen name="tab_overlap">8dp</dimen>
<dimen name="tab_addoverlap">14dp</dimen>
<dimen name="tab_slice">15.5dp</dimen>
- <dimen name="tab_padding">16dp</dimen>
+ <dimen name="tab_focus_stroke">2dip</dimen>
<dimen name="max_tab_width">300dp</dimen>
<dimen name="bookmarkThumbnailWidth">90dip</dimen>
<dimen name="bookmarkThumbnailHeight">80dip</dimen>
- <dimen name="add_bookmark_width">500dip</dimen>
- <dimen name="folder_selector_height">230dip</dimen>
+ <!-- Height determined by measuring the TableLayout in
+ browser_add_bookmark_content and matching that. -->
+ <dimen name="folder_selector_height">181dip</dimen>
<dimen name="widgetItemMinHeight">48dip</dimen>
<dimen name="favicon_size">16dip</dimen>
<dimen name="favicon_padded_size">20dip</dimen>
@@ -44,5 +45,11 @@
<dimen name="list_favicon_padding">5dip</dimen>
<dimen name="list_favicon_corner_radius">3dip</dimen>
<dimen name="tab_favicon_corner_radius">2dip</dimen>
- <dimen name="dropdown_offset">8dip</dimen>
+ <dimen name="widgetThumbnailHeight">104dip</dimen>
+ <dimen name="widgetHorizontalSpacing">14dip</dimen>
+ <dimen name="widgetVerticalSpacing">12dip</dimen>
+ <!-- For the combined Bookmarks History view -->
+ <dimen name="combo_paddingTop">10dip</dimen>
+ <dimen name="combo_paddingLeftRight">16dip</dimen>
+ <dimen name="combo_horizontalSpacing">8dip</dimen>
</resources>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index a0b7ae3..ad0ed90 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -15,7 +15,7 @@
<!-- The number of lines in the suggestions dropdown in landscape -->
<integer name="max_suggest_lines_landscape">5</integer>
<!-- The number of lines in the suggestions dropdown in portrait -->
- <integer name="max_suggest_lines_portrait">12</integer>
+ <integer name="max_suggest_lines_portrait">4</integer>
<!-- The maximum number of open tabs -->
<integer name="max_tabs">16</integer>
<!-- The duration of the tab animations in millisecs -->
@@ -23,4 +23,8 @@
<integer name="max_width_crumb">200</integer>
<!-- The maximum number of most visited URLs in the history tab -->
<integer name="most_visits_limit">10</integer>
+ <!-- Animation durations -->
+ <integer name="comboViewFadeInDuration">400</integer>
+ <!-- fade between tabs duration -->
+ <integer name="tabFadeDuration">300</integer>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 75762a0..0aeffca 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -18,6 +18,9 @@
<string name="application_name">Browser</string>
<!-- Displayed with a file picker to choose a file to upload -->
<string name="choose_upload">Choose file for upload</string>
+ <!-- Toast to show the user after they try to open the file picker but no apps on the
+ system can provide a file [CHAR-LIMIT=NONE]-->
+ <string name="uploads_disabled">File uploads are disabled.</string>
<!-- Name of menu item of a new tab. Also used in the title bar when displaying a new tab -->
<string name="new_tab">New window</string>
<!-- Name of menu item of a new incognito tab. Also used in the
@@ -232,12 +235,12 @@
<string name="copy_page_url">Copy page url</string>
<!-- Menu item -->
<string name="share_page">Share page</string>
- <!-- Menu item for saving a page as a web archive. -->
- <string name="menu_save_webarchive">Save as Web Archive</string>
- <!-- Toast informing the user that the page has been saved. -->
- <string name="webarchive_saved">Web archive saved.</string>
- <!-- Toast informing the user that saving the page has failed. -->
- <string name="webarchive_failed">Failed to save web archive.</string>
+ <!-- Menu item for saving a page. [CHAR LIMIT=30] -->
+ <string name="menu_save_webarchive">Save page</string>
+ <!-- Toast informing the user that the page has been saved. [CHAR LIMIT=50] -->
+ <string name="webarchive_saved">Page saved.</string>
+ <!-- Toast informing the user that saving the page has failed. [CHAR LIMIT=50] -->
+ <string name="webarchive_failed">Failed to save page.</string>
<!-- The number of bookmarks in a folder [CHAR LIMT=50] -->
<string name="contextheader_folder_bookmarkcount"><xliff:g id="bookmark_count">%d</xliff:g> bookmarks</string>
<!-- No bookmarks in the folder [CHAR LIMIT=50] -->
@@ -573,6 +576,8 @@
<!-- Do not tranlsate. Development option -->
<string name="pref_development_hardware_accel" translatable="false">Enable OpenGL Rendering</string>
<!-- Do not tranlsate. Development option -->
+ <string name="pref_development_visual_indicator" translatable="false">Enable Visual Indicator</string>
+ <!-- Do not tranlsate. Development option -->
<string name="js_engine_flags" translatable="false">Set JS flags</string>
<!-- Do not tranlsate. Development option -->
<string name="pref_development_uastring" translatable="false">UAString</string>
@@ -749,6 +754,8 @@
the user how to enable SD card storage -->
<string name="download_sdcard_busy_dlg_msg" product="default">The SD card is busy. To allow downloads, select \"Turn off USB storage\" in the notification.</string>
+ <!-- Toast for a download which cannot begin because the URL is not http or https -->
+ <string name="cannot_download">Can only download \"http\" or \"https\" URLs.</string>
<!-- Title for a dialog informing the user that there is no application on
the phone that can open the file that was downloaded -->
<string name="download_no_application_title">Cannot open file</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a4a27cc..8d222f4 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -18,20 +18,48 @@
*/
-->
<resources>
- <style name="BrowserTheme" parent="@android:Theme.Black">
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowContentOverlay">@null</item>
+ <style name="BrowserTheme" parent="@android:Theme.Holo">
+ <item name="android:windowBackground">@color/white</item>
+ <item name="android:colorBackground">#FFFFFFFF</item>
+ <item name="android:windowActionBar">true</item>
+ <item name="android:windowNoTitle">false</item>
+ <item name="android:actionBarStyle">@style/ActionBarStyle</item>
+ <item name="android:actionButtonStyle">@style/ActionButton</item>
</style>
- <style name="Dialog" parent="@android:style/Theme.Dialog">
- <item name="android:background">@color/black</item>
+ <style name="DialogWhenLarge" parent="@android:style/Theme.Holo.DialogWhenLarge" >
+ <item name="android:windowActionBar">false</item>
+ </style>
+ <style name="BookmarkTheme" parent="@android:Theme.Holo">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:windowFrame">@null</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
+ <style name="ActionBarStyle">
+ <item name="android:height">56dip</item>
+ <item name="android:background">@drawable/bg_browsertabs</item>
+ <item name="android:displayOptions"></item>
+ </style>
+ <style name="ActionButton">
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ </style>
+ <style name="Suggestions" parent="@android:style/Widget.Holo.Light.AutoCompleteTextView">
+ </style>
+ <style name="SuggestionLineMedium"
+ parent="@android:style/TextAppearance.Holo.Medium.Inverse">
+ <item name="android:textSize">16sp</item>
+ </style>
+ <style name="SuggestionLineSmall"
+ parent="@android:style/TextAppearance.Holo.Small.Inverse">
+ <item name="android:textSize">12sp</item>
</style>
<style name="ActionBar" parent="@android:style/Widget.ActionBar">
<item name="android:background">@color/black</item>
</style>
- <style name="BookmarkTheme" parent="@android:Theme.Black">
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowContentOverlay">@null</item>
- </style>
<style name="ShortcutTheme" parent="@android:Theme.Holo.DialogWhenLarge">
<item name="android:windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
@@ -56,10 +84,4 @@
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textStyle">normal</item>
</style>
- <style name="Suggestions" parent="@android:style/Widget.AutoCompleteTextView">
- </style>
- <style name="SuggestionLineMedium" parent="@android:style/TextAppearance.Medium">
- </style>
- <style name="SuggestionLineSmall" parent="@android:style/TextAppearance.Small">
- </style>
</resources>
diff --git a/res/xml/bookmarkthumbnailwidget_info.xml b/res/xml/bookmarkthumbnailwidget_info.xml
index 6797f85..65497cd 100644
--- a/res/xml/bookmarkthumbnailwidget_info.xml
+++ b/res/xml/bookmarkthumbnailwidget_info.xml
@@ -21,5 +21,6 @@
android:minHeight="219dip"
android:updatePeriodMillis="0"
android:previewImage="@drawable/browser_widget_preview"
- android:initialLayout="@layout/bookmarkthumbnailwidget">
+ android:initialLayout="@layout/bookmarkthumbnailwidget"
+ android:resizeMode="vertical">
</appwidget-provider>
diff --git a/res/xml/hidden_debug_preferences.xml b/res/xml/hidden_debug_preferences.xml
index 6d66eaa..661d9de 100644
--- a/res/xml/hidden_debug_preferences.xml
+++ b/res/xml/hidden_debug_preferences.xml
@@ -20,6 +20,11 @@
<!-- The javascript console is enabled by default when the user has
also enabled debug mode by navigating to about:debug. -->
<CheckBoxPreference
+ android:key="enable_visual_indicator"
+ android:defaultValue="false"
+ android:title="@string/pref_development_visual_indicator" />
+
+ <CheckBoxPreference
android:key="javascript_console"
android:defaultValue="true"
android:title="@string/pref_development_error_console" />
diff --git a/src/com/android/browser/AccountsChangedReceiver.java b/src/com/android/browser/AccountsChangedReceiver.java
index 92d6ad0..a34180a 100644
--- a/src/com/android/browser/AccountsChangedReceiver.java
+++ b/src/com/android/browser/AccountsChangedReceiver.java
@@ -16,6 +16,8 @@
package com.android.browser;
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
+
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.BroadcastReceiver;
@@ -55,6 +57,7 @@
ContentResolver.setSyncAutomatically(a, BrowserContract.AUTHORITY, false);
ContentResolver.setIsSyncable(a, BrowserContract.AUTHORITY, 0);
}
+ BookmarkThumbnailWidgetProvider.refreshWidgets(context, true);
}
}
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index 1444862..08f9d39 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -73,6 +73,7 @@
// Place on an edited bookmark to remove the saved thumbnail
public static final String REMOVE_THUMBNAIL = "remove_thumbnail";
public static final String USER_AGENT = "user_agent";
+ public static final String CHECK_FOR_DUPE = "check_for_dupe";
/* package */ static final String EXTRA_EDIT_BOOKMARK = "bookmark";
/* package */ static final String EXTRA_IS_FOLDER = "is_folder";
@@ -86,6 +87,8 @@
private final int LOADER_ID_ALL_FOLDERS = 1;
private final int LOADER_ID_FIND_ROOT = 2;
private final int LOADER_ID_CHECK_FOR_DUPE = 3;
+ private final int LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK = 4;
+ private final int LOADER_ID_FIND_FOLDER_BY_ID = 5;
private EditText mTitle;
private EditText mAddress;
@@ -117,6 +120,7 @@
private Drawable mHeaderIcon;
private View mRemoveLink;
private View mFakeTitleHolder;
+ private FolderSpinnerAdapter mFolderAdapter;
private static class Folder {
String Name;
long Id;
@@ -308,6 +312,16 @@
case FolderSpinnerAdapter.OTHER_FOLDER:
switchToFolderSelector();
break;
+ case FolderSpinnerAdapter.RECENT_FOLDER:
+ mCurrentFolder = mFolderAdapter.recentFolderId();
+ mSaveToHomeScreen = false;
+ // In case the user decides to select OTHER_FOLDER
+ // and choose a different one, so that we will start from
+ // the correct place.
+ LoaderManager manager = getLoaderManager();
+ manager.initLoader(LOADER_ID_ALL_FOLDERS, null, this);
+ manager.restartLoader(LOADER_ID_FOLDER_CONTENTS, null, this);
+ break;
default:
break;
}
@@ -412,6 +426,18 @@
selection,
selArgs,
null);
+ case LOADER_ID_FIND_FOLDER_BY_ID:
+ projection = new String[] {
+ BrowserContract.Bookmarks._ID,
+ BrowserContract.Bookmarks.TITLE
+ };
+ return new CursorLoader(this,
+ BookmarkUtils.getBookmarksUri(this),
+ projection,
+ BrowserContract.Bookmarks._ID + " = "
+ + args.getLong(BrowserContract.Bookmarks._ID),
+ null,
+ null);
case LOADER_ID_ALL_FOLDERS:
projection = new String[] {
BrowserContract.Bookmarks._ID,
@@ -420,7 +446,7 @@
BrowserContract.Bookmarks.IS_FOLDER
};
return new CursorLoader(this,
- BrowserContract.Bookmarks.CONTENT_URI,
+ BookmarkUtils.getBookmarksUri(this),
projection,
BrowserContract.Bookmarks.IS_FOLDER + " != 0",
null,
@@ -442,6 +468,16 @@
where,
null,
BrowserContract.Bookmarks._ID + " ASC");
+ case LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK:
+ projection = new String[] {
+ BrowserContract.Bookmarks.PARENT
+ };
+ return new CursorLoader(this,
+ BookmarkUtils.getBookmarksUri(this),
+ projection,
+ BrowserContract.Bookmarks.IS_FOLDER + " = 0",
+ null,
+ BrowserContract.Bookmarks.DATE_CREATED + " DESC");
default:
throw new AssertionError("Asking for nonexistant loader!");
}
@@ -484,6 +520,32 @@
case LOADER_ID_FOLDER_CONTENTS:
mAdapter.changeCursor(cursor);
break;
+ case LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK:
+ LoaderManager manager = getLoaderManager();
+ if (cursor != null && cursor.moveToFirst()) {
+ // Find the parent
+ long lastUsedFolder = cursor.getLong(0);
+ if (lastUsedFolder != mRootFolder
+ && lastUsedFolder != mCurrentFolder
+ && lastUsedFolder != 0) {
+ // Find out the parent's name
+ Bundle b = new Bundle();
+ b.putLong(BrowserContract.Bookmarks._ID, lastUsedFolder);
+ manager.initLoader(LOADER_ID_FIND_FOLDER_BY_ID, b, this);
+ }
+ }
+ manager.destroyLoader(LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK);
+ break;
+ case LOADER_ID_FIND_FOLDER_BY_ID:
+ if (cursor != null && cursor.moveToFirst()) {
+ long id = cursor.getLong(cursor.getColumnIndexOrThrow(
+ BrowserContract.Bookmarks._ID));
+ String title = cursor.getString(cursor.getColumnIndexOrThrow(
+ BrowserContract.Bookmarks.TITLE));
+ mFolderAdapter.addRecentFolder(id, title);
+ }
+ getLoaderManager().destroyLoader(LOADER_ID_FIND_FOLDER_BY_ID);
+ break;
case LOADER_ID_ALL_FOLDERS:
long parent = mCurrentFolder;
int idIndex = cursor.getColumnIndexOrThrow(
@@ -665,7 +727,8 @@
mCancelButton.setOnClickListener(this);
mFolder = (FolderSpinner) findViewById(R.id.folder);
- mFolder.setAdapter(new FolderSpinnerAdapter(!mEditingFolder));
+ mFolderAdapter = new FolderSpinnerAdapter(!mEditingFolder);
+ mFolder.setAdapter(mFolderAdapter);
mFolder.setOnSetSelectionListener(this);
mDefaultView = findViewById(R.id.default_view);
@@ -728,7 +791,8 @@
mCurrentFolder = mRootFolder;
}
setupTopCrumb();
- if (mEditingExisting || TextUtils.isEmpty(mOriginalUrl)) {
+ if (mEditingExisting || TextUtils.isEmpty(mOriginalUrl)
+ || !(mMap != null && mMap.getBoolean(CHECK_FOR_DUPE))) {
onCurrentFolderFound();
} else {
// User is attempting to bookmark a site, rather than deliberately
@@ -757,6 +821,11 @@
mFolder.setSelectionIgnoringSelectionChange(mEditingFolder ? 1 : 2);
} else {
setShowBookmarkIcon(true);
+ if (!mEditingExisting) {
+ // Find the most recently saved bookmark, so that we can include it in
+ // the list of options to save the current bookmark.
+ manager.initLoader(LOADER_ID_MOST_RECENTLY_SAVED_BOOKMARK, null, this);
+ }
if (!mEditingFolder) {
// Initially the "Bookmarks" folder should be showing, rather than
// the home screen. In the editing folder case, home screen is not
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index 756f8b8..93b0ec8 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -18,6 +18,9 @@
import com.android.browser.Tab.LockIcon;
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -32,7 +35,6 @@
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
@@ -67,7 +69,7 @@
Activity mActivity;
UiController mUiController;
TabControl mTabControl;
- private Tab mActiveTab;
+ protected Tab mActiveTab;
private InputMethodManager mInputManager;
private Drawable mSecLockIcon;
@@ -86,6 +88,8 @@
private Toast mStopToast;
+ private boolean mTitleShowing;
+
// the default <video> poster
private Bitmap mDefaultVideoPoster;
// the video progress view
@@ -114,6 +118,7 @@
mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
.findViewById(R.id.fullscreen_custom_content);
frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
+ mTitleShowing = false;
}
/**
@@ -161,6 +166,8 @@
public void onConfigurationChanged(Configuration config) {
}
+ public abstract void editUrl(boolean clearInput);
+
// key handling
@Override
@@ -189,7 +196,10 @@
@Override
public void bookmarkedStatusHasChanged(Tab tab) {
- // no op in base case
+ if (tab.inForeground()) {
+ boolean isBookmark = tab.isBookmarkedSite();
+ getTitleBar().setCurrentUrlIsBookmark(isBookmark);
+ }
}
@Override
@@ -212,18 +222,23 @@
}
@Override
- public void setActiveTab(Tab tab) {
+ public void setActiveTab(final Tab tab) {
+ setActiveTab(tab, true);
+ }
+
+ void setActiveTab(Tab tab, boolean needsAttaching) {
if ((tab != mActiveTab) && (mActiveTab != null)) {
removeTabFromContentView(mActiveTab);
}
mActiveTab = tab;
- attachTabToContentView(tab);
+ if (needsAttaching) {
+ attachTabToContentView(tab);
+ }
setShouldShowErrorConsole(tab, mUiController.shouldShowErrorConsole());
onTabDataChanged(tab);
onProgressChanged(tab);
boolean incognito = mActiveTab.getWebView().isPrivateBrowsingEnabled();
- getEmbeddedTitleBar().setIncognitoMode(incognito);
- getFakeTitleBar().setIncognitoMode(incognito);
+ getTitleBar().setIncognitoMode(incognito);
}
Tab getActiveTab() {
@@ -252,7 +267,7 @@
attachTabToContentView(tab);
}
- private void attachTabToContentView(Tab tab) {
+ protected void attachTabToContentView(Tab tab) {
if ((tab == null) || (tab.getWebView() == null)) {
return;
}
@@ -292,7 +307,7 @@
}
private void removeTabFromContentView(Tab tab) {
- hideFakeTitleBar();
+ hideTitleBar();
// Remove the container that contains the main WebView.
WebView mainView = tab.getWebView();
View container = tab.getViewContainer();
@@ -381,49 +396,47 @@
mContentView.addView(container, COVER_SCREEN_PARAMS);
}
- void showFakeTitleBar() {
- if (!isFakeTitleBarShowing() && !isActivityPaused()) {
- WebView mainView = mUiController.getCurrentWebView();
- // if there is no current WebView, don't show the faked title bar;
- if (mainView == null) {
- return;
- }
- // Do not need to check for null, since the current tab will have
- // at least a main WebView, or we would have returned above.
- if (mUiController.isInCustomActionMode()) {
- // Do not show the fake title bar, while a custom ActionMode
- // (i.e. find or select) is showing.
- return;
- }
- attachFakeTitleBar(mainView);
+ boolean canShowTitleBar() {
+ return !isTitleBarShowing()
+ && !isActivityPaused()
+ && (getActiveTab() != null)
+ && (getActiveTab().getWebView() != null)
+ && !mUiController.isInCustomActionMode();
+ }
+
+ void showTitleBar() {
+ mTitleShowing = true;
+ }
+
+ protected void hideTitleBar() {
+ mTitleShowing = false;
+ }
+
+ protected boolean isTitleBarShowing() {
+ return mTitleShowing;
+ }
+
+ protected abstract TitleBarBase getTitleBar();
+
+ protected void setTitleGravity(int gravity) {
+ getTitleBar().setTitleGravity(gravity);
+ Tab tab = getActiveTab();
+ if ((tab != null) && (tab.getWebView() != null)) {
+ tab.getWebView().setTitleBarGravity(gravity);
}
}
- protected abstract void attachFakeTitleBar(WebView mainView);
-
- protected abstract void hideFakeTitleBar();
-
- protected abstract boolean isFakeTitleBarShowing();
-
- protected abstract TitleBarBase getFakeTitleBar();
-
- protected abstract TitleBarBase getEmbeddedTitleBar();
-
@Override
public void showVoiceTitleBar(String title) {
- getEmbeddedTitleBar().setInVoiceMode(true);
- getEmbeddedTitleBar().setDisplayTitle(title);
- getFakeTitleBar().setInVoiceMode(true);
- getFakeTitleBar().setDisplayTitle(title);
+ getTitleBar().setInVoiceMode(true);
+ getTitleBar().setDisplayTitle(title);
}
@Override
public void revertVoiceTitleBar(Tab tab) {
- getEmbeddedTitleBar().setInVoiceMode(false);
+ getTitleBar().setInVoiceMode(false);
String url = tab.getUrl();
- getEmbeddedTitleBar().setDisplayTitle(url);
- getFakeTitleBar().setInVoiceMode(false);
- getFakeTitleBar().setDisplayTitle(url);
+ getTitleBar().setDisplayTitle(url);
}
@Override
@@ -440,12 +453,17 @@
FrameLayout wrapper =
(FrameLayout) mContentView.findViewById(R.id.webview_wrapper);
wrapper.setVisibility(View.GONE);
- hideFakeTitleBar();
+ hideTitleBar();
dismissIME();
if (mActiveTab != null) {
WebView web = mActiveTab.getWebView();
mActiveTab.putInBackground();
}
+ mComboView.setAlpha(0f);
+ ObjectAnimator anim = ObjectAnimator.ofFloat(mComboView, "alpha", 0f, 1f);
+ Resources res = mActivity.getResources();
+ anim.setDuration(res.getInteger(R.integer.comboViewFadeInDuration));
+ anim.start();
mContentView.addView(mComboView, COVER_SCREEN_PARAMS);
}
@@ -547,8 +565,7 @@
} else if (lockIconType == LockIcon.LOCK_ICON_MIXED) {
d = mMixLockIcon;
}
- getEmbeddedTitleBar().setLock(d);
- getFakeTitleBar().setLock(d);
+ getTitleBar().setLock(d);
}
protected void setUrlTitle(Tab tab) {
@@ -559,8 +576,7 @@
}
if (tab.isInVoiceSearchMode()) return;
if (tab.inForeground()) {
- getEmbeddedTitleBar().setDisplayTitle(url);
- getFakeTitleBar().setDisplayTitle(url);
+ getTitleBar().setDisplayTitle(url);
}
}
@@ -568,8 +584,7 @@
protected void setFavicon(Tab tab) {
if (tab.inForeground()) {
Bitmap icon = tab.getFavicon();
- getEmbeddedTitleBar().setFavicon(icon);
- getFakeTitleBar().setFavicon(icon);
+ getTitleBar().setFavicon(icon);
}
}
@@ -578,7 +593,7 @@
if (inLoad) {
// the titlebar was removed when the CAB was shown
// if the page is loading, show it again
- showFakeTitleBar();
+ showTitleBar();
}
}
@@ -623,6 +638,7 @@
@Override
public void setShouldShowErrorConsole(Tab tab, boolean flag) {
+ if (tab == null) return;
ErrorConsoleView errorConsole = tab.getErrorConsole(true);
if (flag) {
// Setting the show state of the console will cause it's the layout
diff --git a/src/com/android/browser/BookmarkUtils.java b/src/com/android/browser/BookmarkUtils.java
index cfb8e46..3aaf5d4 100644
--- a/src/com/android/browser/BookmarkUtils.java
+++ b/src/com/android/browser/BookmarkUtils.java
@@ -223,7 +223,6 @@
}
public static Uri.Builder addAccountInfo(Context context, Uri.Builder ub) {
- Uri uri = BrowserContract.Bookmarks.CONTENT_URI;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String accountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
String accountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
index beea489..e7dc729 100644
--- a/src/com/android/browser/Bookmarks.java
+++ b/src/com/android/browser/Bookmarks.java
@@ -39,7 +39,7 @@
/**
* This class is purely to have a common place for adding/deleting bookmarks.
*/
-/* package */ class Bookmarks {
+public class Bookmarks {
// We only want the user to be able to bookmark content that
// the browser can handle directly.
private static final String acceptableBookmarkSchemes[] = {
@@ -162,11 +162,9 @@
static final String QUERY_BOOKMARKS_WHERE =
Combined.URL + " == ? OR " +
- Combined.URL + " == ? OR " +
- Combined.URL + " LIKE ? || '%' OR " +
- Combined.URL + " LIKE ? || '%'";
+ Combined.URL + " == ?";
- /* package */ static Cursor queryCombinedForUrl(ContentResolver cr,
+ public static Cursor queryCombinedForUrl(ContentResolver cr,
String originalUrl, String url) {
if (cr == null || url == null) {
return null;
@@ -179,17 +177,8 @@
// Look for both the original url and the actual url. This takes in to
// account redirects.
- String originalUrlNoQuery = removeQuery(originalUrl);
- String urlNoQuery = removeQuery(url);
- originalUrl = originalUrlNoQuery + '?';
- url = urlNoQuery + '?';
- // Use NoQuery to search for the base url (i.e. if the url is
- // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com)
- // Use url to match the base url with other queries (i.e. if the url is
- // http://www.google.com/m, search for
- // http://www.google.com/m?some_query)
- final String[] selArgs = new String[] { originalUrlNoQuery, urlNoQuery, originalUrl, url };
+ final String[] selArgs = new String[] { originalUrl, url };
final String[] projection = new String[] { Combined.URL };
return cr.query(Combined.CONTENT_URI, projection, QUERY_BOOKMARKS_WHERE, selArgs, null);
}
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 899a7c2..3c025d2 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -19,6 +19,7 @@
import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -95,9 +96,7 @@
}
mController = new Controller(this);
- boolean xlarge = (getResources().getConfiguration().screenLayout
- & Configuration.SCREENLAYOUT_SIZE_MASK)
- == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ boolean xlarge = isXlarge(this);
if (xlarge) {
mUi = new XLargeUi(this, mController);
} else {
@@ -114,6 +113,12 @@
mController.start(icicle, getIntent());
}
+ public static boolean isXlarge(Context context) {
+ return (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK)
+ == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ }
+
@VisibleForTesting
Controller getController() {
return mController;
@@ -248,6 +253,12 @@
}
@Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return mController.onKeyLongPress(keyCode, event) ||
+ super.onKeyLongPress(keyCode, event);
+ }
+
+ @Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mController.onKeyUp(keyCode, event) ||
super.onKeyUp(keyCode, event);
@@ -271,5 +282,4 @@
mController.onActivityResult(requestCode, resultCode, intent);
}
-
}
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index 7475237..98b25a8 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -262,6 +262,10 @@
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.bookmark, menu);
+ if (!BrowserActivity.isXlarge(getActivity())) {
+ MenuItem item = menu.findItem(R.id.add_bookmark);
+ item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ }
}
@Override
@@ -372,7 +376,7 @@
.getDefaultSharedPreferences(getActivity());
prefs.registerOnSharedPreferenceChangeListener(this);
mCurrentView =
- prefs.getInt(PREF_SELECTED_VIEW, BrowserBookmarksPage.VIEW_THUMBNAILS);
+ prefs.getInt(PREF_SELECTED_VIEW, getDefaultView());
mAdapter = new BrowserBookmarksAdapter(getActivity(), mCurrentView);
lm.restartLoader(LOADER_BOOKMARKS, null, this);
@@ -382,6 +386,13 @@
return mRoot;
}
+ private int getDefaultView() {
+ if (BrowserActivity.isXlarge(getActivity())) {
+ return VIEW_THUMBNAILS;
+ }
+ return VIEW_LIST;
+ }
+
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -585,13 +596,17 @@
switch (mCurrentView) {
case VIEW_THUMBNAILS:
mList.setAdapter(null);
- mGrid.setAdapter(mAdapter);
+ if (mGrid.getAdapter() != mAdapter) {
+ mGrid.setAdapter(mAdapter);
+ }
mGrid.setVisibility(View.VISIBLE);
mList.setVisibility(View.GONE);
break;
case VIEW_LIST:
mGrid.setAdapter(null);
- mList.setAdapter(mAdapter);
+ if (mList.getAdapter() != mAdapter) {
+ mList.setAdapter(mAdapter);
+ }
mGrid.setVisibility(View.GONE);
mList.setVisibility(View.VISIBLE);
break;
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index faf0042..f3bc48a 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -109,7 +109,7 @@
private boolean lightTouch = false;
private boolean navDump = false;
private boolean hardwareAccelerated = true;
-
+ private boolean showVisualIndicator = false;
// Lab settings
private boolean quickControls = false;
private boolean useMostVisitedHomepage = false;
@@ -162,6 +162,7 @@
public final static String PREF_AUTOFILL_PROFILE = "autofill_profile";
public final static String PREF_AUTOFILL_ACTIVE_PROFILE_ID = "autofill_active_profile_id";
public final static String PREF_HARDWARE_ACCEL = "enable_hardware_accel";
+ public final static String PREF_VISUAL_INDICATOR = "enable_visual_indicator";
public final static String PREF_USER_AGENT = "user_agent";
public final static String PREF_QUICK_CONTROLS = "enable_quick_controls";
@@ -251,6 +252,7 @@
s.setUseWideViewPort(b.useWideViewPort);
s.setLoadsImagesAutomatically(b.loadsImagesAutomatically);
s.setJavaScriptEnabled(b.javaScriptEnabled);
+ s.setShowVisualIndicator(b.showVisualIndicator);
s.setPluginState(b.pluginState);
s.setJavaScriptCanOpenWindowsAutomatically(
b.javaScriptCanOpenWindowsAutomatically);
@@ -493,6 +495,7 @@
tracing = p.getBoolean("enable_tracing", tracing);
lightTouch = p.getBoolean("enable_light_touch", lightTouch);
navDump = p.getBoolean("enable_nav_dump", navDump);
+ showVisualIndicator = p.getBoolean(PREF_VISUAL_INDICATOR, showVisualIndicator);
}
quickControls = p.getBoolean(PREF_QUICK_CONTROLS, quickControls);
@@ -589,6 +592,10 @@
return hardwareAccelerated;
}
+ public boolean showVisualIndicator() {
+ return showVisualIndicator;
+ }
+
public boolean useQuickControls() {
return quickControls;
}
@@ -886,6 +893,8 @@
SharedPreferences p, String key) {
if (PREF_HARDWARE_ACCEL.equals(key)) {
hardwareAccelerated = p.getBoolean(PREF_HARDWARE_ACCEL, hardwareAccelerated);
+ } else if (PREF_VISUAL_INDICATOR.equals(key)) {
+ showVisualIndicator = p.getBoolean(PREF_VISUAL_INDICATOR, showVisualIndicator);
} else if (PREF_USER_AGENT.equals(key)) {
userAgent = Integer.parseInt(p.getString(PREF_USER_AGENT, "0"));
update();
diff --git a/src/com/android/browser/CombinedBookmarkHistoryView.java b/src/com/android/browser/CombinedBookmarkHistoryView.java
index 173abba..793f7a2 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryView.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryView.java
@@ -157,8 +157,12 @@
}
void setupActionBar(int startingFragment) {
- mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
- | ActionBar.DISPLAY_USE_LOGO);
+ if (BrowserActivity.isXlarge(mContext)) {
+ mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
+ | ActionBar.DISPLAY_USE_LOGO);
+ } else {
+ mActionBar.setDisplayOptions(0);
+ }
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
mActionBar.removeAllTabs();
mTabBookmarks = mActionBar.newTab();
@@ -170,7 +174,7 @@
mTabHistory.setTabListener(this);
mActionBar.addTab(mTabHistory, FRAGMENT_ID_HISTORY == startingFragment);
mActionBar.setCustomView(mBookmarksHeader);
-
+ mActionBar.show();
}
@Override
@@ -324,7 +328,7 @@
mUiController.onUrlSelected(settings.getHomePage(), false);
return true;
case R.id.add_bookmark:
- mUiController.bookmarkCurrentPage(mBookmarks.getFolderId());
+ mUiController.bookmarkCurrentPage(mBookmarks.getFolderId(), false);
return true;
case R.id.preferences_menu_id:
Intent intent = new Intent(mActivity, BrowserPreferencesPage.class);
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 74a66b1..ae91f11 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -43,6 +43,7 @@
import android.net.http.SslError;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
@@ -77,6 +78,7 @@
import android.webkit.WebIconDatabase;
import android.webkit.WebSettings;
import android.webkit.WebView;
+import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -315,7 +317,10 @@
if (urlData.isEmpty()) {
loadUrl(webView, mSettings.getHomePage());
} else {
- loadUrlDataIn(t, urlData);
+ // monkey protection against delayed start
+ if (t != null) {
+ loadUrlDataIn(t, urlData);
+ }
}
} else {
mTabControl.restoreState(icicle, currentTab, restoreIncognitoTabs,
@@ -483,7 +488,8 @@
case R.id.save_link_context_menu_id:
case R.id.download_context_menu_id:
DownloadHandler.onDownloadStartNoStream(
- mActivity, url, null, null, null);
+ mActivity, url, null, null, null,
+ view.isPrivateBrowsingEnabled());
break;
}
break;
@@ -508,9 +514,9 @@
break;
case UPDATE_BOOKMARK_THUMBNAIL:
- WebView view = (WebView) msg.obj;
- if (view != null) {
- updateScreenshot(view);
+ Tab tab = (Tab) msg.obj;
+ if (tab != null) {
+ updateScreenshot(tab);
}
break;
}
@@ -676,7 +682,7 @@
}
void onDestroy() {
- if (mUploadHandler != null) {
+ if (mUploadHandler != null && !mUploadHandler.handled()) {
mUploadHandler.onResult(Activity.RESULT_CANCELED, null);
mUploadHandler = null;
}
@@ -747,7 +753,7 @@
// an incorrect screenshot. Therefore, remove any pending thumbnail
// messages from the queue.
mHandler.removeMessages(Controller.UPDATE_BOOKMARK_THUMBNAIL,
- view);
+ tab);
// reset sync timer to avoid sync starts during loading a page
CookieSyncManager.getInstance().resetSync();
@@ -794,7 +800,7 @@
// Only update the bookmark screenshot if the user did not
// cancel the load early.
mHandler.sendMessageDelayed(mHandler.obtainMessage(
- UPDATE_BOOKMARK_THUMBNAIL, 0, 0, tab.getWebView()),
+ UPDATE_BOOKMARK_THUMBNAIL, 0, 0, tab),
500);
}
}
@@ -957,9 +963,10 @@
@Override
public void onDownloadStart(Tab tab, String url, String userAgent,
String contentDisposition, String mimetype, long contentLength) {
+ WebView w = tab.getWebView();
DownloadHandler.onDownloadStart(mActivity, url, userAgent,
- contentDisposition, mimetype);
- if (tab.getWebView().copyBackForwardList().getSize() == 0) {
+ contentDisposition, mimetype, w.isPrivateBrowsingEnabled());
+ if (w.copyBackForwardList().getSize() == 0) {
// This Tab was opened for the sole purpose of downloading a
// file. Remove it.
if (tab == mTabControl.getCurrentTab()) {
@@ -1024,9 +1031,7 @@
// callback from phone title bar
public void editUrl() {
if (mOptionsMenuOpen) mActivity.closeOptionsMenu();
- String url = (getCurrentTopWebView() == null) ? null : getCurrentTopWebView().getUrl();
- startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
- null, false);
+ mUi.editUrl(false);
}
public void startVoiceSearch() {
@@ -1088,10 +1093,9 @@
}
break;
case FILE_SELECTED:
- // Choose a file from the file picker.
+ // Chose a file from the file picker.
if (null == mUploadHandler) break;
mUploadHandler.onResult(resultCode, intent);
- mUploadHandler = null;
break;
case AUTOFILL_SETUP:
// Determine whether a profile was actually set up or not
@@ -1369,7 +1373,8 @@
menu.findItem(R.id.view_image_context_menu_id).setIntent(
new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
menu.findItem(R.id.download_context_menu_id).
- setOnMenuItemClickListener(new Download(mActivity, extra));
+ setOnMenuItemClickListener(
+ new Download(mActivity, extra, webview.isPrivateBrowsingEnabled()));
menu.findItem(R.id.set_wallpaper_context_menu_id).
setOnMenuItemClickListener(new WallpaperHandler(mActivity,
extra));
@@ -1465,6 +1470,11 @@
final MenuItem newtab = menu.findItem(R.id.new_tab_menu_id);
newtab.setEnabled(getTabControl().canCreateNewTab());
+ MenuItem archive = menu.findItem(R.id.save_webarchive_menu_id);
+ Tab tab = getTabControl().getCurrentTab();
+ String url = tab != null ? tab.getUrl() : null;
+ archive.setVisible(!TextUtils.isEmpty(url)
+ && !url.endsWith(".webarchivexml"));
break;
}
mCurrentMenuState = mMenuState;
@@ -1520,7 +1530,7 @@
break;
case R.id.add_bookmark_menu_id:
- bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID);
+ bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID, false);
break;
case R.id.stop_reload_menu_id:
@@ -1567,6 +1577,55 @@
getCurrentTopWebView().showFindDialog(null, true);
break;
+ case R.id.save_webarchive_menu_id:
+ String state = Environment.getExternalStorageState();
+ if (!Environment.MEDIA_MOUNTED.equals(state)) {
+ Log.e(LOGTAG, "External storage not mounted");
+ Toast.makeText(mActivity, R.string.webarchive_failed,
+ Toast.LENGTH_SHORT).show();
+ break;
+ }
+ final String directory = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOWNLOADS) + File.separator;
+ File dir = new File(directory);
+ if (!dir.exists() && !dir.mkdirs()) {
+ Log.e(LOGTAG, "Save as Web Archive: mkdirs for " + directory + " failed!");
+ Toast.makeText(mActivity, R.string.webarchive_failed,
+ Toast.LENGTH_SHORT).show();
+ break;
+ }
+ final WebView topWebView = getCurrentTopWebView();
+ final String title = topWebView.getTitle();
+ final String url = topWebView.getUrl();
+ topWebView.saveWebArchive(directory, true,
+ new ValueCallback<String>() {
+ @Override
+ public void onReceiveValue(final String value) {
+ if (value != null) {
+ File file = new File(value);
+ final long length = file.length();
+ if (file.exists() && length > 0) {
+ Toast.makeText(mActivity, R.string.webarchive_saved,
+ Toast.LENGTH_SHORT).show();
+ final DownloadManager manager = (DownloadManager) mActivity
+ .getSystemService(Context.DOWNLOAD_SERVICE);
+ new Thread("Add WebArchive to download manager") {
+ @Override
+ public void run() {
+ manager.completedDownload(null == title ? value : title,
+ value, true, "application/x-webarchive-xml",
+ value, length, true);
+ }
+ }.start();
+ return;
+ }
+ }
+ DownloadHandler.onDownloadStartNoStream(mActivity,
+ url, null, null, null, topWebView.isPrivateBrowsingEnabled());
+ }
+ });
+ break;
+
case R.id.page_info_menu_id:
mPageDialogsHandler.showPageInfo(mTabControl.getCurrentTab(),
false);
@@ -1804,9 +1863,13 @@
/**
* add the current page as a bookmark to the given folder id
* @param folderId use -1 for the default folder
+ * @param canBeAnEdit If true, check to see whether the site is already
+ * bookmarked, and if it is, edit that bookmark. If false, and
+ * the site is already bookmarked, do not attempt to edit the
+ * existing bookmark.
*/
@Override
- public void bookmarkCurrentPage(long folderId) {
+ public void bookmarkCurrentPage(long folderId, boolean canBeAnEdit) {
Intent i = new Intent(mActivity,
AddBookmarkPage.class);
WebView w = getCurrentTopWebView();
@@ -1827,6 +1890,9 @@
i.putExtra(BrowserContract.Bookmarks.FAVICON, w.getFavicon());
i.putExtra(BrowserContract.Bookmarks.PARENT,
folderId);
+ if (canBeAnEdit) {
+ i.putExtra(AddBookmarkPage.CHECK_FOR_DUPE, true);
+ }
// Put the dialog at the upper right of the screen, covering the
// star on the title bar.
i.putExtra("gravity", Gravity.RIGHT | Gravity.TOP);
@@ -1905,13 +1971,31 @@
return ret;
}
- private void updateScreenshot(WebView view) {
+ private void updateScreenshot(Tab tab) {
// If this is a bookmarked site, add a screenshot to the database.
- // FIXME: When should we update? Every time?
// FIXME: Would like to make sure there is actually something to
// draw, but the API for that (WebViewCore.pictureReady()) is not
// currently accessible here.
+ WebView view = tab.getWebView();
+ if (view == null) {
+ // Tab was destroyed
+ return;
+ }
+ final String url = tab.getUrl();
+ final String originalUrl = view.getOriginalUrl();
+
+ if (TextUtils.isEmpty(url)) {
+ return;
+ }
+
+ // Only update thumbnails for web urls (http(s)://), not for
+ // about:, javascript:, data:, etc...
+ // Unless it is a bookmarked site, then always update
+ if (!Patterns.WEB_URL.matcher(url).matches() && !tab.isBookmarkedSite()) {
+ return;
+ }
+
final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(mActivity),
getDesiredThumbnailHeight(mActivity));
if (bm == null) {
@@ -1919,41 +2003,34 @@
}
final ContentResolver cr = mActivity.getContentResolver();
- final String url = view.getUrl();
- final String originalUrl = view.getOriginalUrl();
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ Cursor cursor = null;
+ try {
+ // TODO: Clean this up
+ cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
+ if (cursor != null && cursor.moveToFirst()) {
+ final ByteArrayOutputStream os =
+ new ByteArrayOutputStream();
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
- // Only update thumbnails for web urls (http(s)://), not for
- // about:, javascript:, data:, etc...
- if (url != null && Patterns.WEB_URL.matcher(url).matches()) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... unused) {
- Cursor cursor = null;
- try {
- // TODO: Clean this up
- cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
- if (cursor != null && cursor.moveToFirst()) {
- final ByteArrayOutputStream os =
- new ByteArrayOutputStream();
- bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+ ContentValues values = new ContentValues();
+ values.put(Images.THUMBNAIL, os.toByteArray());
- ContentValues values = new ContentValues();
- values.put(Images.THUMBNAIL, os.toByteArray());
+ do {
values.put(Images.URL, cursor.getString(0));
-
- do {
- cr.update(Images.CONTENT_URI, values, null, null);
- } while (cursor.moveToNext());
- }
- } catch (IllegalStateException e) {
- // Ignore
- } finally {
- if (cursor != null) cursor.close();
+ cr.update(Images.CONTENT_URI, values, null, null);
+ } while (cursor.moveToNext());
}
- return null;
+ } catch (IllegalStateException e) {
+ // Ignore
+ } finally {
+ if (cursor != null) cursor.close();
}
- }.execute();
- }
+ return null;
+ }
+ }.execute();
}
private class Copy implements OnMenuItemClickListener {
@@ -1972,16 +2049,18 @@
private static class Download implements OnMenuItemClickListener {
private Activity mActivity;
private String mText;
+ private boolean mPrivateBrowsing;
public boolean onMenuItemClick(MenuItem item) {
DownloadHandler.onDownloadStartNoStream(mActivity, mText, null,
- null, null);
+ null, null, mPrivateBrowsing);
return true;
}
- public Download(Activity activity, String toDownload) {
+ public Download(Activity activity, String toDownload, boolean privateBrowsing) {
mActivity = activity;
mText = toDownload;
+ mPrivateBrowsing = privateBrowsing;
}
}
@@ -2020,9 +2099,12 @@
}
protected void setActiveTab(Tab tab) {
- mTabControl.setCurrentTab(tab);
- // the tab is guaranteed to have a webview after setCurrentTab
- mUi.setActiveTab(tab);
+ // monkey protection against delayed start
+ if (tab != null) {
+ mTabControl.setCurrentTab(tab);
+ // the tab is guaranteed to have a webview after setCurrentTab
+ mUi.setActiveTab(tab);
+ }
}
protected void closeEmptyChildTab() {
@@ -2298,20 +2380,10 @@
closeTab(current);
} else {
if (current.closeOnExit()) {
- // force the tab's inLoad() to be false as we are going to
- // either finish the activity or remove the tab. This will
- // ensure pauseWebViewTimers() taking action.
- current.clearInPageLoad();
- if (mTabControl.getTabCount() == 1) {
- mActivity.finish();
- return;
- }
- if (mActivityPaused) {
- Log.e(LOGTAG, "BrowserActivity is already paused "
- + "while handing goBackOnePageOrQuit.");
- }
- pauseWebViewTimers(current);
- removeTab(current);
+ // This will finish the activity if there is only one tab
+ // open or it will switch to the next available tab if
+ // available.
+ closeCurrentTab();
}
/*
* Instead of finishing the activity, simply push this to the back
@@ -2345,6 +2417,12 @@
startSearch(result, false, bundle, false);
}
+ @Override
+ public void startSearch(String url) {
+ startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
+ null, false);
+ }
+
private void startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
if (appSearchData == null) {
@@ -2378,14 +2456,13 @@
// Even if MENU is already held down, we need to call to super to open
// the IME on long press.
- if (!noModifiers && KeyEvent.KEYCODE_MENU == keyCode) {
+ if (!noModifiers
+ && ((KeyEvent.KEYCODE_MENU == keyCode)
+ || (KeyEvent.KEYCODE_CTRL_LEFT == keyCode)
+ || (KeyEvent.KEYCODE_CTRL_RIGHT == keyCode))) {
mMenuIsDown = true;
return false;
}
- // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
- // still down, we don't want to trigger the search. Pretend to consume
- // the key and do nothing.
- if (mMenuIsDown) return true;
WebView webView = getCurrentTopWebView();
if (webView == null) return false;
@@ -2410,15 +2487,8 @@
return true;
case KeyEvent.KEYCODE_BACK:
if (!noModifiers) break;
- if (event.getRepeatCount() == 0) {
- event.startTracking();
- return true;
- } else if (mUi.showsWeb()
- && event.isLongPress()) {
- bookmarksOrHistoryPicker(true);
- return true;
- }
- break;
+ event.startTracking();
+ return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
if (ctrl) {
webView.goBack();
@@ -2458,10 +2528,12 @@
// case KeyEvent.KEYCODE_O: // in Chrome: open file
// case KeyEvent.KEYCODE_P: // in Chrome: print page
// case KeyEvent.KEYCODE_Q: // unused
-// case KeyEvent.KEYCODE_R:
+// case KeyEvent.KEYCODE_R:
// case KeyEvent.KEYCODE_S: // in Chrome: saves page
case KeyEvent.KEYCODE_T:
- if (ctrl) {
+ // we can't use the ctrl/shift flags, they check for
+ // exclusive use of a modifier
+ if (event.isCtrlPressed()) {
if (event.isShiftPressed()) {
openIncognitoTab();
} else {
@@ -2477,8 +2549,20 @@
// case KeyEvent.KEYCODE_Y: // unused
// case KeyEvent.KEYCODE_Z: // unused
}
- // if we get here, it is a regular key and webview is not null
- return mUi.dispatchKey(keyCode, event);
+ // it is a regular key and webview is not null
+ return mUi.dispatchKey(keyCode, event);
+ }
+
+ boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ switch(keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ if (mUi.showsWeb()) {
+ bookmarksOrHistoryPicker(true);
+ return true;
+ }
+ break;
+ }
+ return false;
}
boolean onKeyUp(int keyCode, KeyEvent event) {
diff --git a/src/com/android/browser/DownloadHandler.java b/src/com/android/browser/DownloadHandler.java
index 40278f4..17ad320 100644
--- a/src/com/android/browser/DownloadHandler.java
+++ b/src/com/android/browser/DownloadHandler.java
@@ -53,9 +53,11 @@
* @param userAgent User agent of the downloading application.
* @param contentDisposition Content-disposition http header, if present.
* @param mimetype The mimetype of the content reported by the server
+ * @param privateBrowsing If the request is coming from a private browsing tab.
*/
public static void onDownloadStart(Activity activity, String url,
- String userAgent, String contentDisposition, String mimetype) {
+ String userAgent, String contentDisposition, String mimetype,
+ boolean privateBrowsing) {
// if we're dealing wih A/V content that's not explicitly marked
// for download, check if it's streamable.
if (contentDisposition == null
@@ -93,7 +95,7 @@
}
}
onDownloadStartNoStream(activity, url, userAgent, contentDisposition,
- mimetype);
+ mimetype, privateBrowsing);
}
// This is to work around the fact that java.net.URI throws Exceptions
@@ -134,10 +136,11 @@
* @param userAgent User agent of the downloading application.
* @param contentDisposition Content-disposition http header, if present.
* @param mimetype The mimetype of the content reported by the server
+ * @param privateBrowsing If the request is coming from a private browsing tab.
*/
/*package */ static void onDownloadStartNoStream(Activity activity,
String url, String userAgent, String contentDisposition,
- String mimetype) {
+ String mimetype, boolean privateBrowsing) {
String filename = URLUtil.guessFileName(url,
contentDisposition, mimetype);
@@ -181,7 +184,13 @@
String addressString = webAddress.toString();
Uri uri = Uri.parse(addressString);
- final DownloadManager.Request request = new DownloadManager.Request(uri);
+ final DownloadManager.Request request;
+ try {
+ request = new DownloadManager.Request(uri);
+ } catch (IllegalArgumentException e) {
+ Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show();
+ return;
+ }
request.setMimeType(mimetype);
// set downloaded file destination to /sdcard/Download.
// or, should it be set to one of several Environment.DIRECTORY* dirs depending on mimetype?
@@ -192,7 +201,7 @@
request.setDescription(webAddress.getHost());
// XXX: Have to use the old url since the cookies were stored using the
// old percent-encoded url.
- String cookies = CookieManager.getInstance().getCookie(url);
+ String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing);
request.addRequestHeader("cookie", cookies);
request.setNotificationVisibility(
DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
index 768eab5..64a7316 100644
--- a/src/com/android/browser/DownloadTouchIcon.java
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -39,10 +39,10 @@
import android.webkit.WebView;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.io.InputStream;
class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
+
private final ContentResolver mContentResolver;
private Cursor mCursor;
private final String mOriginalUrl;
@@ -110,18 +110,21 @@
String url = values[0];
if (inDatabase || mMessage != null) {
- AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
- HttpHost httpHost = Proxy.getPreferredHttpHost(mContext, url);
- if (httpHost != null) {
- ConnRouteParams.setDefaultProxy(client.getParams(), httpHost);
- }
-
- HttpGet request = new HttpGet(url);
-
- // Follow redirects
- HttpClientParams.setRedirecting(client.getParams(), true);
+ AndroidHttpClient client = null;
+ HttpGet request = null;
try {
+ client = AndroidHttpClient.newInstance(mUserAgent);
+ HttpHost httpHost = Proxy.getPreferredHttpHost(mContext, url);
+ if (httpHost != null) {
+ ConnRouteParams.setDefaultProxy(client.getParams(), httpHost);
+ }
+
+ request = new HttpGet(url);
+
+ // Follow redirects
+ HttpClientParams.setRedirecting(client.getParams(), true);
+
HttpResponse response = client.execute(request);
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
@@ -139,12 +142,14 @@
}
}
}
- } catch (IllegalArgumentException ex) {
- request.abort();
- } catch (IOException ex) {
- request.abort();
+ } catch (Exception ex) {
+ if (request != null) {
+ request.abort();
+ }
} finally {
- client.close();
+ if (client != null) {
+ client.close();
+ }
}
}
@@ -183,9 +188,9 @@
ContentValues values = new ContentValues();
values.put(Images.TOUCH_ICON, os.toByteArray());
- values.put(Images.URL, mCursor.getString(0));
do {
+ values.put(Images.URL, mCursor.getString(0));
mContentResolver.update(Images.CONTENT_URI, values, null, null);
} while (mCursor.moveToNext());
}
diff --git a/src/com/android/browser/GoogleAccountLogin.java b/src/com/android/browser/GoogleAccountLogin.java
index eaf45ea..f4ccfd1 100644
--- a/src/com/android/browser/GoogleAccountLogin.java
+++ b/src/com/android/browser/GoogleAccountLogin.java
@@ -41,6 +41,7 @@
import android.preference.PreferenceManager;
import android.util.Log;
import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -86,6 +87,12 @@
mWebView = new WebView(mActivity);
mRunnable = runnable;
+ // XXX: Doing pre-login causes onResume to skip calling
+ // resumeWebViewTimers. So to avoid problems with timers not running, we
+ // duplicate the work here using the off-screen WebView.
+ CookieSyncManager.getInstance().startSync();
+ mWebView.resumeTimers();
+
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
@@ -336,7 +343,7 @@
@Override public void run() {
mProgressDialog.dismiss();
}
- }, 1000);
+ }, 2000);
mRunnable = null;
mWebView.destroy();
diff --git a/src/com/android/browser/HttpAuthenticationDialog.java b/src/com/android/browser/HttpAuthenticationDialog.java
index a9ba332..ac4119c 100644
--- a/src/com/android/browser/HttpAuthenticationDialog.java
+++ b/src/com/android/browser/HttpAuthenticationDialog.java
@@ -18,10 +18,13 @@
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
/**
* HTTP authentication dialog.
@@ -109,6 +112,16 @@
View v = factory.inflate(R.layout.http_authentication, null);
mUsernameView = (TextView) v.findViewById(R.id.username_edit);
mPasswordView = (TextView) v.findViewById(R.id.password_edit);
+ mPasswordView.setOnEditorActionListener(new OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ mDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick();
+ return true;
+ }
+ return false;
+ }
+ });
String title = mContext.getText(R.string.sign_in_to).toString().replace(
"%s1", mHost).replace("%s2", mRealm);
diff --git a/src/com/android/browser/IntentHandler.java b/src/com/android/browser/IntentHandler.java
index e17fdc5..77db538 100644
--- a/src/com/android/browser/IntentHandler.java
+++ b/src/com/android/browser/IntentHandler.java
@@ -133,6 +133,10 @@
urlData = new UrlData(mSettings.getHomePage());
}
+ if (intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false)) {
+ mController.openTabAndShow(mTabControl.getCurrentTab(), urlData, false, null);
+ return;
+ }
final String appId = intent
.getStringExtra(Browser.EXTRA_APPLICATION_ID);
if ((Intent.ACTION_VIEW.equals(action)
diff --git a/src/com/android/browser/PhoneUi.java b/src/com/android/browser/PhoneUi.java
index 4119c29..f1939e4 100644
--- a/src/com/android/browser/PhoneUi.java
+++ b/src/com/android/browser/PhoneUi.java
@@ -24,8 +24,8 @@
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
+import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebView;
@@ -37,8 +37,8 @@
private static final String LOGTAG = "PhoneUi";
private TitleBar mTitleBar;
- private TitleBar mFakeTitleBar;
private ActiveTabsPage mActiveTabsPage;
+ private TouchProxy mTitleOverlay;
boolean mExtendedMenuOpen;
boolean mOptionsMenuOpen;
@@ -49,12 +49,17 @@
*/
public PhoneUi(Activity browser, UiController controller) {
super(browser, controller);
- mTitleBar = new TitleBar(mActivity, mUiController);
+ mTitleBar = new TitleBar(mActivity, mUiController, this);
// mTitleBar will be always be shown in the fully loaded mode on
// phone
mTitleBar.setProgress(100);
- mFakeTitleBar = new TitleBar(mActivity, mUiController);
+ mActivity.getActionBar().hide();
+ }
+ @Override
+ public void hideComboView() {
+ super.hideComboView();
+ mActivity.getActionBar().hide();
}
// webview factory
@@ -90,7 +95,13 @@
@Override
public void onDestroy() {
- hideFakeTitleBar();
+ hideTitleBar();
+ }
+
+ @Override
+ public void editUrl(boolean clearInput) {
+ String url = getActiveTab().getUrl();
+ mUiController.startSearch(url);
}
@Override
@@ -107,14 +118,14 @@
public void onProgressChanged(Tab tab) {
if (tab.inForeground()) {
int progress = tab.getLoadProgress();
- mFakeTitleBar.setProgress(progress);
+ mTitleBar.setProgress(progress);
if (progress == 100) {
if (!mOptionsMenuOpen || !mExtendedMenuOpen) {
- hideFakeTitleBar();
+ hideTitleBar();
}
} else {
if (!mOptionsMenuOpen || mExtendedMenuOpen) {
- showFakeTitleBar();
+ showTitleBar();
}
}
}
@@ -130,7 +141,7 @@
Log.e(LOGTAG, "active tab with no webview detected");
return;
}
- view.setEmbeddedTitleBar(getEmbeddedTitleBar());
+ view.setEmbeddedTitleBar(getTitleBar());
if (tab.isInVoiceSearchMode()) {
showVoiceTitleBar(tab.getVoiceDisplayTitle());
} else {
@@ -140,57 +151,23 @@
}
@Override
- protected void attachFakeTitleBar(WebView mainView) {
- WindowManager manager = (WindowManager)
- mActivity.getSystemService(Context.WINDOW_SERVICE);
-
- // Add the title bar to the window manager so it can receive
- // touches while the menu is up
- WindowManager.LayoutParams params =
- new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- params.gravity = Gravity.TOP;
- boolean atTop = mainView.getScrollY() == 0;
- params.windowAnimations = atTop ? 0 : R.style.TitleBar;
- manager.addView(mFakeTitleBar, params);
+ protected void showTitleBar() {
+ if (canShowTitleBar()) {
+ setTitleGravity(Gravity.TOP);
+ super.showTitleBar();
+ }
}
@Override
- protected void hideFakeTitleBar() {
- if (!isFakeTitleBarShowing()) return;
- WindowManager.LayoutParams params =
- (WindowManager.LayoutParams) mFakeTitleBar.getLayoutParams();
- WebView mainView = mUiController.getCurrentWebView();
- // Although we decided whether or not to animate based on the
- // current
- // scroll position, the scroll position may have changed since the
- // fake title bar was displayed. Make sure it has the appropriate
- // animation/lack thereof before removing.
- params.windowAnimations =
- mainView != null && mainView.getScrollY() == 0 ?
- 0 : R.style.TitleBar;
- WindowManager manager = (WindowManager) mActivity
- .getSystemService(Context.WINDOW_SERVICE);
- manager.updateViewLayout(mFakeTitleBar, params);
- manager.removeView(mFakeTitleBar);
+ protected void hideTitleBar() {
+ if (isTitleBarShowing()) {
+ setTitleGravity(Gravity.NO_GRAVITY);
+ super.hideTitleBar();
+ }
}
@Override
- protected boolean isFakeTitleBarShowing() {
- return (mFakeTitleBar.getParent() != null);
- }
-
- @Override
- protected TitleBarBase getFakeTitleBar() {
- return mFakeTitleBar;
- }
-
- @Override
- protected TitleBarBase getEmbeddedTitleBar() {
+ protected TitleBarBase getTitleBar() {
return mTitleBar;
}
@@ -200,7 +177,7 @@
public void showActiveTabsPage() {
mActiveTabsPage = new ActiveTabsPage(mActivity, mUiController);
mTitleBar.setVisibility(View.GONE);
- hideFakeTitleBar();
+ hideTitleBar();
mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
mActiveTabsPage.requestFocus();
}
@@ -225,8 +202,14 @@
@Override
public void onOptionsMenuOpened() {
mOptionsMenuOpen = true;
- // options menu opened, show fake title bar
- showFakeTitleBar();
+ // options menu opened, show title bar
+ showTitleBar();
+ if (mTitleOverlay == null) {
+ // This assumes that getTitleBar always returns the same View
+ mTitleOverlay = new TouchProxy(mActivity, getTitleBar());
+ }
+ mActivity.getWindowManager().addView(mTitleOverlay,
+ mTitleOverlay.getWindowLayoutParams());
}
@Override
@@ -234,32 +217,33 @@
// Switching the menu to expanded view, so hide the
// title bar.
mExtendedMenuOpen = true;
- hideFakeTitleBar();
+ hideTitleBar();
}
@Override
public void onOptionsMenuClosed(boolean inLoad) {
mOptionsMenuOpen = false;
- if (!inLoad) {
- hideFakeTitleBar();
+ mActivity.getWindowManager().removeView(mTitleOverlay);
+ if (!inLoad && !getTitleBar().hasFocus()) {
+ hideTitleBar();
}
}
@Override
public void onExtendedMenuClosed(boolean inLoad) {
mExtendedMenuOpen = false;
- showFakeTitleBar();
+ showTitleBar();
}
@Override
public void onContextMenuCreated(Menu menu) {
- hideFakeTitleBar();
+ hideTitleBar();
}
@Override
public void onContextMenuClosed(Menu menu, boolean inLoad) {
if (inLoad) {
- showFakeTitleBar();
+ showTitleBar();
}
}
@@ -267,7 +251,7 @@
@Override
public void onActionModeStarted(ActionMode mode) {
- hideFakeTitleBar();
+ hideTitleBar();
}
@Override
@@ -275,4 +259,32 @@
return false;
}
+ static class TouchProxy extends View {
+
+ View mTarget;
+
+ TouchProxy(Context context, View target) {
+ super(context);
+ mTarget = target;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ return mTarget.dispatchTouchEvent(event);
+ }
+
+ WindowManager.LayoutParams getWindowLayoutParams() {
+ WindowManager.LayoutParams params =
+ new WindowManager.LayoutParams(
+ mTarget.getWidth(),
+ mTarget.getHeight(),
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.gravity = Gravity.TOP | Gravity.LEFT;
+ params.y = mTarget.getTop();
+ params.x = mTarget.getLeft();
+ return params;
+ }
+ }
}
diff --git a/src/com/android/browser/PieControl.java b/src/com/android/browser/PieControl.java
index 6326f2e..2e2eba4 100644
--- a/src/com/android/browser/PieControl.java
+++ b/src/com/android/browser/PieControl.java
@@ -64,22 +64,22 @@
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
mPie.setLayoutParams(lp);
- mForward = makeMenuView(R.drawable.ic_pie_forward);
- mPie.addItem(mForward);
- mRefresh = makeMenuView(R.drawable.ic_pie_refresh);
- mPie.addItem(mRefresh);
+ mNewTab = makeMenuView(R.drawable.ic_pie_new_tab);
+ mPie.addItem(mNewTab);
mBack = makeMenuView(R.drawable.ic_pie_back);
mPie.addItem(mBack);
mUrl = makeMenuView(R.drawable.ic_pie_web);
mPie.addItem(mUrl);
mBookmarks = makeMenuView(R.drawable.ic_pie_bookmarks);
mPie.addItem(mBookmarks);
- mNewTab = makeMenuView(R.drawable.ic_pie_new_tab);
- mPie.addItem(mNewTab);
mOptions = makeMenuView(R.drawable.ic_pie_more);
mPie.addItem(mOptions);
- setClickListener(mBack, mForward, mRefresh, mUrl, mOptions,
- mBookmarks, mNewTab);
+ setClickListener(mBack,
+ mUrl,
+ mOptions,
+ mBookmarks,
+ mNewTab
+ );
mPie.setController(this);
}
container.addView(mPie);
@@ -126,14 +126,14 @@
web.reload();
}
} else if (mUrl == v) {
- mUi.showFakeTitleBarAndEdit();
+ mUi.showTitleBarAndEdit();
} else if (mOptions == v) {
mActivity.openOptionsMenu();
} else if (mBookmarks == v) {
mUiController.bookmarksOrHistoryPicker(false);
} else if (mNewTab == v) {
mUiController.openTabToHomePage();
- mUi.showFakeTitleBarAndEdit();
+ mUi.showTitleBarAndEdit();
} else if (mClose == v) {
mUiController.closeCurrentTab();
} else {
diff --git a/src/com/android/browser/ScrollWebView.java b/src/com/android/browser/ScrollWebView.java
index e2ef902..d1dc25b 100644
--- a/src/com/android/browser/ScrollWebView.java
+++ b/src/com/android/browser/ScrollWebView.java
@@ -1,36 +1,44 @@
/*
* 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
+ * 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
+ * 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.
+ * 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.Context;
+import android.graphics.Bitmap;
+import android.graphics.Paint;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;
import java.util.Map;
/**
- * Manage WebView scroll events
+ * Manage WebView scroll events
*/
public class ScrollWebView extends WebView implements Runnable {
private ScrollListener mScrollListener;
private boolean mIsCancelled;
private boolean mBackgroundRemoved = false;
+ private boolean mUserInitiated = false;
+ private TitleBarBase mTitleBar;
+ private boolean mDrawCached = false;
+ private Bitmap mBitmap;
+ private Paint mCachePaint = new Paint();
/**
* @param context
@@ -48,8 +56,8 @@
* @param attrs
* @param defStyle
*/
- public ScrollWebView(Context context, AttributeSet attrs, int defStyle,
- boolean privateBrowsing) {
+ public ScrollWebView(
+ Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
super(context, attrs, defStyle, privateBrowsing);
}
@@ -68,10 +76,15 @@
super(context);
}
+ @Override
+ protected int getTitleHeight() {
+ return (mTitleBar != null) ? mTitleBar.getEmbeddedHeight() : 0;
+ }
+
// scroll runnable implementation
public void run() {
if (!mIsCancelled && (mScrollListener != null)) {
- mScrollListener.onScroll(getVisibleTitleHeight());
+ mScrollListener.onScroll(getVisibleTitleHeight(), mUserInitiated);
}
}
@@ -82,12 +95,28 @@
@Override
public void setEmbeddedTitleBar(final View title) {
super.setEmbeddedTitleBar(title);
+ mTitleBar = (TitleBarBase) title;
if (title != null && mScrollListener != null) {
// allow the scroll listener to initialize its state
post(this);
}
}
+ public boolean hasTitleBar() {
+ return (mTitleBar != null);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ if (MotionEvent.ACTION_DOWN == evt.getActionMasked()) {
+ mUserInitiated = true;
+ } else if (MotionEvent.ACTION_UP == evt.getActionMasked()
+ || (MotionEvent.ACTION_CANCEL == evt.getActionMasked())) {
+ mUserInitiated = false;
+ }
+ return super.onTouchEvent(evt);
+ }
+
@Override
public void stopScroll() {
mIsCancelled = true;
@@ -108,22 +137,46 @@
mScrollListener = l;
}
+ @Override
+ public void invalidate() {
+ if (!mDrawCached) {
+ super.invalidate();
+ }
+ }
+
// callback for scroll events
interface ScrollListener {
- public void onScroll(int visibleTitleHeight);
+ public void onScroll(int visibleTitleHeight, boolean userInitiated);
+ }
+
+ void setDrawCached(boolean cached) {
+ if (cached) {
+ buildDrawingCache();
+ mBitmap = getDrawingCache(false);
+ mDrawCached = (mBitmap != null);
+ } else {
+ mBitmap = null;
+ destroyDrawingCache();
+ mDrawCached = false;
+ }
}
@Override
protected void onDraw(android.graphics.Canvas c) {
- super.onDraw(c);
- if (!mBackgroundRemoved && getRootView().getBackground() != null) {
- mBackgroundRemoved = true;
- post(new Runnable() {
- public void run() {
- getRootView().setBackgroundDrawable(null);
- }
- });
+ if (mDrawCached) {
+ c.drawBitmap(mBitmap, getScrollX(), getScrollY(), mCachePaint);
+ } else {
+ super.onDraw(c);
+ if (!mBackgroundRemoved && getRootView().getBackground() != null) {
+ mBackgroundRemoved = true;
+ post(new Runnable() {
+ public void run() {
+ getRootView().setBackgroundDrawable(null);
+ }
+ });
+ }
}
}
+
}
diff --git a/src/com/android/browser/TabBar.java b/src/com/android/browser/TabBar.java
index d115f1a..1d17cb3 100644
--- a/src/com/android/browser/TabBar.java
+++ b/src/com/android/browser/TabBar.java
@@ -75,8 +75,6 @@
private Map<Tab, TabView> mTabMap;
- private int mVisibleTitleHeight;
-
private Drawable mGenericFavicon;
private int mCurrentTextureWidth = 0;
@@ -87,6 +85,7 @@
private final Paint mActiveShaderPaint = new Paint();
private final Paint mInactiveShaderPaint = new Paint();
+ private final Paint mFocusPaint = new Paint();
private final Matrix mActiveMatrix = new Matrix();
private final Matrix mInactiveMatrix = new Matrix();
@@ -96,7 +95,6 @@
private int mTabOverlap;
private int mAddTabOverlap;
private int mTabSliceWidth;
- private int mTabPadding;
private boolean mUseQuickControls;
public TabBar(Activity activity, UiController controller, XLargeUi ui) {
@@ -122,14 +120,11 @@
mGenericFavicon = res.getDrawable(R.drawable.app_web_browser_sm);
updateTabs(mUiController.getTabs());
-
- mVisibleTitleHeight = 1;
mButtonWidth = -1;
// tab dimensions
mTabOverlap = (int) res.getDimension(R.dimen.tab_overlap);
mAddTabOverlap = (int) res.getDimension(R.dimen.tab_addoverlap);
mTabSliceWidth = (int) res.getDimension(R.dimen.tab_slice);
- mTabPadding = (int) res.getDimension(R.dimen.tab_padding);
mActiveShaderPaint.setStyle(Paint.Style.FILL);
mActiveShaderPaint.setAntiAlias(true);
@@ -137,6 +132,10 @@
mInactiveShaderPaint.setStyle(Paint.Style.FILL);
mInactiveShaderPaint.setAntiAlias(true);
+ mFocusPaint.setStyle(Paint.Style.STROKE);
+ mFocusPaint.setStrokeWidth(res.getDimension(R.dimen.tab_focus_stroke));
+ mFocusPaint.setAntiAlias(true);
+ mFocusPaint.setColor(res.getColor(R.color.tabFocusHighlight));
}
void setUseQuickControls(boolean useQuickControls) {
@@ -198,14 +197,16 @@
mUiController.openTabToHomePage();
} else if (mTabs.getSelectedTab() == view) {
if (mUseQuickControls) {
- if (mUi.isFakeTitleBarShowing() && !isLoading()) {
- mUi.hideFakeTitleBar();
+ if (mUi.isTitleBarShowing() && !isLoading()) {
+ mUi.stopEditingUrl();
+ mUi.hideTitleBar();
} else {
mUi.stopWebViewScrolling();
- mUi.showFakeTitleBarAndEdit();
+ mUi.showTitleBarAndEdit();
}
- } else if (mUi.isFakeTitleBarShowing() && !isLoading()) {
- mUi.hideFakeTitleBar();
+ } else if (mUi.isTitleBarShowing() && !isLoading()) {
+ mUi.stopEditingUrl();
+ mUi.hideTitleBar();
} else {
showUrlBar();
}
@@ -220,7 +221,7 @@
private void showUrlBar() {
mUi.stopWebViewScrolling();
- mUi.showFakeTitleBar();
+ mUi.showTitleBar();
}
void showTitleBarIndicator(boolean show) {
@@ -251,22 +252,25 @@
// callback after fake titlebar is hidden
void onHideTitleBar() {
- showTitleBarIndicator(mVisibleTitleHeight == 0);
Tab tab = mTabControl.getCurrentTab();
- tab.getWebView().requestFocus();
+ WebView w = tab.getWebView();
+ if (w != null) {
+ showTitleBarIndicator(w.getVisibleTitleHeight() == 0);
+ }
}
// webview scroll listener
@Override
- public void onScroll(int visibleTitleHeight) {
+ public void onScroll(int visibleTitleHeight, boolean userInitiated) {
if (mUseQuickControls) return;
// isLoading is using the current tab, which initially might not be set yet
if (mTabControl.getCurrentTab() != null
&& !isLoading()) {
if (visibleTitleHeight == 0) {
- if (!showsTitleBarIndicator()) {
- mUi.hideFakeTitleBar();
+ if (!showsTitleBarIndicator()
+ && (!mUi.isEditingUrl() || userInitiated)) {
+ mUi.hideTitleBar();
showTitleBarIndicator(true);
}
} else {
@@ -275,7 +279,6 @@
}
}
}
- mVisibleTitleHeight = visibleTitleHeight;
}
@Override
@@ -316,6 +319,7 @@
boolean mSelected;
boolean mInLoad;
Path mPath;
+ Path mFocusPath;
int[] mWindowPos;
/**
@@ -325,6 +329,7 @@
super(context);
setWillNotDraw(false);
mPath = new Path();
+ mFocusPath = new Path();
mWindowPos = new int[2];
mTab = tab;
setGravity(Gravity.CENTER_VERTICAL);
@@ -402,6 +407,8 @@
lp.width = selected ? mTabWidthSelected : mTabWidthUnselected;
lp.height = LayoutParams.MATCH_PARENT;
setLayoutParams(lp);
+ setFocusable(!selected);
+ postInvalidate();
}
void setDisplayTitle(String title) {
@@ -443,6 +450,7 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
setTabPath(mPath, 0, 0, r - l, b - t);
+ setFocusPath(mFocusPath, 0, 0, r - l, b - t);
}
@Override
@@ -467,12 +475,14 @@
mInactiveShaderPaint.setShader(mInactiveShader);
}
}
-
- int state = canvas.save();
- getLocationInWindow(mWindowPos);
- Paint paint = mSelected ? mActiveShaderPaint : mInactiveShaderPaint;
- drawClipped(canvas, paint, mPath, mWindowPos[0]);
- canvas.restoreToCount(state);
+ // add some monkey protection
+ if ((mActiveShader != null) && (mInactiveShader != null)) {
+ int state = canvas.save();
+ getLocationInWindow(mWindowPos);
+ Paint paint = mSelected ? mActiveShaderPaint : mInactiveShaderPaint;
+ drawClipped(canvas, paint, mPath, mWindowPos[0]);
+ canvas.restoreToCount(state);
+ }
super.dispatchDraw(canvas);
}
@@ -482,6 +492,9 @@
matrix.setTranslate(-left, 0.0f);
(mSelected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix);
canvas.drawPath(clipPath, paint);
+ if (isFocused()) {
+ canvas.drawPath(mFocusPath, mFocusPaint);
+ }
}
private void setTabPath(Path path, int l, int t, int r, int b) {
@@ -493,6 +506,14 @@
path.close();
}
+ private void setFocusPath(Path path, int l, int t, int r, int b) {
+ path.reset();
+ path.moveTo(l, b);
+ path.lineTo(l, t);
+ path.lineTo(r - mTabSliceWidth, t);
+ path.lineTo(r, b);
+ }
+
}
static Drawable createFaviconBackground(Context context) {
@@ -588,8 +609,7 @@
WebView webview = tab.getWebView();
if (webview != null) {
int h = webview.getVisibleTitleHeight();
- mVisibleTitleHeight = h -1;
- onScroll(h);
+ onScroll(h, true);
}
}
}
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
index bdef82e..686416c 100644
--- a/src/com/android/browser/TitleBar.java
+++ b/src/com/android/browser/TitleBar.java
@@ -16,92 +16,65 @@
package com.android.browser;
-import com.android.common.speech.LoggingEvents;
-
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
-import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
-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.DisplayMetrics;
-import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.widget.ImageButton;
import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
/**
* This class represents a title bar for a particular "tab" or "window" in the
* browser.
*/
-public class TitleBar extends TitleBarBase {
+public class TitleBar extends TitleBarBase implements OnFocusChangeListener,
+ OnClickListener {
private Activity mActivity;
- private UiController mController;
- private TextView mTitle;
- private ImageView mRtButton;
- private Drawable mCircularProgress;
- private ProgressBar mHorizontalProgress;
- private ImageView mStopButton;
+ private ImageButton mBookmarkButton;
+ private PageProgressView mHorizontalProgress;
+ private ImageButton mStopButton;
private Drawable mBookmarkDrawable;
private Drawable mVoiceDrawable;
private boolean mInLoad;
- private View mTitleBg;
- private MyHandler mHandler;
private Intent mVoiceSearchIntent;
- private boolean mInVoiceMode;
- private Drawable mVoiceModeBackground;
- private Drawable mNormalBackground;
- private Drawable mLoadingBackground;
private ImageSpan mArcsSpan;
- private int mLeftMargin;
- private int mRightMargin;
- private static int LONG_PRESS = 1;
-
- public TitleBar(Activity activity, UiController controller) {
- super(activity);
- mHandler = new MyHandler();
+ public TitleBar(Activity activity, UiController controller, PhoneUi ui) {
+ super(activity, controller, ui);
LayoutInflater factory = LayoutInflater.from(activity);
factory.inflate(R.layout.title_bar, this);
mActivity = activity;
- mController = controller;
- mTitle = (TextView) findViewById(R.id.title);
- mTitle.setCompoundDrawablePadding(5);
+ mUrlInput = (UrlInputView) findViewById(R.id.url_input);
+ mUrlInput.setCompoundDrawablePadding(5);
+ mUrlInput.setContainer(this);
+ mUrlInput.setSelectAllOnFocus(true);
+ mUrlInput.setController(mUiController);
+ mUrlInput.setUrlInputListener(this);
+ mUrlInput.setOnFocusChangeListener(this);
- mTitleBg = findViewById(R.id.title_bg);
mLockIcon = (ImageView) findViewById(R.id.lock);
mFavicon = (ImageView) findViewById(R.id.favicon);
- mStopButton = (ImageView) findViewById(R.id.stop);
+ mStopButton = (ImageButton) findViewById(R.id.stop);
+ mBookmarkButton = (ImageButton) findViewById(R.id.bookmark);
+ mStopButton.setOnClickListener(this);
+ mBookmarkButton.setOnClickListener(this);
- mRtButton = (ImageView) findViewById(R.id.rt_btn);
- Resources resources = activity.getResources();
- mCircularProgress = resources.getDrawable(
- com.android.internal.R.drawable.search_spinner);
- DisplayMetrics metrics = resources.getDisplayMetrics();
- mLeftMargin = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, 8f, metrics);
- mRightMargin = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, 6f, metrics);
- int iconDimension = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, 20f, metrics);
- mCircularProgress.setBounds(0, 0, iconDimension, iconDimension);
- mHorizontalProgress = (ProgressBar) findViewById(
+ mHorizontalProgress = (PageProgressView) findViewById(
R.id.progress_horizontal);
mVoiceSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
mVoiceSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
@@ -115,40 +88,18 @@
PackageManager pm = activity.getPackageManager();
ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
PackageManager.MATCH_DEFAULT_ONLY);
+ Resources resources = getResources();
if (ri == null) {
mVoiceSearchIntent = null;
} else {
mVoiceDrawable = resources.getDrawable(
android.R.drawable.ic_btn_speak_now);
}
- mBookmarkDrawable = mRtButton.getDrawable();
- mVoiceModeBackground = resources.getDrawable(
- R.drawable.title_voice);
- mNormalBackground = mTitleBg.getBackground();
- mLoadingBackground = resources.getDrawable(R.drawable.title_loading);
+ mBookmarkDrawable = mBookmarkButton.getDrawable();
mArcsSpan = new ImageSpan(activity, R.drawable.arcs,
ImageSpan.ALIGN_BASELINE);
}
- private class MyHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == LONG_PRESS) {
- // Prevent the normal action from happening by setting the title
- // bar's state to false.
- mTitleBg.setPressed(false);
- // Need to call a special method on BrowserActivity for when the
- // fake title bar is up, because its ViewGroup does not show a
- // context menu.
- // TODO:
- // this test is not valid for all UIs; fix later
- if (getParent() != null) {
- mActivity.openContextMenu(TitleBar.this);
- }
- }
- }
- };
-
@Override
public void createContextMenu(ContextMenu menu) {
MenuInflater inflater = mActivity.getMenuInflater();
@@ -156,93 +107,6 @@
mActivity.onCreateContextMenu(menu, this, null);
}
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- ImageView button = mInLoad ? mStopButton : mRtButton;
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- // Make all touches hit either the textfield or the button,
- // depending on which side of the right edge of the textfield
- // they hit.
- if ((int) event.getX() > mTitleBg.getRight()) {
- button.setPressed(true);
- } else {
- mTitleBg.setPressed(true);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- LONG_PRESS),
- ViewConfiguration.getLongPressTimeout());
- }
- break;
- case MotionEvent.ACTION_MOVE:
- int slop = ViewConfiguration.get(mActivity)
- .getScaledTouchSlop();
- if ((int) event.getY() > getHeight() + slop) {
- // We only trigger the actions in ACTION_UP if one or the
- // other is pressed. Since the user moved off the title
- // bar, mark both as not pressed.
- mTitleBg.setPressed(false);
- button.setPressed(false);
- mHandler.removeMessages(LONG_PRESS);
- break;
- }
- int x = (int) event.getX();
- int titleRight = mTitleBg.getRight();
- if (mTitleBg.isPressed() && x > titleRight + slop) {
- mTitleBg.setPressed(false);
- mHandler.removeMessages(LONG_PRESS);
- } else if (button.isPressed() && x < titleRight - slop) {
- button.setPressed(false);
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- button.setPressed(false);
- mTitleBg.setPressed(false);
- mHandler.removeMessages(LONG_PRESS);
- break;
- case MotionEvent.ACTION_UP:
- if (button.isPressed()) {
- if (mInVoiceMode) {
- if (mController.getTabControl().getCurrentTab()
- .voiceSearchSourceIsGoogle()) {
- Intent i = new Intent(
- LoggingEvents.ACTION_LOG_EVENT);
- i.putExtra(LoggingEvents.EXTRA_EVENT,
- LoggingEvents.VoiceSearch.RETRY);
- mActivity.sendBroadcast(i);
- }
- mActivity.startActivity(mVoiceSearchIntent);
- } else if (mInLoad) {
- mController.stopLoading();
- } else {
- mController.bookmarkCurrentPage(
- AddBookmarkPage.DEFAULT_FOLDER_ID);
- }
- button.setPressed(false);
- } else if (mTitleBg.isPressed()) {
- mHandler.removeMessages(LONG_PRESS);
- if (mInVoiceMode) {
- if (mController.getTabControl().getCurrentTab()
- .voiceSearchSourceIsGoogle()) {
- Intent i = new Intent(
- LoggingEvents.ACTION_LOG_EVENT);
- i.putExtra(LoggingEvents.EXTRA_EVENT,
- LoggingEvents.VoiceSearch.N_BEST_REVEAL);
- mActivity.sendBroadcast(i);
- }
- mController.showVoiceSearchResults(
- mTitle.getText().toString().trim());
- } else {
- mController.editUrl();
- }
- mTitleBg.setPressed(false);
- }
- break;
- default:
- break;
- }
- return true;
- }
-
/**
* Change the TitleBar to or from voice mode. If there is no package to
* handle voice search, the TitleBar cannot be set to voice mode.
@@ -253,30 +117,22 @@
mInVoiceMode = inVoiceMode && mVoiceSearchIntent != null;
Drawable titleDrawable;
if (mInVoiceMode) {
- mRtButton.setImageDrawable(mVoiceDrawable);
- titleDrawable = mVoiceModeBackground;
- mTitle.setEllipsize(null);
- mRtButton.setVisibility(View.VISIBLE);
+ mBookmarkButton.setImageDrawable(mVoiceDrawable);
+ mUrlInput.setEllipsize(null);
+ mBookmarkButton.setVisibility(View.VISIBLE);
mStopButton.setVisibility(View.GONE);
- mTitleBg.setBackgroundDrawable(titleDrawable);
- mTitleBg.setPadding(mLeftMargin, mTitleBg.getPaddingTop(),
- mRightMargin, mTitleBg.getPaddingBottom());
} else {
if (mInLoad) {
- titleDrawable = mLoadingBackground;
- mRtButton.setVisibility(View.GONE);
+ mBookmarkButton.setVisibility(View.GONE);
mStopButton.setVisibility(View.VISIBLE);
} else {
- titleDrawable = mNormalBackground;
- mRtButton.setVisibility(View.VISIBLE);
+ mBookmarkButton.setVisibility(View.VISIBLE);
mStopButton.setVisibility(View.GONE);
- mRtButton.setImageDrawable(mBookmarkDrawable);
+ mBookmarkButton.setImageDrawable(mBookmarkDrawable);
}
- mTitle.setEllipsize(TextUtils.TruncateAt.END);
- mTitleBg.setBackgroundDrawable(titleDrawable);
- mTitleBg.setPadding(mLeftMargin, 0, mRightMargin, 0);
+ mUrlInput.setEllipsize(TextUtils.TruncateAt.END);
}
- mTitle.setSingleLine(!mInVoiceMode);
+ mUrlInput.setSingleLine(!mInVoiceMode);
}
/**
@@ -284,33 +140,21 @@
*/
@Override
void setProgress(int newProgress) {
- if (newProgress >= mHorizontalProgress.getMax()) {
- mTitle.setCompoundDrawables(null, null, null, null);
- ((Animatable) mCircularProgress).stop();
- mHorizontalProgress.setVisibility(View.INVISIBLE);
+ if (newProgress >= PROGRESS_MAX) {
+ mHorizontalProgress.setVisibility(View.GONE);
if (!mInVoiceMode) {
- mRtButton.setImageDrawable(mBookmarkDrawable);
- mRtButton.setVisibility(View.VISIBLE);
+ mBookmarkButton.setImageDrawable(mBookmarkDrawable);
+ mBookmarkButton.setVisibility(View.VISIBLE);
mStopButton.setVisibility(View.GONE);
- mTitleBg.setBackgroundDrawable(mNormalBackground);
- mTitleBg.setPadding(mLeftMargin, 0, mRightMargin, 0);
}
mInLoad = false;
} else {
- mHorizontalProgress.setProgress(newProgress);
- if (!mInLoad && getWindowToken() != null) {
- // checking the window token lets us be sure that we
- // are attached to a window before starting the animation,
- // preventing a potential race condition
- // (fix for bug http://b/2115736)
- mTitle.setCompoundDrawables(null, null, mCircularProgress,
- null);
- ((Animatable) mCircularProgress).start();
+ mHorizontalProgress.setProgress(newProgress * PageProgressView.MAX_PROGRESS
+ / PROGRESS_MAX);
+ if (!mInLoad) {
mHorizontalProgress.setVisibility(View.VISIBLE);
if (!mInVoiceMode) {
- mTitleBg.setBackgroundDrawable(mLoadingBackground);
- mTitleBg.setPadding(mLeftMargin, 0, mRightMargin, 0);
- mRtButton.setVisibility(View.GONE);
+ mBookmarkButton.setVisibility(View.GONE);
mStopButton.setVisibility(View.VISIBLE);
}
mInLoad = true;
@@ -326,7 +170,7 @@
@Override
void setDisplayTitle(String title) {
if (title == null) {
- mTitle.setText(R.string.new_tab);
+ mUrlInput.setText(R.string.new_tab);
} else {
if (mInVoiceMode) {
// Add two spaces. The second one will be replaced with an
@@ -336,10 +180,32 @@
int end = spannable.length();
spannable.setSpan(mArcsSpan, end - 1, end,
Spanned.SPAN_MARK_POINT);
- mTitle.setText(spannable);
+ mUrlInput.setText(spannable);
} else {
- mTitle.setText(title);
+ mUrlInput.setText(title);
}
}
}
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (v == mUrlInput && hasFocus) {
+ mActivity.closeOptionsMenu();
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mStopButton) {
+ mUiController.stopLoading();
+ } else if (v == mBookmarkButton) {
+ mUiController.bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID,
+ true);
+ }
+ }
+
+ @Override
+ public void setCurrentUrlIsBookmark(boolean isBookmark) {
+ mBookmarkButton.setActivated(isBookmark);
+ }
}
diff --git a/src/com/android/browser/TitleBarBase.java b/src/com/android/browser/TitleBarBase.java
index 024f83c..46136db 100644
--- a/src/com/android/browser/TitleBarBase.java
+++ b/src/com/android/browser/TitleBarBase.java
@@ -16,29 +16,46 @@
package com.android.browser;
+import com.android.browser.UrlInputView.UrlInputListener;
+
+import android.app.SearchManager;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.PaintDrawable;
+import android.os.Bundle;
+import android.speech.RecognizerResultsIntent;
+import android.view.Gravity;
import android.view.View;
+import android.widget.AbsoluteLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
/**
* Base class for a title bar used by the browser.
*/
-public class TitleBarBase extends LinearLayout {
+public class TitleBarBase extends LinearLayout implements UrlInputListener {
+
+ protected static final int PROGRESS_MAX = 100;
+
// These need to be set by the subclass.
protected ImageView mFavicon;
protected ImageView mLockIcon;
protected Drawable mGenericFavicon;
+ protected UiController mUiController;
+ protected BaseUi mBaseUi;
+ protected UrlInputView mUrlInput;
+ protected boolean mInVoiceMode;
- public TitleBarBase(Context context) {
+ public TitleBarBase(Context context, UiController controller, BaseUi ui) {
super(context, null);
+ mUiController = controller;
+ mBaseUi = ui;
mGenericFavicon = context.getResources().getDrawable(
R.drawable.app_web_browser_sm);
}
@@ -76,4 +93,89 @@
/* package */ void setInVoiceMode(boolean inVoiceMode) {}
/* package */ void setIncognitoMode(boolean incognito) {}
+
+ void setTitleGravity(int gravity) {
+ int newTop = 0;
+ if (gravity != Gravity.NO_GRAVITY) {
+ View parent = (View) getParent();
+ if (parent != null) {
+ if (gravity == Gravity.TOP) {
+ newTop = parent.getScrollY();
+ } else if (gravity == Gravity.BOTTOM) {
+ newTop = parent.getScrollY() + parent.getHeight() - getHeight();
+ }
+ }
+ }
+ AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) getLayoutParams();
+ if (lp != null) {
+ lp.y = newTop;
+ setLayoutParams(lp);
+ }
+ }
+
+ public int getEmbeddedHeight() {
+ return getHeight();
+ }
+
+ // UrlInputListener implementation
+
+ /**
+ * callback from suggestion dropdown
+ * user selected a suggestion
+ */
+ @Override
+ public void onAction(String text, String extra, String source) {
+ mUiController.getCurrentTopWebView().requestFocus();
+ mBaseUi.hideTitleBar();
+ Intent i = new Intent();
+ String action = null;
+ if (UrlInputView.VOICE.equals(source)) {
+ action = RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS;
+ source = null;
+ } else {
+ action = Intent.ACTION_SEARCH;
+ }
+ i.setAction(action);
+ i.putExtra(SearchManager.QUERY, text);
+ if (extra != null) {
+ i.putExtra(SearchManager.EXTRA_DATA_KEY, extra);
+ }
+ if (source != null) {
+ Bundle appData = new Bundle();
+ appData.putString(com.android.common.Search.SOURCE, source);
+ i.putExtra(SearchManager.APP_DATA, appData);
+ }
+ mUiController.handleNewIntent(i);
+ setDisplayTitle(text);
+ }
+
+ @Override
+ public void onDismiss() {
+ final Tab currentTab = mBaseUi.getActiveTab();
+ mBaseUi.hideTitleBar();
+ post(new Runnable() {
+ public void run() {
+ clearFocus();
+ if ((currentTab != null) && !mInVoiceMode) {
+ setDisplayTitle(currentTab.getUrl());
+ }
+ }
+ });
+ }
+
+ /**
+ * callback from the suggestion dropdown
+ * copy text to input field and stay in edit mode
+ */
+ @Override
+ public void onCopySuggestion(String text) {
+ mUrlInput.setText(text, true);
+ if (text != null) {
+ mUrlInput.setSelection(text.length());
+ }
+ }
+
+ public void setCurrentUrlIsBookmark(boolean isBookmark) {
+ }
+
}
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
index 0dcece6..d5a6f97 100644
--- a/src/com/android/browser/TitleBarXLarge.java
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -16,26 +16,26 @@
package com.android.browser;
-import com.android.browser.UrlInputView.UrlInputListener;
import com.android.browser.search.SearchEngine;
import android.app.Activity;
-import android.app.SearchManager;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.speech.RecognizerResultsIntent;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
import android.webkit.WebView;
+import android.widget.AbsoluteLayout;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -45,12 +45,9 @@
* tabbed title bar for xlarge screen browser
*/
public class TitleBarXLarge extends TitleBarBase
- implements UrlInputListener, OnClickListener, OnFocusChangeListener,
+ implements OnClickListener, OnFocusChangeListener,
TextWatcher {
- private static final int PROGRESS_MAX = 100;
-
- private UiController mUiController;
private XLargeUi mUi;
private Drawable mStopDrawable;
@@ -60,29 +57,24 @@
private ImageButton mBackButton;
private ImageButton mForwardButton;
private ImageView mStar;
- private ImageView mWebIcon;
- private View mSearchButton;
+ private ImageView mUrlIcon;
+ private ImageView mSearchButton;
private View mUrlContainer;
private View mGoButton;
private ImageView mStopButton;
private View mAllButton;
private View mClearButton;
- private View mVoiceSearch;
- private View mVoiceSearchIndicator;
+ private ImageView mVoiceSearch;
private PageProgressView mProgressView;
- private UrlInputView mUrlInput;
private Drawable mFocusDrawable;
private Drawable mUnfocusDrawable;
- private boolean mInVoiceMode;
private boolean mInLoad;
- private boolean mEditable;
private boolean mUseQuickControls;
public TitleBarXLarge(Activity activity, UiController controller,
XLargeUi ui) {
- super(activity);
- mUiController = controller;
+ super(activity, controller, ui);
mUi = ui;
Resources resources = activity.getResources();
mStopDrawable = resources.getDrawable(R.drawable.ic_stop_holo_dark);
@@ -95,6 +87,18 @@
mInVoiceMode = false;
}
+ @Override
+ void setTitleGravity(int gravity) {
+ if (mUseQuickControls) {
+ FrameLayout.LayoutParams lp =
+ (FrameLayout.LayoutParams) getLayoutParams();
+ lp.gravity = gravity;
+ setLayoutParams(lp);
+ } else {
+ super.setTitleGravity(gravity);
+ }
+ }
+
private void initLayout(Context context) {
LayoutInflater factory = LayoutInflater.from(context);
factory.inflate(R.layout.url_bar, this);
@@ -106,17 +110,16 @@
// back/forward. Probably should be done inside onPageStarted.
mBackButton = (ImageButton) findViewById(R.id.back);
mForwardButton = (ImageButton) findViewById(R.id.forward);
- mWebIcon = (ImageView) findViewById(R.id.web_icon);
+ mUrlIcon = (ImageView) findViewById(R.id.url_icon);
mStar = (ImageView) findViewById(R.id.star);
mStopButton = (ImageView) findViewById(R.id.stop);
- mSearchButton = findViewById(R.id.search);
+ mSearchButton = (ImageView) findViewById(R.id.search);
mLockIcon = (ImageView) findViewById(R.id.lock);
mGoButton = findViewById(R.id.go);
mClearButton = findViewById(R.id.clear);
- mVoiceSearch = findViewById(R.id.voicesearch);
+ mVoiceSearch = (ImageView) findViewById(R.id.voicesearch);
mProgressView = (PageProgressView) findViewById(R.id.progress);
mUrlContainer = findViewById(R.id.urlbar_focused);
- mVoiceSearchIndicator = findViewById(R.id.voice_icon);
mBackButton.setOnClickListener(this);
mForwardButton.setOnClickListener(this);
mStar.setOnClickListener(this);
@@ -126,14 +129,13 @@
mGoButton.setOnClickListener(this);
mClearButton.setOnClickListener(this);
mVoiceSearch.setOnClickListener(this);
- mUrlContainer.setOnClickListener(this);
mUrlInput.setUrlInputListener(this);
mUrlInput.setContainer(mUrlContainer);
mUrlInput.setController(mUiController);
mUrlInput.setOnFocusChangeListener(this);
mUrlInput.setSelectAllOnFocus(true);
mUrlInput.addTextChangedListener(this);
- setEditMode(false);
+ setFocusState(false);
}
void updateNavigationState(Tab tab) {
@@ -148,30 +150,26 @@
}
}
- public void setEditable(boolean editable) {
- mEditable = editable;
- mUrlInput.setFocusable(mEditable);
- if (!mEditable) {
- mUrlInput.setOnClickListener(this);
+ private ViewGroup.LayoutParams makeLayoutParams() {
+ if (mUseQuickControls) {
+ return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
} else {
- mUrlContainer.setOnClickListener(null);
+ return new AbsoluteLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT,
+ 0, 0);
}
}
+ @Override
+ public int getEmbeddedHeight() {
+ return mContainer.getHeight();
+ }
+
void setUseQuickControls(boolean useQuickControls) {
mUseQuickControls = useQuickControls;
mUrlInput.setUseQuickControls(mUseQuickControls);
- if (mUseQuickControls) {
- mBackButton.setVisibility(View.GONE);
- mForwardButton.setVisibility(View.GONE);
- mStopButton.setVisibility(View.GONE);
- mAllButton.setVisibility(View.GONE);
- } else {
- mBackButton.setVisibility(View.VISIBLE);
- mForwardButton.setVisibility(View.VISIBLE);
- mStopButton.setVisibility(View.VISIBLE);
- mAllButton.setVisibility(View.VISIBLE);
- }
+ setLayoutParams(makeLayoutParams());
}
void setShowProgressOnly(boolean progress) {
@@ -184,30 +182,37 @@
@Override
public void onFocusChange(View view, boolean hasFocus) {
- if (!mEditable && hasFocus) {
- mUi.editUrl(false);
- } else {
- if (hasFocus) {
- setEditMode(hasFocus);
- } else {
- mUrlInput.stopEditing();
+ // if losing focus and not in touch mode, leave as is
+ if (hasFocus || view.isInTouchMode() || mUrlInput.needsUpdate()) {
+ setFocusState(hasFocus);
+ mUrlContainer.setBackgroundDrawable(hasFocus
+ ? mFocusDrawable : mUnfocusDrawable);
+ }
+ if (hasFocus) {
+ mUrlInput.forceIme();
+ if (mInVoiceMode) {
+ mUrlInput.forceFilter();
+ }
+ } else if (!mUrlInput.needsUpdate()) {
+ mUrlInput.dismissDropDown();
+ mUrlInput.hideIME();
+ if (mUseQuickControls) {
+ mUi.hideTitleBar();
}
}
- mUrlContainer.setBackgroundDrawable(hasFocus
- ? mFocusDrawable : mUnfocusDrawable);
+ mUrlInput.clearNeedsUpdate();
}
+ @Override
public void setCurrentUrlIsBookmark(boolean isBookmark) {
mStar.setActivated(isBookmark);
}
/**
* called from the Ui when the user wants to edit
- * Note: only the fake titlebar will get this callback
- * independent of which input field started the edit mode
* @param clearInput clear the input field
*/
- void onEditUrl(boolean clearInput) {
+ void startEditingUrl(boolean clearInput) {
// editing takes preference of progress
mContainer.setVisibility(View.VISIBLE);
if (mUseQuickControls) {
@@ -227,21 +232,19 @@
return mUrlInput.hasFocus();
}
+ void stopEditingUrl() {
+ mUrlInput.clearFocus();
+ }
+
@Override
public void onClick(View v) {
- if (mUrlInput == v) {
- mUi.editUrl(false);
- } else if (mUrlContainer == v) {
- if (!mUrlInput.hasFocus()) {
- mUi.editUrl(false);
- }
- } else if (mBackButton == v) {
+ if (mBackButton == v) {
mUiController.getCurrentTopWebView().goBack();
} else if (mForwardButton == v) {
mUiController.getCurrentTopWebView().goForward();
} else if (mStar == v) {
mUiController.bookmarkCurrentPage(
- AddBookmarkPage.DEFAULT_FOLDER_ID);
+ AddBookmarkPage.DEFAULT_FOLDER_ID, true);
} else if (mAllButton == v) {
mUiController.bookmarksOrHistoryPicker(false);
} else if (mSearchButton == v) {
@@ -260,105 +263,41 @@
}
}
- int getHeightWithoutProgress() {
- return mContainer.getHeight();
- }
-
@Override
void setFavicon(Bitmap icon) { }
private void clearOrClose() {
if (TextUtils.isEmpty(mUrlInput.getText())) {
// close
- mUrlInput.stopEditing();
+ mUrlInput.clearFocus();
} else {
// clear
mUrlInput.setText("");
}
}
- // UrlInputListener implementation
-
- /**
- * callback from suggestion dropdown
- * user selected a suggestion
- */
- @Override
- public void onAction(String text, String extra, String source) {
- mUiController.getCurrentTopWebView().requestFocus();
- mUi.hideFakeTitleBar();
- Intent i = new Intent();
- String action = null;
- if (UrlInputView.VOICE.equals(source)) {
- action = RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS;
- source = null;
- } else {
- action = Intent.ACTION_SEARCH;
- }
- i.setAction(action);
- i.putExtra(SearchManager.QUERY, text);
- if (extra != null) {
- i.putExtra(SearchManager.EXTRA_DATA_KEY, extra);
- }
- if (source != null) {
- Bundle appData = new Bundle();
- appData.putString(com.android.common.Search.SOURCE, source);
- i.putExtra(SearchManager.APP_DATA, appData);
- }
- mUiController.handleNewIntent(i);
- setDisplayTitle(text);
- }
-
- @Override
- public void onDismiss() {
- final Tab currentTab = mUi.getActiveTab();
- mUi.hideFakeTitleBar();
- post(new Runnable() {
- public void run() {
- TitleBarXLarge.this.clearFocus();
- if ((currentTab != null) && !mInVoiceMode) {
- setDisplayTitle(currentTab.getUrl());
- }
- }
- });
- }
-
- /**
- * callback from the suggestion dropdown
- * copy text to input field and stay in edit mode
- */
- @Override
- public void onEdit(String text) {
- mUrlInput.setText(text, true);
- if (text != null) {
- mUrlInput.setSelection(text.length());
- }
- }
-
- void setEditMode(boolean edit) {
- if (edit) {
+ private void setFocusState(boolean focus) {
+ if (focus) {
mUrlInput.setDropDownWidth(mUrlContainer.getWidth());
mUrlInput.setDropDownHorizontalOffset(-mUrlInput.getLeft());
mSearchButton.setVisibility(View.GONE);
mStar.setVisibility(View.GONE);
mClearButton.setVisibility(View.VISIBLE);
- if (mInVoiceMode) {
- mVoiceSearchIndicator.setVisibility(View.VISIBLE);
- }
- mWebIcon.setImageResource(R.drawable.ic_search_holo_dark);
- updateSearchMode();
+ mUrlIcon.setImageResource(R.drawable.ic_search_holo_dark);
+ updateSearchMode(false);
} else {
mGoButton.setVisibility(View.GONE);
mVoiceSearch.setVisibility(View.GONE);
mStar.setVisibility(View.VISIBLE);
mClearButton.setVisibility(View.GONE);
- mVoiceSearchIndicator.setVisibility(View.GONE);
if (mUseQuickControls) {
mSearchButton.setVisibility(View.GONE);
} else {
mSearchButton.setVisibility(View.VISIBLE);
}
- mWebIcon.setImageResource(R.drawable.ic_web_holo_dark);
+ mUrlIcon.setImageResource(mInVoiceMode ?
+ R.drawable.ic_search_holo_dark
+ : R.drawable.ic_web_holo_dark);
}
}
@@ -396,8 +335,8 @@
}
}
- private void updateSearchMode() {
- setSearchMode(TextUtils.isEmpty(mUrlInput.getText()));
+ private void updateSearchMode(boolean userEdited) {
+ setSearchMode(!userEdited || TextUtils.isEmpty(mUrlInput.getText()));
}
private void setSearchMode(boolean voiceSearchEnabled) {
@@ -424,7 +363,7 @@
public void afterTextChanged(Editable s) {
if (mUrlInput.hasFocus()) {
// check if input field is empty and adjust voice search state
- updateSearchMode();
+ updateSearchMode(true);
// clear voice mode when user types
setInVoiceMode(false, null);
}
@@ -448,8 +387,7 @@
public void setInVoiceMode(boolean voicemode, List<String> voiceResults) {
mInVoiceMode = voicemode;
mUrlInput.setVoiceResults(voiceResults);
- mVoiceSearchIndicator.setVisibility(mInVoiceMode
- ? View.VISIBLE : View.GONE);
+ mUrlIcon.setImageDrawable(mSearchButton.getDrawable());
}
@Override
@@ -457,4 +395,31 @@
mUrlInput.setIncognitoMode(incognito);
}
+ @Override
+ public View focusSearch(View focused, int dir) {
+ if (FOCUS_DOWN == dir && hasFocus()) {
+ return getCurrentWebView();
+ }
+ return super.focusSearch(focused, dir);
+ }
+
+ @Override
+ public boolean dispatchKeyEventPreIme(KeyEvent evt) {
+ if (evt.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ // catch back key in order to do slightly more cleanup than usual
+ mUrlInput.clearFocus();
+ return true;
+ }
+ return super.dispatchKeyEventPreIme(evt);
+ }
+
+ private WebView getCurrentWebView() {
+ Tab t = mUi.getActiveTab();
+ if (t != null) {
+ return t.getWebView();
+ } else {
+ return null;
+ }
+ }
+
}
diff --git a/src/com/android/browser/UI.java b/src/com/android/browser/UI.java
index 8de2b19..34dcaee 100644
--- a/src/com/android/browser/UI.java
+++ b/src/com/android/browser/UI.java
@@ -118,6 +118,8 @@
void showMaxTabsWarning();
+ void editUrl(boolean clearInput);
+
boolean dispatchKey(int code, KeyEvent event);
}
diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java
index ae38cff..6075d36 100644
--- a/src/com/android/browser/UiController.java
+++ b/src/com/android/browser/UiController.java
@@ -49,10 +49,12 @@
void stopLoading();
- void bookmarkCurrentPage(long folderId);
+ void bookmarkCurrentPage(long folderId, boolean canBeAnEdit);
void bookmarksOrHistoryPicker(boolean openHistory);
+ void startSearch(String url);
+
void startVoiceSearch();
void showVoiceSearchResults(String title);
diff --git a/src/com/android/browser/UploadHandler.java b/src/com/android/browser/UploadHandler.java
index d9b387f..5947e4a 100644
--- a/src/com/android/browser/UploadHandler.java
+++ b/src/com/android/browser/UploadHandler.java
@@ -17,11 +17,13 @@
package com.android.browser;
import android.app.Activity;
+import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.webkit.ValueCallback;
+import android.widget.Toast;
import java.io.File;
import java.util.Vector;
@@ -37,6 +39,9 @@
private ValueCallback<Uri> mUploadMessage;
private String mCameraFilePath;
+ private boolean mHandled;
+ private boolean mCaughtActivityNotFoundException;
+
private Controller mController;
public UploadHandler(Controller controller) {
@@ -47,7 +52,19 @@
return mCameraFilePath;
}
+ boolean handled() {
+ return mHandled;
+ }
+
void onResult(int resultCode, Intent intent) {
+
+ if (resultCode == Activity.RESULT_CANCELED && mCaughtActivityNotFoundException) {
+ // Couldn't resolve an activity, we are going to try again so skip
+ // this result.
+ mCaughtActivityNotFoundException = false;
+ return;
+ }
+
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
: intent.getData();
@@ -69,6 +86,8 @@
}
mUploadMessage.onReceiveValue(result);
+ mHandled = true;
+ mCaughtActivityNotFoundException = false;
}
void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
@@ -82,14 +101,10 @@
final String mediaSourceValueCamcorder = "camcorder";
final String mediaSourceValueMicrophone = "microphone";
- // media source can be 'filesystem' or 'camera' or 'camcorder' or 'microphone'.
+ // According to the spec, media source can be 'filesystem' or 'camera' or 'camcorder'
+ // or 'microphone'.
String mediaSource = "";
- // We add the camera intent if there was no accept type (or '*/*' or 'image/*').
- boolean addCameraIntent = true;
- // We add the camcorder intent if there was no accept type (or '*/*' or 'video/*').
- boolean addCamcorderIntent = true;
-
if (mUploadMessage != null) {
// Already a file picker operation in progress.
return;
@@ -111,15 +126,122 @@
}
}
- // This intent will display the standard OPENABLE file picker.
+ //Ensure it is not still set from a previous upload.
+ mCameraFilePath = null;
+
+ if (mimeType.equals(imageMimeType)) {
+ if (mediaSource.equals(mediaSourceValueCamera)) {
+ // Specified 'image/*' and requested the camera, so go ahead and launch the
+ // camera directly.
+ startActivity(createCameraIntent());
+ return;
+ } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+ // Specified 'image/*' and requested the filesystem, so go ahead and launch an
+ // OPENABLE intent.
+ startActivity(createOpenableIntent(imageMimeType));
+ return;
+ } else {
+ // Specified just 'image/*', so launch an intent for both the Camera and image/*
+ // OPENABLE.
+ Intent chooser = createChooserIntent(createCameraIntent());
+ chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(imageMimeType));
+ startActivity(chooser);
+ return;
+ }
+ } else if (mimeType.equals(videoMimeType)) {
+ if (mediaSource.equals(mediaSourceValueCamcorder)) {
+ // Specified 'video/*' and requested the camcorder, so go ahead and launch the
+ // camcorder directly.
+ startActivity(createCamcorderIntent());
+ return;
+ } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+ // Specified 'video/*' and requested the filesystem, so go ahead and launch an
+ // an OPENABLE intent.
+ startActivity(createOpenableIntent(videoMimeType));
+ return;
+ } else {
+ // Specified just 'video/*', so go ahead and launch an intent for both camcorder and
+ // video/* OPENABLE.
+ Intent chooser = createChooserIntent(createCamcorderIntent());
+ chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(videoMimeType));
+ startActivity(chooser);
+ return;
+ }
+ } else if (mimeType.equals(audioMimeType)) {
+ if (mediaSource.equals(mediaSourceValueMicrophone)) {
+ // Specified 'audio/*' and requested microphone, so go ahead and launch the sound
+ // recorder.
+ startActivity(createSoundRecorderIntent());
+ return;
+ } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+ // Specified 'audio/*' and requested filesystem, so go ahead and launch an
+ // OPENABLE intent.
+ startActivity(createOpenableIntent(audioMimeType));
+ return;
+ } else {
+ // Specified just 'audio/*', so go ahead and launch an intent for both the sound
+ // recorder and audio/* OPENABLE.
+ Intent chooser = createChooserIntent(createSoundRecorderIntent());
+ chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(audioMimeType));
+ startActivity(chooser);
+ return;
+ }
+ }
+
+ // No special handling based on the accept type was necessary, so trigger the default
+ // file upload chooser.
+ startActivity(createDefaultOpenableIntent());
+ }
+
+ private void startActivity(Intent intent) {
+ try {
+ mController.getActivity().startActivityForResult(intent, Controller.FILE_SELECTED);
+ } catch (ActivityNotFoundException e) {
+ // No installed app was able to handle the intent that
+ // we sent, so fallback to the default file upload control.
+ try {
+ mCaughtActivityNotFoundException = true;
+ mController.getActivity().startActivityForResult(createDefaultOpenableIntent(),
+ Controller.FILE_SELECTED);
+ } catch (ActivityNotFoundException e2) {
+ // Nothing can return us a file, so file upload is effectively disabled.
+ Toast.makeText(mController.getActivity(), R.string.uploads_disabled,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ private Intent createDefaultOpenableIntent() {
+ // Create and return a chooser with the default OPENABLE
+ // actions including the camera, camcorder and sound
+ // recorder where available.
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
+ i.setType("*/*");
- // Create an intent to add to the standard file picker that will
- // capture an image from the camera. We'll combine this intent with
- // the standard OPENABLE picker unless the web developer specifically
- // requested the camera or gallery be opened by passing a parameter
- // in the accept type.
+ Intent chooser = createChooserIntent(createCameraIntent(), createCamcorderIntent(),
+ createSoundRecorderIntent());
+ chooser.putExtra(Intent.EXTRA_INTENT, i);
+ return chooser;
+ }
+
+ private Intent createChooserIntent(Intent... intents) {
+ Intent chooser = new Intent(Intent.ACTION_CHOOSER);
+ chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents);
+ chooser.putExtra(Intent.EXTRA_TITLE,
+ mController.getActivity().getResources()
+ .getString(R.string.choose_upload));
+ return chooser;
+ }
+
+ private Intent createOpenableIntent(String type) {
+ Intent i = new Intent(Intent.ACTION_GET_CONTENT);
+ i.addCategory(Intent.CATEGORY_OPENABLE);
+ i.setType(type);
+ return i;
+ }
+
+ private Intent createCameraIntent() {
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File externalDataDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DCIM);
@@ -129,86 +251,15 @@
mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator +
System.currentTimeMillis() + ".jpg";
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath)));
-
- Intent camcorderIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
-
- Intent soundRecIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
-
- if (mimeType.equals(imageMimeType)) {
- i.setType(imageMimeType);
- addCamcorderIntent = false;
- if (mediaSource.equals(mediaSourceValueCamera)) {
- // Specified 'image/*' and requested the camera, so go ahead and launch the camera
- // directly.
- startActivity(cameraIntent);
- return;
- } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
- // Specified filesytem as the source, so don't want to consider the camera.
- addCameraIntent = false;
- }
- } else if (mimeType.equals(videoMimeType)) {
- i.setType(videoMimeType);
- addCameraIntent = false;
- // The camcorder saves it's own file and returns it to us in the intent, so
- // we don't need to generate one here.
- mCameraFilePath = null;
-
- if (mediaSource.equals(mediaSourceValueCamcorder)) {
- // Specified 'video/*' and requested the camcorder, so go ahead and launch the
- // camcorder directly.
- startActivity(camcorderIntent);
- return;
- } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
- // Specified filesystem as the source, so don't want to consider the camcorder.
- addCamcorderIntent = false;
- }
- } else if (mimeType.equals(audioMimeType)) {
- i.setType(audioMimeType);
- addCameraIntent = false;
- addCamcorderIntent = false;
- if (mediaSource.equals(mediaSourceValueMicrophone)) {
- // Specified 'audio/*' and requested microphone, so go ahead and launch the sound
- // recorder.
- startActivity(soundRecIntent);
- return;
- }
- // On a default system, there is no single option to open an audio "gallery". Both the
- // sound recorder and music browser respond to the OPENABLE/audio/* intent unlike the
- // image/* and video/* OPENABLE intents where the image / video gallery are the only
- // respondants (and so the user is not prompted by default).
- } else {
- i.setType("*/*");
- }
-
- // Combine the chooser and the extra choices (like camera or camcorder)
- Intent chooser = new Intent(Intent.ACTION_CHOOSER);
- chooser.putExtra(Intent.EXTRA_INTENT, i);
-
- Vector<Intent> extraInitialIntents = new Vector<Intent>(0);
-
- if (addCameraIntent) {
- extraInitialIntents.add(cameraIntent);
- }
-
- if (addCamcorderIntent) {
- extraInitialIntents.add(camcorderIntent);
- }
-
- if (extraInitialIntents.size() > 0) {
- Intent[] extraIntents = new Intent[extraInitialIntents.size()];
- chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS,
- extraInitialIntents.toArray(extraIntents));
- }
-
- chooser.putExtra(Intent.EXTRA_TITLE,
- mController.getActivity().getResources()
- .getString(R.string.choose_upload));
- startActivity(chooser);
+ return cameraIntent;
}
- private void startActivity(Intent intent) {
- mController.getActivity().startActivityForResult(intent,
- Controller.FILE_SELECTED);
+ private Intent createCamcorderIntent() {
+ return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+ }
+
+ private Intent createSoundRecorderIntent() {
+ return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
}
}
diff --git a/src/com/android/browser/UrlHandler.java b/src/com/android/browser/UrlHandler.java
index f1d1c4c..03bab9b 100644
--- a/src/com/android/browser/UrlHandler.java
+++ b/src/com/android/browser/UrlHandler.java
@@ -19,15 +19,18 @@
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
import java.net.URISyntaxException;
+import java.util.List;
+import java.util.regex.Matcher;
/**
*
@@ -153,6 +156,13 @@
// security (only access to BROWSABLE activities).
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setComponent(null);
+ // Make sure webkit can handle it internally before checking for specialized
+ // handlers. If webkit can't handle it internally, we need to call
+ // startActivityIfNeeded
+ Matcher m = UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url);
+ if (m.matches() && !isSpecializedHandlerAvailable(intent)) {
+ return false;
+ }
try {
if (mActivity.startActivityIfNeeded(intent, -1)) {
// before leaving BrowserActivity, close the empty child tab.
@@ -170,6 +180,33 @@
return false;
}
+ /**
+ * Search for intent handlers that are specific to this URL
+ * aka, specialized apps like google maps or youtube
+ */
+ private boolean isSpecializedHandlerAvailable(Intent intent) {
+ PackageManager pm = mActivity.getPackageManager();
+ List<ResolveInfo> handlers = pm.queryIntentActivities(intent,
+ PackageManager.GET_RESOLVED_FILTER);
+ if (handlers == null || handlers.size() == 0) {
+ return false;
+ }
+ for (ResolveInfo resolveInfo : handlers) {
+ IntentFilter filter = resolveInfo.filter;
+ if (filter == null) {
+ // No intent filter matches this intent?
+ // Error on the side of staying in the browser, ignore
+ continue;
+ }
+ if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) {
+ // Generic handler, skip
+ continue;
+ }
+ return true;
+ }
+ return false;
+ }
+
// In case a physical keyboard is attached, handle clicks with the menu key
// depressed by opening in a new tab
boolean handleMenuClick(Tab tab, String url) {
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
index 23e412d..c9f4c6a 100644
--- a/src/com/android/browser/UrlInputView.java
+++ b/src/com/android/browser/UrlInputView.java
@@ -29,7 +29,6 @@
import android.util.Patterns;
import android.view.KeyEvent;
import android.view.View;
-import android.view.View.OnFocusChangeListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
@@ -44,7 +43,7 @@
* handling suggestions
*/
public class UrlInputView extends AutoCompleteTextView
- implements OnFocusChangeListener, OnEditorActionListener,
+ implements OnEditorActionListener,
CompletionListener, OnItemClickListener {
@@ -55,12 +54,10 @@
private UrlInputListener mListener;
private InputMethodManager mInputManager;
private SuggestionsAdapter mAdapter;
- private OnFocusChangeListener mWrappedFocusListener;
private View mContainer;
private boolean mLandscape;
- private boolean mInVoiceMode;
private boolean mIncognitoMode;
- private int mVOffset;
+ private boolean mNeedsUpdate;
public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
@@ -80,14 +77,27 @@
private void init(Context ctx) {
mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
setOnEditorActionListener(this);
- super.setOnFocusChangeListener(this);
mAdapter = new SuggestionsAdapter(ctx, this);
setAdapter(mAdapter);
setSelectAllOnFocus(true);
onConfigurationChanged(ctx.getResources().getConfiguration());
setThreshold(1);
setOnItemClickListener(this);
- mVOffset = 0;
+ mNeedsUpdate = false;
+ }
+
+ /**
+ * check if focus change requires a title bar update
+ */
+ boolean needsUpdate() {
+ return mNeedsUpdate;
+ }
+
+ /**
+ * clear the focus change needs title bar update flag
+ */
+ void clearNeedsUpdate() {
+ mNeedsUpdate = false;
}
void setController(UiController controller) {
@@ -97,9 +107,6 @@
}
void setUseQuickControls(boolean useQuickControls) {
- mVOffset = (useQuickControls
- ? (int) getResources().getDimension(R.dimen.dropdown_offset)
- : 0);
mAdapter.setReverseResults(useQuickControls);
}
@@ -107,9 +114,12 @@
mContainer = container;
}
+ public void setUrlInputListener(UrlInputListener listener) {
+ mListener = listener;
+ }
+
void setVoiceResults(List<String> voiceResults) {
mAdapter.setVoiceResults(voiceResults);
- mInVoiceMode = (voiceResults != null);
}
@Override
@@ -137,19 +147,13 @@
}
private void setupDropDown() {
- int width = mContainer.getWidth();
+ int width = mContainer != null ? mContainer.getWidth() : getWidth();
if (width != getDropDownWidth()) {
setDropDownWidth(width);
}
if (getLeft() != -getDropDownHorizontalOffset()) {
setDropDownHorizontalOffset(-getLeft());
}
- setDropDownVerticalOffset(mVOffset);
- }
-
- @Override
- public void setOnFocusChangeListener(OnFocusChangeListener focusListener) {
- mWrappedFocusListener = focusListener;
}
@Override
@@ -158,34 +162,23 @@
return true;
}
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- forceIme();
- if (mInVoiceMode) {
- performFiltering(getText().toString(), 0);
- showDropDown();
- }
- }
- if (mWrappedFocusListener != null) {
- mWrappedFocusListener.onFocusChange(v, hasFocus);
- }
+ void forceFilter() {
+ performFiltering(getText().toString(), 0);
+ showDropDown();
}
- void stopEditing() {
- finishInput(null, null, null);
- }
-
- public void setUrlInputListener(UrlInputListener listener) {
- mListener = listener;
- }
-
- public void forceIme() {
+ void forceIme() {
+ mInputManager.focusIn(this);
mInputManager.showSoftInput(this, 0);
}
+ void hideIME() {
+ mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+
private void finishInput(String url, String extra, String source) {
- this.dismissDropDown();
+ mNeedsUpdate = true;
+ dismissDropDown();
mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
if (TextUtils.isEmpty(url)) {
mListener.onDismiss();
@@ -221,7 +214,7 @@
@Override
public void onSearch(String search) {
- mListener.onEdit(search);
+ mListener.onCopySuggestion(search);
}
@Override
@@ -231,16 +224,6 @@
}
@Override
- public boolean onKeyPreIme(int keyCode, KeyEvent evt) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- // catch back key in order to do slightly more cleanup than usual
- finishInput(null, null, null);
- return true;
- }
- return super.onKeyPreIme(keyCode, evt);
- }
-
- @Override
public void onItemClick(
AdapterView<?> parent, View view, int position, long id) {
SuggestItem item = mAdapter.getItem(position);
@@ -254,7 +237,7 @@
public void onAction(String text, String extra, String source);
- public void onEdit(String text);
+ public void onCopySuggestion(String text);
}
@@ -263,4 +246,13 @@
mAdapter.setIncognitoMode(mIncognitoMode);
}
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_ESCAPE && !isInTouchMode()) {
+ finishInput(null, null, null);
+ return true;
+ }
+ return super.onKeyDown(keyCode, evt);
+ }
+
}
diff --git a/src/com/android/browser/WallpaperHandler.java b/src/com/android/browser/WallpaperHandler.java
index 0c88a50..2cb223a 100644
--- a/src/com/android/browser/WallpaperHandler.java
+++ b/src/com/android/browser/WallpaperHandler.java
@@ -21,12 +21,14 @@
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
+import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -41,6 +43,9 @@
private static final String LOGTAG = "WallpaperHandler";
+ // This should be large enough for BitmapFactory to decode the header so
+ // that we can mark and reset the input stream to avoid duplicate network i/o
+ private static final int BUFFER_SIZE = 128 * 1024;
private Context mContext;
private URL mUrl;
@@ -82,8 +87,9 @@
@Override
public void run() {
- Drawable oldWallpaper =
- WallpaperManager.getInstance(mContext).getDrawable();
+ WallpaperManager wm = WallpaperManager.getInstance(mContext);
+ Drawable oldWallpaper = wm.getDrawable();
+ InputStream inputstream = null;
try {
// TODO: This will cause the resource to be downloaded again, when
// we should in most cases be able to grab it from the cache. To fix
@@ -91,15 +97,59 @@
// version and instead open an input stream on that. This pattern
// could also be used in the download manager where the same problem
// exists.
- InputStream inputstream = mUrl.openStream();
+ inputstream = mUrl.openStream();
if (inputstream != null) {
- WallpaperManager.getInstance(mContext).setStream(inputstream);
+ if (!inputstream.markSupported()) {
+ inputstream = new BufferedInputStream(inputstream, BUFFER_SIZE);
+ }
+ inputstream.mark(BUFFER_SIZE);
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ // We give decodeStream a wrapped input stream so it doesn't
+ // mess with our mark (currently it sets a mark of 1024)
+ BitmapFactory.decodeStream(
+ new BufferedInputStream(inputstream), null, options);
+ int maxWidth = wm.getDesiredMinimumWidth();
+ int maxHeight = wm.getDesiredMinimumHeight();
+ // Give maxWidth and maxHeight some leeway
+ maxWidth *= 1.25;
+ maxHeight *= 1.25;
+ int bmWidth = options.outWidth;
+ int bmHeight = options.outHeight;
+
+ int scale = 1;
+ while (bmWidth > maxWidth || bmHeight > maxWidth) {
+ scale <<= 1;
+ bmWidth >>= 1;
+ bmHeight >>= 1;
+ }
+ options.inJustDecodeBounds = false;
+ options.inSampleSize = scale;
+ try {
+ inputstream.reset();
+ } catch (IOException e) {
+ // BitmapFactory read more than we could buffer
+ // Re-open the stream
+ inputstream.close();
+ inputstream = mUrl.openStream();
+ }
+ Bitmap scaledWallpaper = BitmapFactory.decodeStream(inputstream,
+ null, options);
+ wm.setBitmap(scaledWallpaper);
}
} catch (IOException e) {
Log.e(LOGTAG, "Unable to set new wallpaper");
// Act as though the user canceled the operation so we try to
// restore the old wallpaper.
mCanceled = true;
+ } finally {
+ if (inputstream != null) {
+ try {
+ inputstream.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
}
if (mCanceled) {
@@ -113,7 +163,7 @@
oldWallpaper.setBounds(0, 0, width, height);
oldWallpaper.draw(canvas);
try {
- WallpaperManager.getInstance(mContext).setBitmap(bm);
+ wm.setBitmap(bm);
} catch (IOException e) {
Log.e(LOGTAG, "Unable to restore old wallpaper.");
}
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index 562705b..d3f83f9 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -18,8 +18,12 @@
import com.android.browser.ScrollWebView.ScrollListener;
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ObjectAnimator;
import android.app.ActionBar;
import android.app.Activity;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
@@ -29,7 +33,6 @@
import android.webkit.WebChromeClient.CustomViewCallback;
import android.webkit.WebView;
import android.widget.FrameLayout;
-import android.widget.FrameLayout.LayoutParams;
import java.util.List;
@@ -44,7 +47,6 @@
private TabBar mTabBar;
private TitleBarXLarge mTitleBar;
- private TitleBarXLarge mFakeTitleBar;
private boolean mUseQuickControls;
private PieControl mPieControl;
@@ -57,9 +59,6 @@
super(browser, controller);
mTitleBar = new TitleBarXLarge(mActivity, mUiController, this);
mTitleBar.setProgress(100);
- mTitleBar.setEditable(false);
- mFakeTitleBar = new TitleBarXLarge(mActivity, mUiController, this);
- mFakeTitleBar.setEditable(true);
mTabBar = new TabBar(mActivity, mUiController, this);
mActionBar = mActivity.getActionBar();
setupActionBar();
@@ -90,31 +89,28 @@
private void setUseQuickControls(boolean useQuickControls) {
mUseQuickControls = useQuickControls;
+ mTitleBar.setUseQuickControls(mUseQuickControls);
if (useQuickControls) {
checkTabCount();
mPieControl = new PieControl(mActivity, mUiController, this);
mPieControl.attachToContainer(mContentView);
- setFakeTitleBarGravity(Gravity.BOTTOM);
-
- // remove embedded title bar if present
- WebView web = mTabControl.getCurrentWebView();
- if ((web != null) && (web.getVisibleTitleHeight() > 0)) {
- web.setEmbeddedTitleBar(null);
+ Tab tab = getActiveTab();
+ if ((tab != null) && (tab.getWebView() != null)) {
+ tab.getWebView().setEmbeddedTitleBar(null);
}
+ setTitleGravity(Gravity.BOTTOM);
} else {
mActivity.getActionBar().show();
if (mPieControl != null) {
mPieControl.removeFromContainer(mContentView);
}
- setFakeTitleBarGravity(Gravity.TOP);
- // remove embedded title bar if present
+ setTitleGravity(Gravity.TOP);
WebView web = mTabControl.getCurrentWebView();
- if ((web != null) && (web.getVisibleTitleHeight() == 0)) {
+ if (web != null) {
web.setEmbeddedTitleBar(mTitleBar);
}
}
mTabBar.setUseQuickControls(mUseQuickControls);
- mFakeTitleBar.setUseQuickControls(mUseQuickControls);
}
private void checkTabCount() {
@@ -130,7 +126,7 @@
@Override
public void onDestroy() {
- hideFakeTitleBar();
+ hideTitleBar();
}
// webview factory
@@ -142,7 +138,9 @@
android.R.attr.webViewStyle, privateBrowsing);
initWebViewSettings(w);
w.setScrollListener(this);
- w.getSettings().setDisplayZoomControls(false);
+ boolean supportsMultiTouch = mActivity.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
+ w.getSettings().setDisplayZoomControls(!supportsMultiTouch);
w.setExpandedTileBounds(true); // smoother scrolling
return w;
}
@@ -156,8 +154,8 @@
}
@Override
- public void onScroll(int visibleTitleHeight) {
- mTabBar.onScroll(visibleTitleHeight);
+ public void onScroll(int visibleTitleHeight, boolean userInitiated) {
+ mTabBar.onScroll(visibleTitleHeight, userInitiated);
}
void stopWebViewScrolling() {
@@ -170,36 +168,27 @@
// WebView callbacks
@Override
- public void bookmarkedStatusHasChanged(Tab tab) {
- if (tab.inForeground()) {
- boolean isBookmark = tab.isBookmarkedSite();
- mTitleBar.setCurrentUrlIsBookmark(isBookmark);
- mFakeTitleBar.setCurrentUrlIsBookmark(isBookmark);
- }
- }
-
- @Override
public void onProgressChanged(Tab tab) {
int progress = tab.getLoadProgress();
mTabBar.onProgress(tab, progress);
if (tab.inForeground()) {
- mFakeTitleBar.setProgress(progress);
+ mTitleBar.setProgress(progress);
if (progress == 100) {
- if (!mFakeTitleBar.isEditingUrl()) {
- hideFakeTitleBar();
+ if (!mTitleBar.isEditingUrl()) {
+ hideTitleBar();
if (mUseQuickControls) {
- mFakeTitleBar.setShowProgressOnly(false);
- setFakeTitleBarGravity(Gravity.BOTTOM);
+ mTitleBar.setShowProgressOnly(false);
+ setTitleGravity(Gravity.BOTTOM);
}
}
} else {
- if (mUseQuickControls && !mFakeTitleBar.isEditingUrl()) {
- mFakeTitleBar.setShowProgressOnly(true);
- if (!isFakeTitleBarShowing()) {
- setFakeTitleBarGravity(Gravity.TOP);
+ if (!isTitleBarShowing()) {
+ if (mUseQuickControls && !mTitleBar.isEditingUrl()) {
+ mTitleBar.setShowProgressOnly(true);
+ setTitleGravity(Gravity.TOP);
}
+ showTitleBar();
}
- showFakeTitleBar();
}
}
}
@@ -219,8 +208,57 @@
}
@Override
- public void setActiveTab(Tab tab) {
- super.setActiveTab(tab);
+ public void setActiveTab(final Tab tab) {
+ if ((tab != mActiveTab) && (mActiveTab != null)) {
+ // animate between the two
+ final ScrollWebView fromWV = (ScrollWebView) mActiveTab.getWebView();
+ fromWV.setDrawCached(true);
+ fromWV.setEmbeddedTitleBar(null);
+ final ScrollWebView toWV = (ScrollWebView) tab.getWebView();
+ if (!mUseQuickControls) {
+ if (mTitleBar.getParent() == null) {
+ toWV.setEmbeddedTitleBar(mTitleBar);
+ }
+ }
+ toWV.setDrawCached(true);
+ attachTabToContentView(tab);
+ super.setActiveTab(tab, false);
+ ObjectAnimator transition = ObjectAnimator.ofFloat(
+ toWV, "alpha", 0f, 1f);
+ transition.setDuration(mActivity.getResources()
+ .getInteger(R.integer.tabFadeDuration));
+ transition.addListener(new AnimatorListener() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ fromWV.setDrawCached(false);
+ toWV.setDrawCached(false);
+ setActiveTab(tab, false);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ fromWV.setDrawCached(false);
+ toWV.setDrawCached(false);
+ setActiveTab(tab, false);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+ });
+ transition.start();
+ } else {
+ super.setActiveTab(tab, true);
+ setActiveTab(tab, true);
+ }
+ }
+
+ @Override
+ void setActiveTab(Tab tab, boolean needsAttaching) {
ScrollWebView view = (ScrollWebView) tab.getWebView();
// TabControl.setCurrentTab has been called before this,
// so the tab is guaranteed to have a webview
@@ -234,7 +272,10 @@
view.setScrollListener(null);
mTabBar.showTitleBarIndicator(false);
} else {
- view.setEmbeddedTitleBar(mTitleBar);
+ // check if title bar is already attached by animation
+ if (mTitleBar.getParent() == null) {
+ view.setEmbeddedTitleBar(mTitleBar);
+ }
view.setScrollListener(this);
}
mTabBar.onSetActiveTab(tab);
@@ -270,69 +311,79 @@
return 0;
}
- void editUrl(boolean clearInput) {
+ @Override
+ public void editUrl(boolean clearInput) {
if (mUiController.isInCustomActionMode()) {
mUiController.endActionMode();
}
- showFakeTitleBar();
- mFakeTitleBar.onEditUrl(clearInput);
+ showTitleBar();
+ mTitleBar.startEditingUrl(clearInput);
}
- void setFakeTitleBarGravity(int gravity) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)
- mFakeTitleBar.getLayoutParams();
- if (lp == null) {
- lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.WRAP_CONTENT);
+ void showTitleBarAndEdit() {
+ mTitleBar.setShowProgressOnly(false);
+ showTitleBar();
+ mTitleBar.startEditingUrl(false);
+ }
+
+ void stopEditingUrl() {
+ mTitleBar.stopEditingUrl();
+ }
+
+ @Override
+ protected void showTitleBar() {
+ if (canShowTitleBar()) {
+ if (mUseQuickControls) {
+ mContentView.addView(mTitleBar);
+ } else {
+ setTitleGravity(Gravity.TOP);
+ }
+ super.showTitleBar();
+ mTabBar.onShowTitleBar();
}
- lp.gravity = gravity;
- mFakeTitleBar.setLayoutParams(lp);
- }
-
- void showFakeTitleBarAndEdit() {
- mFakeTitleBar.setShowProgressOnly(false);
- setFakeTitleBarGravity(Gravity.BOTTOM);
- showFakeTitleBar();
- mFakeTitleBar.onEditUrl(false);
}
@Override
- protected void attachFakeTitleBar(WebView mainView) {
- mContentView.addView(mFakeTitleBar);
- mTabBar.onShowTitleBar();
- }
-
- @Override
- protected void hideFakeTitleBar() {
- if (isFakeTitleBarShowing()) {
- mFakeTitleBar.setEditMode(false);
+ protected void hideTitleBar() {
+ if (isTitleBarShowing()) {
mTabBar.onHideTitleBar();
- mContentView.removeView(mFakeTitleBar);
+ if (mUseQuickControls) {
+ mContentView.removeView(mTitleBar);
+ } else {
+ setTitleGravity(Gravity.NO_GRAVITY);
+ }
+ super.hideTitleBar();
}
}
- @Override
- protected boolean isFakeTitleBarShowing() {
- return (mFakeTitleBar.getParent() != null);
+ public boolean isEditingUrl() {
+ return mTitleBar.isEditingUrl();
}
@Override
- protected TitleBarBase getFakeTitleBar() {
- return mFakeTitleBar;
- }
-
- @Override
- protected TitleBarBase getEmbeddedTitleBar() {
+ protected TitleBarBase getTitleBar() {
return mTitleBar;
}
+ @Override
+ protected void setTitleGravity(int gravity) {
+ if (mUseQuickControls) {
+ FrameLayout.LayoutParams lp =
+ (FrameLayout.LayoutParams) mTitleBar.getLayoutParams();
+ lp.gravity = gravity;
+ mTitleBar.setLayoutParams(lp);
+ } else {
+ super.setTitleGravity(gravity);
+ }
+ }
+
// action mode callbacks
@Override
public void onActionModeStarted(ActionMode mode) {
- if (!mFakeTitleBar.isEditingUrl()) {
+ if (!mTitleBar.isEditingUrl()) {
// hide the fake title bar when CAB is shown
- hideFakeTitleBar();
+ hideTitleBar();
}
}
@@ -342,18 +393,17 @@
if (inLoad) {
// the titlebar was removed when the CAB was shown
// if the page is loading, show it again
- mFakeTitleBar.setShowProgressOnly(true);
- if (!isFakeTitleBarShowing()) {
- setFakeTitleBarGravity(Gravity.TOP);
+ mTitleBar.setShowProgressOnly(true);
+ if (!isTitleBarShowing()) {
+ setTitleGravity(Gravity.TOP);
}
- showFakeTitleBar();
+ showTitleBar();
}
}
@Override
protected void updateNavigationState(Tab tab) {
mTitleBar.updateNavigationState(tab);
- mFakeTitleBar.updateNavigationState(tab);
}
@Override
@@ -375,10 +425,8 @@
if (getActiveTab() != null) {
vsresults = getActiveTab().getVoiceSearchResults();
}
- mTitleBar.setInVoiceMode(true, null);
+ mTitleBar.setInVoiceMode(true, vsresults);
mTitleBar.setDisplayTitle(title);
- mFakeTitleBar.setInVoiceMode(true, vsresults);
- mFakeTitleBar.setDisplayTitle(title);
}
@Override
@@ -386,8 +434,6 @@
mTitleBar.setInVoiceMode(false, null);
String url = tab.getUrl();
mTitleBar.setDisplayTitle(url);
- mFakeTitleBar.setInVoiceMode(false, null);
- mFakeTitleBar.setDisplayTitle(url);
}
@Override
@@ -409,21 +455,32 @@
@Override
public boolean dispatchKey(int code, KeyEvent event) {
WebView web = getActiveTab().getWebView();
- switch (code) {
- case KeyEvent.KEYCODE_TAB:
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if ((web != null) && web.hasFocus()) {
- editUrl(true);
- return true;
- }
- }
- boolean ctrl = event.hasModifiers(KeyEvent.META_CTRL_ON);
- if (!ctrl && event.isPrintingKey() && !mFakeTitleBar.isEditingUrl()) {
- editUrl(true);
- return mContentView.dispatchKeyEvent(event);
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+
+ switch (code) {
+ case KeyEvent.KEYCODE_TAB:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if ((web != null) && web.hasFocus() && !mTitleBar.hasFocus()) {
+ editUrl(false);
+ return true;
+ }
+ }
+ boolean ctrl = event.hasModifiers(KeyEvent.META_CTRL_ON);
+ if (!ctrl && isTypingKey(event) && !mTitleBar.isEditingUrl()) {
+ editUrl(true);
+ return mContentView.dispatchKeyEvent(event);
+ }
}
return false;
}
+ private boolean isTypingKey(KeyEvent evt) {
+ return evt.getUnicodeChar() > 0;
+ }
+
+ TabBar getTabBar() {
+ return mTabBar;
+ }
+
}
diff --git a/src/com/android/browser/addbookmark/FolderSpinnerAdapter.java b/src/com/android/browser/addbookmark/FolderSpinnerAdapter.java
index 0712469..261aa62 100644
--- a/src/com/android/browser/addbookmark/FolderSpinnerAdapter.java
+++ b/src/com/android/browser/addbookmark/FolderSpinnerAdapter.java
@@ -35,15 +35,27 @@
*/
public class FolderSpinnerAdapter implements SpinnerAdapter {
private boolean mIncludeHomeScreen;
+ private boolean mIncludesRecentFolder;
+ private long mRecentFolderId;
+ private String mRecentFolderName;
public static final int HOME_SCREEN = 0;
public static final int ROOT_FOLDER = 1;
public static final int OTHER_FOLDER = 2;
+ public static final int RECENT_FOLDER = 3;
public FolderSpinnerAdapter(boolean includeHomeScreen) {
mIncludeHomeScreen = includeHomeScreen;
}
+ public void addRecentFolder(long folderId, String folderName) {
+ mIncludesRecentFolder = true;
+ mRecentFolderId = folderId;
+ mRecentFolderName = folderName;
+ }
+
+ public long recentFolderId() { return mRecentFolderId; }
+
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
int labelResource;
@@ -60,6 +72,8 @@
labelResource = R.string.add_to_bookmarks_menu_option;
drawableResource = R.drawable.ic_bookmarks_holo_dark;
break;
+ case RECENT_FOLDER:
+ // Fall through and use the same icon resource
case OTHER_FOLDER:
labelResource = R.string.add_to_other_folder_menu_option;
drawableResource = R.drawable.ic_folder_holo_dark;
@@ -73,7 +87,11 @@
Context context = parent.getContext();
LayoutInflater factory = LayoutInflater.from(context);
TextView textView = (TextView) factory.inflate(R.layout.add_to_option, null);
- textView.setText(labelResource);
+ if (position == RECENT_FOLDER) {
+ textView.setText(mRecentFolderName);
+ } else {
+ textView.setText(labelResource);
+ }
Drawable drawable = context.getResources().getDrawable(drawableResource);
textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null,
null, null);
@@ -90,7 +108,10 @@
@Override
public int getCount() {
- return mIncludeHomeScreen ? 3 : 2;
+ int count = 2;
+ if (mIncludeHomeScreen) count++;
+ if (mIncludesRecentFolder) count++;
+ return count;
}
@Override
diff --git a/src/com/android/browser/preferences/AdvancedPreferencesFragment.java b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
index 835778a..e2e45f5 100644
--- a/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
+++ b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
@@ -16,9 +16,11 @@
package com.android.browser.preferences;
+import com.android.browser.BrowserActivity;
import com.android.browser.BrowserSettings;
import com.android.browser.R;
+import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.ListPreference;
@@ -62,6 +64,9 @@
e = findPreference(BrowserSettings.PREF_DEFAULT_TEXT_ENCODING);
e.setOnPreferenceChangeListener(this);
+ e = findPreference(BrowserSettings.PREF_EXTRAS_RESET_DEFAULTS);
+ e.setOnPreferenceChangeListener(this);
+
e = findPreference(BrowserSettings.PREF_PLUGIN_STATE);
e.setOnPreferenceChangeListener(this);
updatePluginSummary((ListPreference) e);
@@ -121,7 +126,8 @@
} else if (pref.getKey().equals(BrowserSettings.PREF_EXTRAS_RESET_DEFAULTS)) {
Boolean value = (Boolean) objValue;
if (value.booleanValue() == true) {
- getActivity().finish();
+ startActivity(new Intent(BrowserActivity.ACTION_RESTART, null,
+ getActivity(), BrowserActivity.class));
return true;
}
} else if (pref.getKey().equals(BrowserSettings.PREF_PLUGIN_STATE)) {
diff --git a/src/com/android/browser/preferences/GeneralPreferencesFragment.java b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
index b6228dc..9c763e9 100644
--- a/src/com/android/browser/preferences/GeneralPreferencesFragment.java
+++ b/src/com/android/browser/preferences/GeneralPreferencesFragment.java
@@ -21,9 +21,12 @@
import com.android.browser.BrowserPreferencesPage;
import com.android.browser.BrowserSettings;
import com.android.browser.R;
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
import android.accounts.Account;
import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -125,11 +128,46 @@
if (BrowserBookmarksPage.PREF_ACCOUNT_NAME.equals(key)
|| BrowserBookmarksPage.PREF_ACCOUNT_TYPE.equals(key)) {
refreshUi();
+ BookmarkThumbnailWidgetProvider.refreshWidgets(getActivity(), true);
}
}
};
+ private AccountManagerCallback<Bundle> mCallback =
+ new AccountManagerCallback<Bundle>() {
+
+ @Override
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ Bundle bundle = future.getResult();
+ String name = bundle.getString(AccountManager.KEY_ACCOUNT_NAME);
+ String type = bundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
+ Account account = new Account(name, type);
+ Fragment frag = new ImportWizardDialog();
+ Bundle extras = mChromeSync.getExtras();
+ extras.putParcelableArray("accounts", new Account[] { account });
+ frag.setArguments(extras);
+ getFragmentManager().beginTransaction()
+ .add(frag, null)
+ .commit();
+ } catch (Exception ex) {
+ // Canceled or failed to login, doesn't matter to us
+ }
+ }
+ };
+
+ OnPreferenceClickListener mAddAccount = new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ AccountManager am = AccountManager.get(getActivity());
+ am.addAccount("com.google", null, null, null, getActivity(),
+ mCallback, null);
+ return true;
+ }
+ };
+
private class GetAccountsTask extends AsyncTask<Void, Void, String> {
private Context mContext;
@@ -144,7 +182,7 @@
if (accounts == null || accounts.length == 0) {
// No Google accounts setup, don't offer Chrome sync
if (mChromeSync != null) {
- getPreferenceScreen().removePreference(mChromeSync);
+ mChromeSync.setOnPreferenceClickListener(mAddAccount);
}
} else {
// Google accounts are present.
@@ -388,7 +426,8 @@
// Re-parent the existing bookmarks to the newly create bookmarks bar folder
ops.add(ContentProviderOperation.newUpdate(Bookmarks.CONTENT_URI)
.withValueBackReference(Bookmarks.PARENT, 2)
- .withSelection(Bookmarks.PARENT + "=?",
+ .withSelection(Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
+ Bookmarks.PARENT + "=?",
new String[] { Integer.toString(1) })
.build());
diff --git a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
index 409ddb7..2b2ee3e 100644
--- a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
+++ b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
@@ -44,6 +44,7 @@
Preference e = findPreference(BrowserSettings.PREF_CLEAR_HISTORY);
e.setOnPreferenceChangeListener(this);
+ setupAutoLoginPreference();
}
@Override
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index a51635c..94b1651 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -16,9 +16,12 @@
package com.android.browser.provider;
+import com.google.common.annotations.VisibleForTesting;
+
import com.android.browser.BookmarkUtils;
import com.android.browser.BrowserBookmarksPage;
import com.android.browser.R;
+import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
import com.android.common.content.SyncStateContentProviderHelper;
import android.accounts.Account;
@@ -33,8 +36,10 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.AbstractCursor;
+import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DatabaseUtils;
+import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
@@ -60,11 +65,12 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
import java.util.HashMap;
public class BrowserProvider2 extends SQLiteContentProvider {
- static final String LEGACY_AUTHORITY = "browser";
+ public static final String LEGACY_AUTHORITY = "browser";
static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder()
.authority(LEGACY_AUTHORITY).scheme("content").build();
@@ -106,6 +112,7 @@
static final int BOOKMARKS_FOLDER = 1002;
static final int BOOKMARKS_FOLDER_ID = 1003;
static final int BOOKMARKS_SUGGESTIONS = 1004;
+ static final int BOOKMARKS_DEFAULT_FOLDER_ID = 1005;
static final int HISTORY = 2000;
static final int HISTORY_ID = 2001;
@@ -159,6 +166,7 @@
matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID);
matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER);
matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
+ matcher.addURI(authority, "bookmarks/folder/id", BOOKMARKS_DEFAULT_FOLDER_ID);
matcher.addURI(authority,
SearchManager.SUGGEST_URI_PATH_QUERY,
BOOKMARKS_SUGGESTIONS);
@@ -315,6 +323,8 @@
DatabaseHelper mOpenHelper;
SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
+ // This is so provider tests can intercept widget updating
+ ContentObserver mWidgetObserver = null;
final class DatabaseHelper extends SQLiteOpenHelper {
static final String DATABASE_NAME = "browser2.db";
@@ -569,11 +579,17 @@
return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false);
}
- @Override
- public void notifyChange(boolean callerIsSyncAdapter) {
- ContentResolver resolver = getContext().getContentResolver();
- resolver.notifyChange(BrowserContract.AUTHORITY_URI, null, !callerIsSyncAdapter);
- resolver.notifyChange(LEGACY_AUTHORITY_URI, null, !callerIsSyncAdapter);
+ @VisibleForTesting
+ public void setWidgetObserver(ContentObserver obs) {
+ mWidgetObserver = obs;
+ }
+
+ void refreshWidgets() {
+ if (mWidgetObserver == null) {
+ BookmarkThumbnailWidgetProvider.refreshWidgets(getContext());
+ } else {
+ mWidgetObserver.dispatchChange(false);
+ }
}
@Override
@@ -758,6 +774,15 @@
return cursor;
}
+ case BOOKMARKS_DEFAULT_FOLDER_ID: {
+ String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
+ String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
+ long id = queryDefaultFolderId(accountName, accountType);
+ MatrixCursor c = new MatrixCursor(new String[] {Bookmarks._ID});
+ c.newRow().add(id);
+ return c;
+ }
+
case BOOKMARKS_SUGGESTIONS: {
return doSuggestQuery(selection, selectionArgs, limit);
}
@@ -958,6 +983,7 @@
boolean callerIsSyncAdapter) {
final int match = URI_MATCHER.match(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int deleted = 0;
switch (match) {
case BOOKMARKS_ID: {
selection = DatabaseUtils.concatenateWhere(selection,
@@ -971,9 +997,12 @@
Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
selection = (String) withAccount[0];
selectionArgs = (String[]) withAccount[1];
- int deleted = deleteBookmarks(selection, selectionArgs, callerIsSyncAdapter);
+ deleted = deleteBookmarks(selection, selectionArgs, callerIsSyncAdapter);
pruneImages();
- return deleted;
+ if (deleted > 0) {
+ refreshWidgets();
+ }
+ break;
}
case HISTORY_ID: {
@@ -984,9 +1013,9 @@
}
case HISTORY: {
filterSearchClient(selectionArgs);
- int deleted = db.delete(TABLE_HISTORY, selection, selectionArgs);
+ deleted = db.delete(TABLE_HISTORY, selection, selectionArgs);
pruneImages();
- return deleted;
+ break;
}
case SEARCHES_ID: {
@@ -996,17 +1025,20 @@
// fall through
}
case SEARCHES: {
- return db.delete(TABLE_SEARCHES, selection, selectionArgs);
+ deleted = db.delete(TABLE_SEARCHES, selection, selectionArgs);
+ break;
}
case SYNCSTATE: {
- return mSyncHelper.delete(db, selection, selectionArgs);
+ deleted = mSyncHelper.delete(db, selection, selectionArgs);
+ break;
}
case SYNCSTATE_ID: {
String selectionWithId =
(SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
+ (selection == null ? "" : " AND (" + selection + ")");
- return mSyncHelper.delete(db, selectionWithId, selectionArgs);
+ deleted = mSyncHelper.delete(db, selectionWithId, selectionArgs);
+ break;
}
case LEGACY_ID: {
selection = DatabaseUtils.concatenateWhere(
@@ -1030,7 +1062,6 @@
}
Cursor c = qb.query(db, projection, selection, selectionArgs,
null, null, null);
- int deleted = 0;
while (c.moveToNext()) {
long id = c.getLong(0);
boolean isBookmark = c.getInt(1) != 0;
@@ -1047,14 +1078,21 @@
new String[] { Long.toString(id) });
}
}
- return deleted;
+ break;
+ }
+ default: {
+ throw new UnsupportedOperationException("Unknown delete URI " + uri);
}
}
- throw new UnsupportedOperationException("Unknown delete URI " + uri);
+ if (deleted > 0) {
+ postNotifyUri(uri);
+ postNotifyUri(LEGACY_AUTHORITY_URI);
+ }
+ return deleted;
}
long queryDefaultFolderId(String accountName, String accountType) {
- if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+ if (!isNullAccount(accountName) && !isNullAccount(accountType)) {
final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor c = db.query(TABLE_BOOKMARKS, new String[] { Bookmarks._ID },
ChromeSyncColumns.SERVER_UNIQUE + " = ?" +
@@ -1104,12 +1142,20 @@
values.put(Bookmarks.DATE_MODIFIED, now);
values.put(Bookmarks.DIRTY, 1);
+ String accountType = values
+ .getAsString(Bookmarks.ACCOUNT_TYPE);
+ String accountName = values
+ .getAsString(Bookmarks.ACCOUNT_NAME);
+ boolean hasParent = values.containsKey(Bookmarks.PARENT);
+ if (hasParent) {
+ // Let's make sure it's valid
+ long parentId = values.getAsLong(Bookmarks.PARENT);
+ hasParent = isValidParent(
+ accountType, accountName, parentId);
+ }
+
// If no parent is set default to the "Bookmarks Bar" folder
- if (!values.containsKey(Bookmarks.PARENT)) {
- String accountType = values
- .getAsString(Bookmarks.ACCOUNT_TYPE);
- String accountName = values
- .getAsString(Bookmarks.ACCOUNT_NAME);
+ if (!hasParent) {
values.put(Bookmarks.PARENT,
queryDefaultFolderId(accountName, accountType));
}
@@ -1134,6 +1180,7 @@
}
id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
+ refreshWidgets();
break;
}
@@ -1179,12 +1226,35 @@
}
if (id >= 0) {
+ postNotifyUri(uri);
+ postNotifyUri(LEGACY_AUTHORITY_URI);
return ContentUris.withAppendedId(uri, id);
} else {
return null;
}
}
+ private boolean isValidParent(String accountType, String accountName,
+ long parentId) {
+ Uri uri = Bookmarks.buildFolderUri(parentId);
+ Cursor c = query(uri,
+ new String[] { Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_TYPE },
+ null, null, null);
+ try {
+ if (c.moveToFirst()) {
+ String parentName = c.getString(0);
+ String parentType = c.getString(1);
+ if (TextUtils.equals(accountName, parentName)
+ && TextUtils.equals(accountType, parentType)) {
+ return true;
+ }
+ }
+ return false;
+ } finally {
+ c.close();
+ }
+ }
+
private void filterSearchClient(String[] selectionArgs) {
if (selectionArgs != null) {
for (int i = 0; i < selectionArgs.length; i++) {
@@ -1287,6 +1357,7 @@
values.remove(BookmarkColumns.USER_ENTERED);
}
}
+ int modified = 0;
switch (match) {
case BOOKMARKS_ID: {
selection = DatabaseUtils.concatenateWhere(selection,
@@ -1299,10 +1370,12 @@
Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
selection = (String) withAccount[0];
selectionArgs = (String[]) withAccount[1];
- int updated = updateBookmarksInTransaction(values, selection, selectionArgs,
+ modified = updateBookmarksInTransaction(values, selection, selectionArgs,
callerIsSyncAdapter);
- pruneImages();
- return updated;
+ if (modified > 0) {
+ refreshWidgets();
+ }
+ break;
}
case HISTORY_ID: {
@@ -1312,14 +1385,14 @@
// fall through
}
case HISTORY: {
- int updated = updateHistoryInTransaction(values, selection, selectionArgs);
- pruneImages();
- return updated;
+ modified = updateHistoryInTransaction(values, selection, selectionArgs);
+ break;
}
case SYNCSTATE: {
- return mSyncHelper.update(mDb, values,
+ modified = mSyncHelper.update(mDb, values,
appendAccountToSelection(uri, selection), selectionArgs);
+ break;
}
case SYNCSTATE_ID: {
@@ -1327,8 +1400,9 @@
String selectionWithId =
(SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
+ (selection == null ? "" : " AND (" + selection + ")");
- return mSyncHelper.update(mDb, values,
+ modified = mSyncHelper.update(mDb, values,
selectionWithId, selectionArgs);
+ break;
}
case IMAGES: {
@@ -1336,20 +1410,107 @@
if (TextUtils.isEmpty(url)) {
throw new IllegalArgumentException("Images.URL is required");
}
+ if (!shouldUpdateImages(db, url, values)) {
+ return 0;
+ }
int count = db.update(TABLE_IMAGES, values, Images.URL + "=?",
new String[] { url });
if (count == 0) {
db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values);
count = 1;
}
+ if (getUrlCount(db, TABLE_BOOKMARKS, url) > 0) {
+ postNotifyUri(Bookmarks.CONTENT_URI);
+ refreshWidgets();
+ }
+ if (getUrlCount(db, TABLE_HISTORY, url) > 0) {
+ postNotifyUri(History.CONTENT_URI);
+ }
+ postNotifyUri(LEGACY_AUTHORITY_URI);
+ pruneImages();
return count;
}
case SEARCHES: {
- return db.update(TABLE_SEARCHES, values, selection, selectionArgs);
+ modified = db.update(TABLE_SEARCHES, values, selection, selectionArgs);
+ break;
+ }
+
+ default: {
+ throw new UnsupportedOperationException("Unknown update URI " + uri);
}
}
- throw new UnsupportedOperationException("Unknown update URI " + uri);
+ pruneImages();
+ if (modified > 0) {
+ postNotifyUri(uri);
+ postNotifyUri(LEGACY_AUTHORITY_URI);
+ }
+ return modified;
+ }
+
+ // We want to avoid sending out more URI notifications than we have to
+ // Thus, we check to see if the images we are about to store are already there
+ // This is used because things like a site's favion or touch icon is rarely
+ // changed, but the browser tries to update it every time the page loads.
+ // Without this, we will always send out 3 URI notifications per page load.
+ // With this, that drops to 0 or 1, depending on if the thumbnail changed.
+ private boolean shouldUpdateImages(
+ SQLiteDatabase db, String url, ContentValues values) {
+ final String[] projection = new String[] {
+ Images.FAVICON,
+ Images.THUMBNAIL,
+ Images.TOUCH_ICON,
+ };
+ Cursor cursor = db.query(TABLE_IMAGES, projection, Images.URL + "=?",
+ new String[] { url }, null, null, null);
+ byte[] nfavicon = values.getAsByteArray(Images.FAVICON);
+ byte[] nthumb = values.getAsByteArray(Images.THUMBNAIL);
+ byte[] ntouch = values.getAsByteArray(Images.TOUCH_ICON);
+ byte[] cfavicon = null;
+ byte[] cthumb = null;
+ byte[] ctouch = null;
+ try {
+ if (cursor.getCount() <= 0) {
+ return nfavicon != null || nthumb != null || ntouch != null;
+ }
+ while (cursor.moveToNext()) {
+ if (nfavicon != null) {
+ cfavicon = cursor.getBlob(0);
+ if (!Arrays.equals(nfavicon, cfavicon)) {
+ return true;
+ }
+ }
+ if (nthumb != null) {
+ cthumb = cursor.getBlob(1);
+ if (!Arrays.equals(nthumb, cthumb)) {
+ return true;
+ }
+ }
+ if (ntouch != null) {
+ ctouch = cursor.getBlob(2);
+ if (!Arrays.equals(ntouch, ctouch)) {
+ return true;
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ return false;
+ }
+
+ int getUrlCount(SQLiteDatabase db, String table, String url) {
+ Cursor c = db.query(table, new String[] { "COUNT(*)" },
+ "url = ?", new String[] { url }, null, null, null);
+ try {
+ int count = 0;
+ if (c.moveToFirst()) {
+ count = c.getInt(0);
+ }
+ return count;
+ } finally {
+ c.close();
+ }
}
/**
diff --git a/src/com/android/browser/provider/SQLiteContentProvider.java b/src/com/android/browser/provider/SQLiteContentProvider.java
index a50894a..13acd3d 100644
--- a/src/com/android/browser/provider/SQLiteContentProvider.java
+++ b/src/com/android/browser/provider/SQLiteContentProvider.java
@@ -19,26 +19,27 @@
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteTransactionListener;
import android.net.Uri;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
/**
* General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage.
*/
-public abstract class SQLiteContentProvider extends ContentProvider
- implements SQLiteTransactionListener {
+public abstract class SQLiteContentProvider extends ContentProvider {
private static final String TAG = "SQLiteContentProvider";
private SQLiteOpenHelper mOpenHelper;
- private volatile boolean mNotifyChange;
+ private Set<Uri> mChangedUris;
protected SQLiteDatabase mDb;
private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
@@ -53,6 +54,7 @@
public boolean onCreate() {
Context context = getContext();
mOpenHelper = getDatabaseHelper(context);
+ mChangedUris = new HashSet<Uri>();
return true;
}
@@ -80,10 +82,14 @@
boolean callerIsSyncAdapter);
/**
- * Called when the provider needs to notify the system of a change.
- * @param callerIsSyncAdapter true if the caller that caused the change was a sync adapter.
+ * Call this to add a URI to the list of URIs to be notified when the transaction
+ * is committed.
*/
- public abstract void notifyChange(boolean callerIsSyncAdapter);
+ protected void postNotifyUri(Uri uri) {
+ synchronized (mChangedUris) {
+ mChangedUris.add(uri);
+ }
+ }
public boolean isCallerSyncAdapter(Uri uri) {
return false;
@@ -104,12 +110,9 @@
boolean applyingBatch = applyingBatch();
if (!applyingBatch) {
mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransactionWithListener(this);
+ mDb.beginTransaction();
try {
result = insertInTransaction(uri, values, callerIsSyncAdapter);
- if (result != null) {
- mNotifyChange = true;
- }
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
@@ -118,9 +121,6 @@
onEndTransaction(callerIsSyncAdapter);
} else {
result = insertInTransaction(uri, values, callerIsSyncAdapter);
- if (result != null) {
- mNotifyChange = true;
- }
}
return result;
}
@@ -130,13 +130,10 @@
int numValues = values.length;
boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransactionWithListener(this);
+ mDb.beginTransaction();
try {
for (int i = 0; i < numValues; i++) {
Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter);
- if (result != null) {
- mNotifyChange = true;
- }
mDb.yieldIfContendedSafely();
}
mDb.setTransactionSuccessful();
@@ -155,13 +152,10 @@
boolean applyingBatch = applyingBatch();
if (!applyingBatch) {
mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransactionWithListener(this);
+ mDb.beginTransaction();
try {
count = updateInTransaction(uri, values, selection, selectionArgs,
callerIsSyncAdapter);
- if (count > 0) {
- mNotifyChange = true;
- }
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
@@ -170,9 +164,6 @@
onEndTransaction(callerIsSyncAdapter);
} else {
count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter);
- if (count > 0) {
- mNotifyChange = true;
- }
}
return count;
@@ -185,12 +176,9 @@
boolean applyingBatch = applyingBatch();
if (!applyingBatch) {
mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransactionWithListener(this);
+ mDb.beginTransaction();
try {
count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
- if (count > 0) {
- mNotifyChange = true;
- }
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
@@ -199,9 +187,6 @@
onEndTransaction(callerIsSyncAdapter);
} else {
count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
- if (count > 0) {
- mNotifyChange = true;
- }
}
return count;
}
@@ -213,7 +198,7 @@
int opCount = 0;
boolean callerIsSyncAdapter = false;
mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransactionWithListener(this);
+ mDb.beginTransaction();
try {
mApplyingBatch.set(true);
final int numOperations = operations.size();
@@ -246,31 +231,15 @@
}
}
- @Override
- public void onBegin() {
- onBeginTransaction();
- }
-
- @Override
- public void onCommit() {
- beforeTransactionCommit();
- }
-
- @Override
- public void onRollback() {
- // not used
- }
-
- protected void onBeginTransaction() {
- }
-
- protected void beforeTransactionCommit() {
- }
-
protected void onEndTransaction(boolean callerIsSyncAdapter) {
- if (mNotifyChange) {
- mNotifyChange = false;
- notifyChange(callerIsSyncAdapter);
+ Set<Uri> changed;
+ synchronized (mChangedUris) {
+ changed = new HashSet<Uri>(mChangedUris);
+ mChangedUris.clear();
+ }
+ ContentResolver resolver = getContext().getContentResolver();
+ for (Uri uri : changed) {
+ resolver.notifyChange(uri, null, !callerIsSyncAdapter);
}
}
}
diff --git a/src/com/android/browser/view/PieMenu.java b/src/com/android/browser/view/PieMenu.java
index 5185adb..080c257 100644
--- a/src/com/android/browser/view/PieMenu.java
+++ b/src/com/android/browser/view/PieMenu.java
@@ -20,15 +20,21 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -64,6 +70,17 @@
private boolean mDirty;
+ private Drawable mActiveDrawable;
+ private Drawable mInactiveDrawable;
+ private final Paint mActiveShaderPaint = new Paint();
+ private final Paint mInactiveShaderPaint = new Paint();
+ private final Matrix mActiveMatrix = new Matrix();
+ private final Matrix mInactiveMatrix = new Matrix();
+
+ private BitmapShader mActiveShader;
+ private BitmapShader mInactiveShader;
+
+
/**
* @param context
* @param attrs
@@ -111,6 +128,37 @@
setDrawingCacheEnabled(false);
mCenter = new Point(0,0);
mDirty = true;
+ mActiveShaderPaint.setStyle(Paint.Style.FILL);
+ mActiveShaderPaint.setAntiAlias(true);
+
+ mInactiveShaderPaint.setStyle(Paint.Style.FILL);
+ mInactiveShaderPaint.setAntiAlias(true);
+ mActiveDrawable = res.getDrawable(R.drawable.qc_background_selected);
+ mInactiveDrawable = res.getDrawable(R.drawable.qc_background_normal);
+
+ Bitmap activeTexture = getDrawableAsBitmap(mActiveDrawable,
+ mActiveDrawable.getIntrinsicWidth(),
+ mActiveDrawable.getIntrinsicHeight());
+ Bitmap inactiveTexture = getDrawableAsBitmap(mInactiveDrawable,
+ mInactiveDrawable.getIntrinsicWidth(),
+ mInactiveDrawable.getIntrinsicHeight());
+
+ mActiveShader = new BitmapShader(activeTexture,
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ mActiveShaderPaint.setShader(mActiveShader);
+
+ mInactiveShader = new BitmapShader(inactiveTexture,
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ mInactiveShaderPaint.setShader(mInactiveShader);
+
+ }
+
+ private static Bitmap getDrawableAsBitmap(Drawable drawable, int width, int height) {
+ Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(c);
+ return b;
}
public void setController(PieController ctl) {
@@ -268,10 +316,15 @@
tag.sweep = sweep;
tag.inner = inner;
tag.outer = outer;
-
- Paint p = item.isPressed() ? mSelectedPaint : mPaint;
- canvas.drawPath(slice, p);
int state = canvas.save();
+ int[] topLeft = new int[2];
+ getLocationInWindow(topLeft);
+ topLeft[0] = mCenter.x - outer;
+ topLeft[1] = mCenter.y - outer;
+ Paint paint = item.isPressed() ? mActiveShaderPaint : mInactiveShaderPaint;
+ drawClipped(canvas, paint, slice, topLeft, item.isPressed());
+ canvas.restoreToCount(state);
+ state = canvas.save();
if (onTheLeft()) {
canvas.scale(-1, 1);
}
@@ -287,6 +340,16 @@
return newanchor;
}
+ private void drawClipped(Canvas canvas, Paint paint, Path clipPath, int[] pos,
+ boolean selected) {
+ // TODO: We should change the matrix/shader only when needed
+ final Matrix matrix = selected ? mActiveMatrix : mInactiveMatrix;
+ matrix.setTranslate(pos[0], pos[1]);
+ (selected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix);
+ canvas.drawPath(clipPath, paint);
+ }
+
+
/**
* converts a
* @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
@@ -391,6 +454,7 @@
}
if (view != null) {
// clear up stack
+ playSoundEffect(SoundEffectConstants.CLICK);
MenuTag tag = (MenuTag) view.getTag();
int i = mStack.size() - 1;
while (i > 0) {
diff --git a/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java b/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java
index b991abd..48d7123 100644
--- a/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java
+++ b/src/com/android/browser/widget/BookmarkThumbnailWidgetProvider.java
@@ -32,12 +32,9 @@
* Widget that shows a preview of the user's bookmarks.
*/
public class BookmarkThumbnailWidgetProvider extends AppWidgetProvider {
- static final String ACTION_BOOKMARK_APPWIDGET_UPDATE =
+ public static final String ACTION_BOOKMARK_APPWIDGET_UPDATE =
"com.android.browser.BOOKMARK_APPWIDGET_UPDATE";
- /**
- * {@inheritDoc}
- */
@Override
public void onReceive(Context context, Intent intent) {
// Handle bookmark-specific updates ourselves because they might be
@@ -58,23 +55,27 @@
}
@Override
- public void onEnabled(Context context) {
- // Start the backing service
- context.startService(new Intent(context, BookmarkThumbnailWidgetService.class));
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ super.onDeleted(context, appWidgetIds);
+ for (int widgetId : appWidgetIds) {
+ BookmarkThumbnailWidgetService.deleteWidgetState(context, widgetId);
+ }
+ removeOrphanedFiles(context);
}
@Override
public void onDisabled(Context context) {
- // Stop the backing service
- context.stopService(new Intent(context, BookmarkThumbnailWidgetService.class));
+ super.onDisabled(context);
+ removeOrphanedFiles(context);
}
- @Override
- public void onDeleted(Context context, int[] appWidgetIds) {
- super.onDeleted(context, appWidgetIds);
- context.startService(new Intent(BookmarkThumbnailWidgetService.ACTION_REMOVE_FACTORIES,
- null, context, BookmarkThumbnailWidgetService.class)
- .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds));
+ /**
+ * Checks for any state files that may have not received onDeleted
+ */
+ void removeOrphanedFiles(Context context) {
+ AppWidgetManager wm = AppWidgetManager.getInstance(context);
+ int[] ids = wm.getAppWidgetIds(getComponentName(context));
+ BookmarkThumbnailWidgetService.removeOrphanedStates(context, ids);
}
private void performUpdate(Context context,
@@ -92,9 +93,9 @@
views.setOnClickPendingIntent(R.id.app_shortcut, launchBrowser);
views.setRemoteAdapter(appWidgetId, R.id.bookmarks_list, updateIntent);
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.bookmarks_list);
- Intent ic = new Intent(context, BookmarkThumbnailWidgetService.class);
+ Intent ic = new Intent(context, BookmarkWidgetProxy.class);
views.setPendingIntentTemplate(R.id.bookmarks_list,
- PendingIntent.getService(context, 0, ic,
+ PendingIntent.getBroadcast(context, 0, ic,
PendingIntent.FLAG_UPDATE_CURRENT));
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@@ -107,4 +108,29 @@
static ComponentName getComponentName(Context context) {
return new ComponentName(context, BookmarkThumbnailWidgetProvider.class);
}
+
+ public static void refreshWidgets(Context context) {
+ refreshWidgets(context, false);
+ }
+
+ public static void refreshWidgets(Context context, boolean zeroState) {
+ if (zeroState) {
+ final Context appContext = context.getApplicationContext();
+ new Thread() {
+ @Override
+ public void run() {
+ AppWidgetManager wm = AppWidgetManager.getInstance(appContext);
+ int[] ids = wm.getAppWidgetIds(getComponentName(appContext));
+ for (int id : ids) {
+ BookmarkThumbnailWidgetService.clearWidgetState(appContext, id);
+ }
+ refreshWidgets(appContext, false);
+ }
+ }.start();
+ } else {
+ context.sendBroadcast(new Intent(
+ BookmarkThumbnailWidgetProvider.ACTION_BOOKMARK_APPWIDGET_UPDATE,
+ null, context, BookmarkThumbnailWidgetProvider.class));
+ }
+ }
}
diff --git a/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java b/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java
index e525159..675cdd9 100644
--- a/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java
+++ b/src/com/android/browser/widget/BookmarkThumbnailWidgetService.java
@@ -16,8 +16,8 @@
package com.android.browser.widget;
+import com.android.browser.BookmarkUtils;
import com.android.browser.BrowserActivity;
-import com.android.browser.BrowserBookmarksPage;
import com.android.browser.R;
import android.appwidget.AppWidgetManager;
@@ -25,17 +25,14 @@
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.database.ContentObserver;
import android.database.Cursor;
+import android.database.MergeCursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.preference.PreferenceManager;
+import android.os.Binder;
import android.provider.BrowserContract;
import android.provider.BrowserContract.Bookmarks;
import android.text.TextUtils;
@@ -43,120 +40,38 @@
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Stack;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class BookmarkThumbnailWidgetService extends RemoteViewsService {
static final String TAG = "BookmarkThumbnailWidgetService";
- static final boolean USE_FOLDERS = true;
-
- static final String ACTION_REMOVE_FACTORIES
- = "com.android.browser.widget.REMOVE_FACTORIES";
static final String ACTION_CHANGE_FOLDER
= "com.android.browser.widget.CHANGE_FOLDER";
+ static final String STATE_CURRENT_FOLDER = "current_folder";
+ static final String STATE_ROOT_FOLDER = "root_folder";
+
private static final String[] PROJECTION = new String[] {
BrowserContract.Bookmarks._ID,
BrowserContract.Bookmarks.TITLE,
BrowserContract.Bookmarks.URL,
BrowserContract.Bookmarks.FAVICON,
BrowserContract.Bookmarks.IS_FOLDER,
- BrowserContract.Bookmarks.TOUCH_ICON,
BrowserContract.Bookmarks.POSITION, /* needed for order by */
- BrowserContract.Bookmarks.THUMBNAIL};
+ BrowserContract.Bookmarks.THUMBNAIL,
+ BrowserContract.Bookmarks.PARENT};
private static final int BOOKMARK_INDEX_ID = 0;
private static final int BOOKMARK_INDEX_TITLE = 1;
private static final int BOOKMARK_INDEX_URL = 2;
private static final int BOOKMARK_INDEX_FAVICON = 3;
private static final int BOOKMARK_INDEX_IS_FOLDER = 4;
- private static final int BOOKMARK_INDEX_TOUCH_ICON = 5;
- private static final int BOOKMARK_INDEX_THUMBNAIL = 7;
-
- // The service will likely be destroyed at any time, so we need to keep references to the
- // factories across services connections.
- private static final Map<Integer, BookmarkFactory> mFactories =
- new HashMap<Integer, BookmarkFactory>();
- private Handler mUiHandler;
- private BookmarksObserver mBookmarksObserver;
-
- @Override
- public void onCreate() {
- super.onCreate();
- mUiHandler = new Handler();
- mBookmarksObserver = new BookmarksObserver(mUiHandler);
- getContentResolver().registerContentObserver(
- BrowserContract.Bookmarks.CONTENT_URI, true, mBookmarksObserver);
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- String action = intent.getAction();
- if (Intent.ACTION_VIEW.equals(action)) {
- if (intent.getData() == null) {
- startActivity(new Intent(BrowserActivity.ACTION_SHOW_BROWSER, null,
- this, BrowserActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- } else {
- Intent view = new Intent(intent);
- view.setComponent(null);
- startActivity(view);
- }
- } else if (ACTION_REMOVE_FACTORIES.equals(action)) {
- int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
- if (ids != null) {
- for (int id : ids) {
- BookmarkFactory bf = mFactories.remove(id);
- if (bf != null) {
- // Workaround a known framework bug
- // onDestroy is currently never called
- bf.onDestroy();
- }
- }
- }
- } else if (ACTION_CHANGE_FOLDER.equals(action)) {
- int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
- long folderId = intent.getLongExtra(Bookmarks._ID, -1);
- BookmarkFactory fac = mFactories.get(widgetId);
- if (fac != null && folderId >= 0) {
- fac.changeFolder(folderId);
- } else {
- // This a workaround to the issue when the Browser process crashes, after which
- // mFactories is not populated (due to onBind() not being called). Calling
- // notifyDataSetChanged() will trigger a connection to be made.
- AppWidgetManager.getInstance(getApplicationContext())
- .notifyAppWidgetViewDataChanged(widgetId, R.id.bookmarks_list);
- }
- }
- return START_STICKY;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- getContentResolver().unregisterContentObserver(mBookmarksObserver);
- }
-
- private class BookmarksObserver extends ContentObserver {
- public BookmarksObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
-
- // Update all the bookmark widgets
- if (mFactories != null) {
- for (BookmarkFactory fac : mFactories.values()) {
- fac.loadData();
- }
- }
- }
- }
+ private static final int BOOKMARK_INDEX_THUMBNAIL = 6;
+ private static final int BOOKMARK_INDEX_PARENT_ID = 7;
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
@@ -164,64 +79,125 @@
if (widgetId < 0) {
Log.w(TAG, "Missing EXTRA_APPWIDGET_ID!");
return null;
- } else {
- BookmarkFactory fac = mFactories.get(widgetId);
- if (fac == null) {
- fac = new BookmarkFactory(getApplicationContext(), widgetId);
+ }
+ return new BookmarkFactory(getApplicationContext(), widgetId);
+ }
+
+ static SharedPreferences getWidgetState(Context context, int widgetId) {
+ return context.getSharedPreferences(
+ String.format("widgetState-%d", widgetId),
+ Context.MODE_PRIVATE);
+ }
+
+ static void deleteWidgetState(Context context, int widgetId) {
+ File file = context.getSharedPrefsFile(
+ String.format("widgetState-%d", widgetId));
+ if (file.exists()) {
+ if (!file.delete()) {
+ file.deleteOnExit();
}
- mFactories.put(widgetId, fac);
- return fac;
}
}
- private static class Breadcrumb {
- long mId;
- String mTitle;
- public Breadcrumb(long id, String title) {
- mId = id;
- mTitle = title;
+ static void changeFolder(Context context, Intent intent) {
+ int wid = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ long fid = intent.getLongExtra(Bookmarks._ID, -1);
+ if (wid >= 0 && fid >= 0) {
+ SharedPreferences prefs = getWidgetState(context, wid);
+ prefs.edit().putLong(STATE_CURRENT_FOLDER, fid).commit();
+ AppWidgetManager.getInstance(context)
+ .notifyAppWidgetViewDataChanged(wid, R.id.bookmarks_list);
}
}
- static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory,
- OnSharedPreferenceChangeListener {
- private List<RenderResult> mBookmarks;
+ static void clearWidgetState(Context context, int widgetId) {
+ SharedPreferences pref = getWidgetState(context, widgetId);
+ pref.edit()
+ .remove(STATE_CURRENT_FOLDER)
+ .remove(STATE_ROOT_FOLDER)
+ .commit();
+ }
+
+ /**
+ * Checks for any state files that may have not received onDeleted
+ */
+ static void removeOrphanedStates(Context context, int[] widgetIds) {
+ File prefsDirectory = context.getSharedPrefsFile("null").getParentFile();
+ File[] widgetStates = prefsDirectory.listFiles(new StateFilter(widgetIds));
+ for (File f : widgetStates) {
+ Log.w(TAG, "Found orphaned state: " + f.getName());
+ if (!f.delete()) {
+ f.deleteOnExit();
+ }
+ }
+ }
+
+ static class StateFilter implements FilenameFilter {
+
+ static final Pattern sStatePattern = Pattern.compile("widgetState-(\\d+)\\.xml");
+ HashSet<Integer> mWidgetIds;
+
+ StateFilter(int[] ids) {
+ mWidgetIds = new HashSet<Integer>();
+ for (int id : ids) {
+ mWidgetIds.add(id);
+ }
+ }
+
+ @Override
+ public boolean accept(File dir, String filename) {
+ Matcher m = sStatePattern.matcher(filename);
+ if (m.matches()) {
+ int id = Integer.parseInt(m.group(1));
+ if (!mWidgetIds.contains(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ }
+
+ static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory {
+ private Cursor mBookmarks;
private Context mContext;
private int mWidgetId;
- private String mAccountType;
- private String mAccountName;
- private Stack<Breadcrumb> mBreadcrumbs;
- private LoadBookmarksTask mLoadTask;
+ private long mCurrentFolder = -1;
+ private long mRootFolder = -1;
+ private SharedPreferences mPreferences = null;
public BookmarkFactory(Context context, int widgetId) {
- mBreadcrumbs = new Stack<Breadcrumb>();
mContext = context;
mWidgetId = widgetId;
}
- void changeFolder(long folderId) {
- if (mBookmarks == null) return;
-
- if (!mBreadcrumbs.empty() && mBreadcrumbs.peek().mId == folderId) {
- mBreadcrumbs.pop();
- loadData();
- return;
+ void syncState() {
+ if (mPreferences == null) {
+ mPreferences = getWidgetState(mContext, mWidgetId);
}
-
- for (RenderResult res : mBookmarks) {
- if (res.mId == folderId) {
- mBreadcrumbs.push(new Breadcrumb(res.mId, res.mTitle));
- loadData();
- break;
- }
+ long currentFolder = mPreferences.getLong(STATE_CURRENT_FOLDER, -1);
+ mRootFolder = mPreferences.getLong(STATE_ROOT_FOLDER, -1);
+ if (currentFolder != mCurrentFolder) {
+ resetBookmarks();
+ mCurrentFolder = currentFolder;
}
}
+ void saveState() {
+ if (mPreferences == null) {
+ mPreferences = getWidgetState(mContext, mWidgetId);
+ }
+ mPreferences.edit()
+ .putLong(STATE_CURRENT_FOLDER, mCurrentFolder)
+ .putLong(STATE_ROOT_FOLDER, mRootFolder)
+ .commit();
+ }
+
@Override
public int getCount() {
if (mBookmarks == null)
return 0;
- return mBookmarks.size();
+ return mBookmarks.getCount();
}
@Override
@@ -231,44 +207,33 @@
@Override
public RemoteViews getLoadingView() {
- return null;
+ return new RemoteViews(
+ mContext.getPackageName(), R.layout.bookmarkthumbnailwidget_item);
}
@Override
public RemoteViews getViewAt(int position) {
- if (position < 0 || position >= getCount()) {
+ if (!mBookmarks.moveToPosition(position)) {
return null;
}
- RenderResult res = mBookmarks.get(position);
- Breadcrumb folder = mBreadcrumbs.empty() ? null : mBreadcrumbs.peek();
+ long id = mBookmarks.getLong(BOOKMARK_INDEX_ID);
+ String title = mBookmarks.getString(BOOKMARK_INDEX_TITLE);
+ String url = mBookmarks.getString(BOOKMARK_INDEX_URL);
+ boolean isFolder = mBookmarks.getInt(BOOKMARK_INDEX_IS_FOLDER) != 0;
RemoteViews views = new RemoteViews(
mContext.getPackageName(), R.layout.bookmarkthumbnailwidget_item);
- Intent fillin;
- if (res.mIsFolder) {
- long nfi = res.mId;
- fillin = new Intent(ACTION_CHANGE_FOLDER, null,
- mContext, BookmarkThumbnailWidgetService.class)
- .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
- .putExtra(Bookmarks._ID, nfi);
- } else {
- fillin = new Intent(Intent.ACTION_VIEW)
- .addCategory(Intent.CATEGORY_BROWSABLE);
- if (!TextUtils.isEmpty(res.mUrl)) {
- fillin.setData(Uri.parse(res.mUrl));
- }
- }
- views.setOnClickFillInIntent(R.id.list_item, fillin);
// Set the title of the bookmark. Use the url as a backup.
- String displayTitle = res.mTitle;
+ String displayTitle = title;
if (TextUtils.isEmpty(displayTitle)) {
// The browser always requires a title for bookmarks, but jic...
- displayTitle = res.mUrl;
+ displayTitle = url;
}
views.setTextViewText(R.id.label, displayTitle);
- if (res.mIsFolder) {
- if (folder != null && res.mId == folder.mId) {
+ if (isFolder) {
+ if (id == mCurrentFolder) {
+ id = mBookmarks.getLong(BOOKMARK_INDEX_PARENT_ID);
views.setImageViewResource(R.id.thumb, R.drawable.thumb_bookmark_widget_folder_back_holo);
} else {
views.setImageViewResource(R.id.thumb, R.drawable.thumb_bookmark_widget_folder_holo);
@@ -276,20 +241,45 @@
views.setImageViewResource(R.id.favicon, R.drawable.ic_bookmark_widget_bookmark_holo_dark);
views.setDrawableParameters(R.id.thumb, true, 0, -1, null, -1);
} else {
+ // RemoteViews require a valid bitmap config
+ Options options = new Options();
+ options.inPreferredConfig = Config.ARGB_8888;
+ Bitmap thumbnail = null, favicon = null;
+ byte[] blob = mBookmarks.getBlob(BOOKMARK_INDEX_THUMBNAIL);
views.setDrawableParameters(R.id.thumb, true, 255, -1, null, -1);
- if (res.mThumbnail != null) {
- views.setImageViewBitmap(R.id.thumb, res.mThumbnail);
+ if (blob != null && blob.length > 0) {
+ thumbnail = BitmapFactory.decodeByteArray(
+ blob, 0, blob.length, options);
+ views.setImageViewBitmap(R.id.thumb, thumbnail);
} else {
views.setImageViewResource(R.id.thumb,
R.drawable.browser_thumbnail);
}
- if (res.mIcon != null) {
- views.setImageViewBitmap(R.id.favicon, res.mIcon);
+ blob = mBookmarks.getBlob(BOOKMARK_INDEX_FAVICON);
+ if (blob != null && blob.length > 0) {
+ favicon = BitmapFactory.decodeByteArray(
+ blob, 0, blob.length, options);
+ views.setImageViewBitmap(R.id.favicon, favicon);
} else {
views.setImageViewResource(R.id.favicon,
R.drawable.app_web_browser_sm);
}
}
+ Intent fillin;
+ if (isFolder) {
+ fillin = new Intent(ACTION_CHANGE_FOLDER)
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
+ .putExtra(Bookmarks._ID, id);
+ } else {
+ if (!TextUtils.isEmpty(url)) {
+ fillin = new Intent(Intent.ACTION_VIEW)
+ .addCategory(Intent.CATEGORY_BROWSABLE)
+ .setData(Uri.parse(url));
+ } else {
+ fillin = new Intent(BrowserActivity.ACTION_SHOW_BROWSER);
+ }
+ }
+ views.setOnClickFillInIntent(R.id.list_item, fillin);
return views;
}
@@ -305,187 +295,70 @@
@Override
public void onCreate() {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
- mAccountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
- mAccountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
- prefs.registerOnSharedPreferenceChangeListener(this);
- loadData();
}
@Override
public void onDestroy() {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
- prefs.unregisterOnSharedPreferenceChangeListener(this);
-
- // Workaround known framework bug
- // This class currently leaks, so free as much memory as we can
- recycleBitmaps();
- mBookmarks.clear();
- mBreadcrumbs.clear();
- if (mLoadTask != null) {
- mLoadTask.cancel(false);
- mLoadTask = null;
+ if (mBookmarks != null) {
+ mBookmarks.close();
+ mBookmarks = null;
}
+ deleteWidgetState(mContext, mWidgetId);
}
@Override
public void onDataSetChanged() {
+ long token = Binder.clearCallingIdentity();
+ syncState();
+ if (mRootFolder < 0 || mCurrentFolder < 0) {
+ // Our state has been zero'd, reset (account change most likely)
+ mRootFolder = getRootFolder();
+ mCurrentFolder = mRootFolder;
+ saveState();
+ }
+ loadBookmarks();
+ Binder.restoreCallingIdentity(token);
}
- void loadData() {
- if (mLoadTask != null) {
- mLoadTask.cancel(false);
- }
- mLoadTask = new LoadBookmarksTask();
- mLoadTask.execute();
- }
-
- class LoadBookmarksTask extends AsyncTask<Void, Void, List<RenderResult>> {
- private Breadcrumb mFolder;
-
- @Override
- protected void onPreExecute() {
- mFolder = mBreadcrumbs.empty() ? null : mBreadcrumbs.peek();
- }
-
- @Override
- protected List<RenderResult> doInBackground(Void... params) {
- return loadBookmarks(mFolder);
- }
-
- @Override
- protected void onPostExecute(List<RenderResult> result) {
- if (!isCancelled() && result != null) {
- recycleBitmaps();
- mBookmarks = result;
- AppWidgetManager.getInstance(mContext)
- .notifyAppWidgetViewDataChanged(mWidgetId, R.id.bookmarks_list);
- }
- }
- }
-
- List<RenderResult> loadBookmarks(Breadcrumb folder) {
- String where = null;
- Uri uri;
- if (USE_FOLDERS) {
- uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
- if (folder != null) {
- uri = ContentUris.withAppendedId(uri, folder.mId);
- }
- } else {
- uri = BrowserContract.Bookmarks.CONTENT_URI;
- where = Bookmarks.IS_FOLDER + " == 0";
- }
- uri = uri.buildUpon()
- .appendQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE, mAccountType)
- .appendQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME, mAccountName)
- .build();
- Cursor c = null;
- try {
- c = mContext.getContentResolver().query(uri, PROJECTION,
- where, null, null);
- if (c != null) {
- ArrayList<RenderResult> bookmarks
- = new ArrayList<RenderResult>(c.getCount() + 1);
- if (folder != null) {
- RenderResult res = new RenderResult(
- folder.mId, folder.mTitle, null);
- res.mIsFolder = true;
- bookmarks.add(res);
- }
- while (c.moveToNext()) {
- long id = c.getLong(BOOKMARK_INDEX_ID);
- String title = c.getString(BOOKMARK_INDEX_TITLE);
- String url = c.getString(BOOKMARK_INDEX_URL);
- RenderResult res = new RenderResult(id, title, url);
- res.mIsFolder = c.getInt(BOOKMARK_INDEX_IS_FOLDER) != 0;
- if (!res.mIsFolder) {
- // RemoteViews require a valid bitmap config
- Options options = new Options();
- options.inPreferredConfig = Config.ARGB_8888;
- Bitmap thumbnail = null, favicon = null;
- byte[] blob = c.getBlob(BOOKMARK_INDEX_THUMBNAIL);
- if (blob != null && blob.length > 0) {
- thumbnail = BitmapFactory.decodeByteArray(
- blob, 0, blob.length, options);
- }
- blob = c.getBlob(BOOKMARK_INDEX_FAVICON);
- if (blob != null && blob.length > 0) {
- favicon = BitmapFactory.decodeByteArray(
- blob, 0, blob.length, options);
- }
- res.mThumbnail = thumbnail;
- res.mIcon = favicon;
- }
- bookmarks.add(res);
- }
- if (bookmarks.size() == 0) {
- RenderResult res = new RenderResult(0, "", "");
- Bitmap thumbnail = BitmapFactory.decodeResource(
- mContext.getResources(),
- R.drawable.thumbnail_bookmarks_widget_no_bookmark_holo);
- Bitmap favicon = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
- res.mThumbnail = thumbnail;
- res.mIcon = favicon;
- for (int i = 0; i < 6; i++) {
- bookmarks.add(res);
- }
- }
- return bookmarks;
- }
- } catch (IllegalStateException e) {
- Log.e(TAG, "update bookmark widget", e);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- return null;
- }
-
- private void recycleBitmaps() {
- // Do a bit of house cleaning for the system
+ private void resetBookmarks() {
if (mBookmarks != null) {
- for (RenderResult res : mBookmarks) {
- if (res.mThumbnail != null) {
- res.mThumbnail.recycle();
- res.mThumbnail = null;
- }
- }
+ mBookmarks.close();
+ mBookmarks = null;
}
}
- @Override
- public void onSharedPreferenceChanged(
- SharedPreferences prefs, String key) {
- if (BrowserBookmarksPage.PREF_ACCOUNT_TYPE.equals(key)) {
- mAccountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
- mBreadcrumbs.clear();
- loadData();
- }
- if (BrowserBookmarksPage.PREF_ACCOUNT_NAME.equals(key)) {
- mAccountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
- mBreadcrumbs.clear();
- loadData();
+ long getRootFolder() {
+ Uri uri = Uri.withAppendedPath(
+ BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, "id");
+ uri = BookmarkUtils.addAccountInfo(mContext, uri.buildUpon()).build();
+ Cursor c = mContext.getContentResolver().query(
+ uri, null, null, null, null);
+ try {
+ c.moveToFirst();
+ return c.getLong(0);
+ } finally {
+ c.close();
}
}
- }
- // Class containing the rendering information for a specific bookmark.
- private static class RenderResult {
- final String mTitle;
- final String mUrl;
- Bitmap mThumbnail;
- Bitmap mIcon;
- boolean mIsFolder;
- long mId;
+ void loadBookmarks() {
+ resetBookmarks();
- RenderResult(long id, String title, String url) {
- mId = id;
- mTitle = title;
- mUrl = url;
+ Uri uri = ContentUris.withAppendedId(
+ BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER,
+ mCurrentFolder);
+ uri = BookmarkUtils.addAccountInfo(mContext, uri.buildUpon()).build();
+ mBookmarks = mContext.getContentResolver().query(uri, PROJECTION,
+ null, null, null);
+ if (mCurrentFolder != mRootFolder) {
+ uri = ContentUris.withAppendedId(
+ BrowserContract.Bookmarks.CONTENT_URI,
+ mCurrentFolder);
+ Cursor c = mContext.getContentResolver().query(uri, PROJECTION,
+ null, null, null);
+ mBookmarks = new MergeCursor(new Cursor[] { c, mBookmarks });
+ }
}
-
}
}
diff --git a/src/com/android/browser/widget/BookmarkWidgetProxy.java b/src/com/android/browser/widget/BookmarkWidgetProxy.java
new file mode 100644
index 0000000..8ab57fc
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkWidgetProxy.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 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.widget;
+
+import com.android.browser.BrowserActivity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class BookmarkWidgetProxy extends BroadcastReceiver {
+
+ private static final String TAG = "BookmarkWidgetProxy";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (BookmarkThumbnailWidgetService.ACTION_CHANGE_FOLDER.equals(intent.getAction())) {
+ BookmarkThumbnailWidgetService.changeFolder(context, intent);
+ } else if (BrowserActivity.ACTION_SHOW_BROWSER.equals(intent.getAction())) {
+ startActivity(context,
+ new Intent(BrowserActivity.ACTION_SHOW_BROWSER,
+ null, context, BrowserActivity.class));
+ } else {
+ Intent view = new Intent(intent);
+ view.setComponent(null);
+ startActivity(context, view);
+ }
+ }
+
+ void startActivity(Context context, Intent intent) {
+ try {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to start intent activity", e);
+ }
+ }
+}
diff --git a/tests/src/com/android/browser/PopularUrlsTest.java b/tests/src/com/android/browser/PopularUrlsTest.java
index 5e367be..3e7515f 100644
--- a/tests/src/com/android/browser/PopularUrlsTest.java
+++ b/tests/src/com/android/browser/PopularUrlsTest.java
@@ -305,12 +305,14 @@
private int page;
private String url;
private boolean isRecovery;
+ private boolean allClear;
private RunStatus(File file) throws IOException {
mFile = file;
FileReader input = null;
BufferedReader reader = null;
isRecovery = false;
+ allClear = false;
iteration = 0;
page = 0;
try {
@@ -369,7 +371,9 @@
}
public void cleanUp() {
- if (mFile.exists()) {
+ // only perform cleanup when allClear flag is set
+ // i.e. when the test was not interrupted by a Java crash
+ if (mFile.exists() && allClear) {
mFile.delete();
}
}
@@ -380,6 +384,7 @@
public void incrementPage() {
++page;
+ allClear = true;
}
public void incrementIteration() {
@@ -400,6 +405,7 @@
public void setUrl(String url) {
this.url = url;
+ allClear = false;
}
}
diff --git a/tests/src/com/android/browser/tests/BP2ProviderTests.java b/tests/src/com/android/browser/tests/BP2ProviderTests.java
new file mode 100644
index 0000000..a468fcd
--- /dev/null
+++ b/tests/src/com/android/browser/tests/BP2ProviderTests.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 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.tests;
+
+import com.android.browser.tests.utils.BP2TestCaseHelper;
+
+import android.content.ContentValues;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.provider.BrowserContract.Images;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.ByteArrayOutputStream;
+
+@SmallTest
+public class BP2ProviderTests extends BP2TestCaseHelper {
+
+ public void testUpdateImage() {
+ String url = "http://stub1.com";
+ insertBookmark(url, "stub 1");
+ ContentValues values = new ContentValues();
+ values.put(Images.URL, url);
+ Bitmap bitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+ values.put(Images.THUMBNAIL, os.toByteArray());
+ // Use updateBookmarks because the bookmarks URI observer should
+ // be triggered, even though we aren't giving it a bookmarks URI
+ assertTrue(updateBookmark(Images.CONTENT_URI, values));
+ }
+
+}
diff --git a/tests/src/com/android/browser/tests/BP2UriObserverTests.java b/tests/src/com/android/browser/tests/BP2UriObserverTests.java
new file mode 100644
index 0000000..2e84814
--- /dev/null
+++ b/tests/src/com/android/browser/tests/BP2UriObserverTests.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 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.tests;
+
+import com.android.browser.tests.utils.BP2TestCaseHelper;
+
+import android.content.ContentValues;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.net.Uri;
+import android.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.History;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.ByteArrayOutputStream;
+
+@SmallTest
+public class BP2UriObserverTests extends BP2TestCaseHelper {
+
+ public void testInsertBookmark() {
+ Uri insertedUri = insertBookmark("http://stub1.com", "Stub1");
+ TriggeredObserver stubObs = new TriggeredObserver(insertedUri);
+ assertObserversTriggered(false, stubObs);
+ insertBookmark("http://stub2.com", "Stub2");
+ perfIdeallyUntriggered(stubObs);
+ }
+
+ public void testUpdateBookmark() {
+ Uri toUpdate = insertBookmark("http://stub1.com", "Stub1");
+ Uri unchanged = insertBookmark("http://stub2.com", "Stub2");
+ TriggeredObserver updateObs = new TriggeredObserver(toUpdate);
+ TriggeredObserver unchangedObs = new TriggeredObserver(unchanged);
+ assertObserversTriggered(false, updateObs, unchangedObs);
+ assertTrue(updateBookmark(toUpdate, "http://stub1.com", "Stub1: Revenge of the stubs"));
+ assertTrue("Update observer not notified!", updateObs.checkTriggered());
+ perfIdeallyUntriggered(unchangedObs);
+ }
+
+ public void testUpdateBookmarkImages() {
+ Uri toUpdate = insertBookmark("http://stub1.com", "Stub1");
+ Uri unchanged = insertBookmark("http://stub2.com", "Stub2");
+ Bitmap favicon = Bitmap.createBitmap(16, 16, Config.ARGB_8888);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
+ byte[] rawFavicon = os.toByteArray();
+ ContentValues values = new ContentValues();
+ values.put(Bookmarks.FAVICON, rawFavicon);
+ values.put(Bookmarks.TITLE, "Stub1");
+ TriggeredObserver updateObs = new TriggeredObserver(toUpdate);
+ TriggeredObserver unchangedObs = new TriggeredObserver(unchanged);
+ assertTrue(updateBookmark(toUpdate, values));
+ assertTrue("Update observer not notified!", updateObs.checkTriggered());
+ perfIdeallyUntriggered(unchangedObs);
+ }
+
+ public void testInsertHistory() {
+ Uri insertedUri = insertHistory("http://stub1.com", "Stub1");
+ TriggeredObserver stubObs = new TriggeredObserver(insertedUri);
+ assertObserversTriggered(false, stubObs);
+ insertHistory("http://stub2.com", "Stub2");
+ perfIdeallyUntriggered(stubObs);
+ }
+
+ public void testUpdateHistory() {
+ Uri toUpdate = insertHistory("http://stub1.com", "Stub1");
+ Uri unchanged = insertHistory("http://stub2.com", "Stub2");
+ TriggeredObserver updateObs = new TriggeredObserver(toUpdate);
+ TriggeredObserver unchangedObs = new TriggeredObserver(unchanged);
+ assertObserversTriggered(false, updateObs, unchangedObs);
+ assertTrue(updateHistory(toUpdate, "http://stub1.com", "Stub1: Revenge of the stubs"));
+ assertTrue("Update observer not notified!", updateObs.checkTriggered());
+ perfIdeallyUntriggered(unchangedObs);
+ }
+
+ public void testUpdateHistoryImages() {
+ Uri toUpdate = insertHistory("http://stub1.com", "Stub1");
+ Uri unchanged = insertHistory("http://stub2.com", "Stub2");
+ Bitmap favicon = Bitmap.createBitmap(16, 16, Config.ARGB_8888);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
+ byte[] rawFavicon = os.toByteArray();
+ ContentValues values = new ContentValues();
+ values.put(History.FAVICON, rawFavicon);
+ values.put(History.TITLE, "Stub1");
+ TriggeredObserver updateObs = new TriggeredObserver(toUpdate);
+ TriggeredObserver unchangedObs = new TriggeredObserver(unchanged);
+ assertTrue(updateHistory(toUpdate, values));
+ assertTrue("Update observer not notified!", updateObs.checkTriggered());
+ perfIdeallyUntriggered(unchangedObs);
+ }
+}
diff --git a/tests/src/com/android/browser/tests/BookmarksTests.java b/tests/src/com/android/browser/tests/BookmarksTests.java
new file mode 100644
index 0000000..bd1a1b2
--- /dev/null
+++ b/tests/src/com/android/browser/tests/BookmarksTests.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 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.tests;
+
+import com.android.browser.Bookmarks;
+import com.android.browser.tests.utils.BP2TestCaseHelper;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Extends from BP2TestCaseHelper for the helper methods
+ * and to get the mock database
+ */
+@SmallTest
+public class BookmarksTests extends BP2TestCaseHelper {
+
+ public void testQueryCombinedForUrl() {
+ // First, add some bookmarks
+ assertNotNull(insertBookmark(
+ "http://google.com/search?q=test", "Test search"));
+ assertNotNull(insertBookmark(
+ "http://google.com/search?q=mustang", "Mustang search"));
+ assertNotNull(insertBookmark(
+ "http://google.com/search?q=aliens", "Aliens search"));
+ ContentResolver cr = getMockContentResolver();
+
+ Cursor c = null;
+ try {
+ // First, search for a match
+ String url = "http://google.com/search?q=test";
+ c = Bookmarks.queryCombinedForUrl(cr, null, url);
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(url, c.getString(0));
+ c.close();
+
+ // Next, search for no match
+ url = "http://google.com/search";
+ c = Bookmarks.queryCombinedForUrl(cr, null, url);
+ assertEquals(0, c.getCount());
+ assertFalse(c.moveToFirst());
+ c.close();
+ } finally {
+ if (c != null) c.close();
+ }
+ }
+
+}
diff --git a/tests/src/com/android/browser/tests/utils/BP2TestCaseHelper.java b/tests/src/com/android/browser/tests/utils/BP2TestCaseHelper.java
new file mode 100644
index 0000000..58e5bbe
--- /dev/null
+++ b/tests/src/com/android/browser/tests/utils/BP2TestCaseHelper.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2011 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.tests.utils;
+
+import com.android.browser.provider.BrowserProvider2;
+
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Browser;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.History;
+import android.util.Log;
+
+/**
+ * This is a replacement for ProviderTestCase2 that can handle notifyChange testing.
+ * It also has helper methods specifically for testing BrowserProvider2
+ */
+public abstract class BP2TestCaseHelper extends ProviderTestCase3<BrowserProvider2> {
+
+ // Tag for potential performance impacts
+ private static final String PERFTAG = "BP2-PerfCheck";
+
+ private TriggeredObserver mLegacyObserver;
+ private TriggeredObserver mRootObserver;
+ private TriggeredObserver mBookmarksObserver;
+ private TriggeredObserver mHistoryObserver;
+ private TriggeredObserver mWidgetObserver;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mLegacyObserver = new TriggeredObserver(Browser.BOOKMARKS_URI);
+ mRootObserver = new TriggeredObserver(BrowserContract.AUTHORITY_URI);
+ mBookmarksObserver = new TriggeredObserver(Bookmarks.CONTENT_URI);
+ mHistoryObserver = new TriggeredObserver(History.CONTENT_URI);
+ mWidgetObserver = new TriggeredObserver();
+ // We don't need to worry about setting this back to null since this
+ // is a private instance local to the MockContentResolver
+ getProvider().setWidgetObserver(mWidgetObserver);
+ }
+
+ public BP2TestCaseHelper() {
+ super(BrowserProvider2.class,
+ BrowserContract.AUTHORITY, BrowserProvider2.LEGACY_AUTHORITY);
+ }
+
+ public void perfIdeallyUntriggered(TriggeredObserver... obs) {
+ for (TriggeredObserver ob : obs) {
+ if (ob.checkTriggered()) {
+ // Not ideal, unnecessary notification
+ Log.i(PERFTAG, ob.mUri + " onChange called but content unaltered!");
+ }
+ }
+ }
+
+ public void assertObserversTriggered(boolean triggered,
+ TriggeredObserver... observers) {
+ for (TriggeredObserver obs : observers) {
+ assertEquals(obs.mUri + ", descendents:" + obs.mNotifyForDescendents,
+ triggered, obs.checkTriggered());
+ }
+ }
+
+ public class TriggeredObserver extends ContentObserver {
+ private boolean mTriggered;
+ Uri mUri;
+ boolean mNotifyForDescendents;
+
+ /**
+ * Creates an unmanaged TriggeredObserver
+ */
+ public TriggeredObserver() {
+ super(null);
+ }
+
+ /**
+ * Same as TriggeredObserver(uri, true);
+ */
+ public TriggeredObserver(Uri uri) {
+ this(uri, true);
+ }
+
+ /**
+ * Creates a managed TriggeredObserver that self-registers with the
+ * mock ContentResolver
+ */
+ public TriggeredObserver(Uri uri, boolean notifyForDescendents) {
+ super(null);
+ mUri = uri;
+ mNotifyForDescendents = notifyForDescendents;
+ registerContentObserver(uri, notifyForDescendents, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ mTriggered = true;
+ }
+
+ public boolean checkTriggered() {
+ boolean ret = mTriggered;
+ mTriggered = false;
+ return ret;
+ }
+ }
+
+ Uri mockInsert(Uri url, ContentValues values) {
+ assertObserversTriggered(false, mLegacyObserver, mRootObserver);
+ Uri ret = getMockContentResolver().insert(url, values);
+ assertObserversTriggered(true, mLegacyObserver, mRootObserver);
+ return ret;
+ }
+
+ int mockUpdate(Uri uri, ContentValues values, String where,
+ String[] selectionArgs) {
+ assertObserversTriggered(false, mLegacyObserver, mRootObserver);
+ int ret = getMockContentResolver().update(uri, values, where, selectionArgs);
+ if (ret > 0) {
+ assertObserversTriggered(true, mLegacyObserver, mRootObserver);
+ } else {
+ perfIdeallyUntriggered(mLegacyObserver);
+ perfIdeallyUntriggered(mRootObserver);
+ }
+ return ret;
+ }
+
+ public Uri insertBookmark(String url, String title) {
+ ContentValues values = new ContentValues();
+ values.put(BrowserContract.Bookmarks.TITLE, title);
+ values.put(BrowserContract.Bookmarks.URL, url);
+ values.put(BrowserContract.Bookmarks.IS_FOLDER, 0);
+ assertObserversTriggered(false, mBookmarksObserver, mWidgetObserver);
+ Uri ret = mockInsert(Bookmarks.CONTENT_URI, values);
+ assertObserversTriggered(true, mBookmarksObserver, mWidgetObserver);
+ perfIdeallyUntriggered(mHistoryObserver);
+ return ret;
+ }
+
+ public boolean updateBookmark(Uri uri, String url, String title) {
+ ContentValues values = new ContentValues();
+ values.put(BrowserContract.Bookmarks.TITLE, title);
+ values.put(BrowserContract.Bookmarks.URL, url);
+ return updateBookmark(uri, values);
+ }
+
+ public boolean updateBookmark(Uri uri, ContentValues values) {
+ assertObserversTriggered(false, mBookmarksObserver, mWidgetObserver);
+ int modifyCount = mockUpdate(uri, values, null, null);
+ assertTrue("UpdatedBookmark modified too much! " + uri, modifyCount <= 1);
+ boolean updated = modifyCount == 1;
+ if (updated) {
+ assertObserversTriggered(updated, mBookmarksObserver, mWidgetObserver);
+ } else {
+ perfIdeallyUntriggered(mBookmarksObserver, mWidgetObserver);
+ }
+ perfIdeallyUntriggered(mHistoryObserver);
+ return updated;
+ }
+
+ public Uri insertHistory(String url, String title) {
+ ContentValues values = new ContentValues();
+ values.put(BrowserContract.History.TITLE, title);
+ values.put(BrowserContract.History.URL, url);
+ assertObserversTriggered(false, mHistoryObserver);
+ Uri ret = mockInsert(History.CONTENT_URI, values);
+ assertObserversTriggered(true, mHistoryObserver);
+ perfIdeallyUntriggered(mBookmarksObserver, mWidgetObserver);
+ return ret;
+ }
+
+ public boolean updateHistory(Uri uri, String url, String title) {
+ ContentValues values = new ContentValues();
+ values.put(BrowserContract.History.TITLE, title);
+ values.put(BrowserContract.History.URL, url);
+ return updateHistory(uri, values);
+ }
+
+ public boolean updateHistory(Uri uri, ContentValues values) {
+ assertObserversTriggered(false, mHistoryObserver);
+ int modifyCount = mockUpdate(uri, values, null, null);
+ assertTrue("UpdatedHistory modified too much! " + uri, modifyCount <= 1);
+ boolean updated = modifyCount == 1;
+ if (updated) {
+ assertObserversTriggered(updated, mHistoryObserver);
+ } else {
+ perfIdeallyUntriggered(mHistoryObserver);
+ }
+ perfIdeallyUntriggered(mBookmarksObserver, mWidgetObserver);
+ return updated;
+ }
+}
diff --git a/tests/src/com/android/browser/tests/utils/MockContentResolver2.java b/tests/src/com/android/browser/tests/utils/MockContentResolver2.java
new file mode 100644
index 0000000..20f5521
--- /dev/null
+++ b/tests/src/com/android/browser/tests/utils/MockContentResolver2.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 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.tests.utils;
+
+import com.google.android.collect.Maps;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.database.ContentObserver;
+import android.net.Uri;
+
+import java.util.Map;
+
+public class MockContentResolver2 extends ContentResolver {
+
+ Map<String, ContentProvider> mProviders;
+ private final MockObserverNode mRootNode = new MockObserverNode("");
+
+ /*
+ * Creates a local map of providers. This map is used instead of the global map when an
+ * API call tries to acquire a provider.
+ */
+ public MockContentResolver2() {
+ super(null);
+ mProviders = Maps.newHashMap();
+ }
+
+ /**
+ * Adds access to a provider based on its authority
+ *
+ * @param name The authority name associated with the provider.
+ * @param provider An instance of {@link android.content.ContentProvider} or one of its
+ * subclasses, or null.
+ */
+ public void addProvider(String name, ContentProvider provider) {
+
+ /*
+ * Maps the authority to the provider locally.
+ */
+ mProviders.put(name, provider);
+ }
+
+ /** @hide */
+ @Override
+ protected IContentProvider acquireProvider(Context context, String name) {
+ return acquireExistingProvider(context, name);
+ }
+
+ /** @hide */
+ @Override
+ protected IContentProvider acquireExistingProvider(Context context, String name) {
+
+ /*
+ * Gets the content provider from the local map
+ */
+ final ContentProvider provider = mProviders.get(name);
+
+ if (provider != null) {
+ return provider.getIContentProvider();
+ } else {
+ return null;
+ }
+ }
+
+ /** @hide */
+ @Override
+ public boolean releaseProvider(IContentProvider provider) {
+ return true;
+ }
+
+ @Override
+ public void notifyChange(Uri uri, ContentObserver observer,
+ boolean syncToNetwork) {
+ mRootNode.notifyMyObservers(uri, 0, observer, false);
+ }
+
+ public void safeRegisterContentObserver(Uri uri, boolean notifyForDescendents,
+ ContentObserver observer) {
+ mRootNode.addObserver(uri, observer, notifyForDescendents);
+ }
+
+ public void safeUnregisterContentObserver(ContentObserver observer) {
+ mRootNode.removeObserver(observer);
+ }
+
+}
diff --git a/tests/src/com/android/browser/tests/utils/MockObserverNode.java b/tests/src/com/android/browser/tests/utils/MockObserverNode.java
new file mode 100644
index 0000000..edcffd4
--- /dev/null
+++ b/tests/src/com/android/browser/tests/utils/MockObserverNode.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2011 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.tests.utils;
+
+import android.database.ContentObserver;
+import android.net.Uri;
+
+import java.util.ArrayList;
+
+public final class MockObserverNode {
+ private class MockObserverEntry {
+ public final ContentObserver observer;
+ public final boolean notifyForDescendents;
+
+ public MockObserverEntry(ContentObserver o, boolean n) {
+ observer = o;
+ notifyForDescendents = n;
+ }
+ }
+
+ public static final int INSERT_TYPE = 0;
+ public static final int UPDATE_TYPE = 1;
+ public static final int DELETE_TYPE = 2;
+
+ private String mName;
+ private ArrayList<MockObserverNode> mChildren = new ArrayList<MockObserverNode>();
+ private ArrayList<MockObserverEntry> mObservers = new ArrayList<MockObserverEntry>();
+
+ public MockObserverNode(String name) {
+ mName = name;
+ }
+
+ private String getUriSegment(Uri uri, int index) {
+ if (uri != null) {
+ if (index == 0) {
+ return uri.getAuthority();
+ } else {
+ return uri.getPathSegments().get(index - 1);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ private int countUriSegments(Uri uri) {
+ if (uri == null) {
+ return 0;
+ }
+ return uri.getPathSegments().size() + 1;
+ }
+
+ public void addObserver(Uri uri, ContentObserver observer,
+ boolean notifyForDescendents) {
+ addObserver(uri, 0, observer, notifyForDescendents);
+ }
+
+ private void addObserver(Uri uri, int index, ContentObserver observer,
+ boolean notifyForDescendents) {
+ // If this is the leaf node add the observer
+ if (index == countUriSegments(uri)) {
+ mObservers.add(new MockObserverEntry(observer, notifyForDescendents));
+ return;
+ }
+
+ // Look to see if the proper child already exists
+ String segment = getUriSegment(uri, index);
+ if (segment == null) {
+ throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
+ }
+ int N = mChildren.size();
+ for (int i = 0; i < N; i++) {
+ MockObserverNode node = mChildren.get(i);
+ if (node.mName.equals(segment)) {
+ node.addObserver(uri, index + 1, observer, notifyForDescendents);
+ return;
+ }
+ }
+
+ // No child found, create one
+ MockObserverNode node = new MockObserverNode(segment);
+ mChildren.add(node);
+ node.addObserver(uri, index + 1, observer, notifyForDescendents);
+ }
+
+ public boolean removeObserver(ContentObserver observer) {
+ int size = mChildren.size();
+ for (int i = 0; i < size; i++) {
+ boolean empty = mChildren.get(i).removeObserver(observer);
+ if (empty) {
+ mChildren.remove(i);
+ i--;
+ size--;
+ }
+ }
+
+ size = mObservers.size();
+ for (int i = 0; i < size; i++) {
+ MockObserverEntry entry = mObservers.get(i);
+ if (entry.observer == observer) {
+ mObservers.remove(i);
+ break;
+ }
+ }
+
+ if (mChildren.size() == 0 && mObservers.size() == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ private void notifyMyObservers(boolean leaf, ContentObserver observer,
+ boolean selfNotify) {
+ int N = mObservers.size();
+ for (int i = 0; i < N; i++) {
+ MockObserverEntry entry = mObservers.get(i);
+
+ // Don't notify the observer if it sent the notification and isn't interesed
+ // in self notifications
+ if (entry.observer == observer && !selfNotify) {
+ continue;
+ }
+
+ // Make sure the observer is interested in the notification
+ if (leaf || (!leaf && entry.notifyForDescendents)) {
+ entry.observer.onChange(selfNotify);
+ }
+ }
+ }
+
+ public void notifyMyObservers(Uri uri, int index, ContentObserver observer,
+ boolean selfNotify) {
+ String segment = null;
+ int segmentCount = countUriSegments(uri);
+ if (index >= segmentCount) {
+ // This is the leaf node, notify all observers
+ notifyMyObservers(true, observer, selfNotify);
+ } else if (index < segmentCount){
+ segment = getUriSegment(uri, index);
+ // Notify any observers at this level who are interested in descendents
+ notifyMyObservers(false, observer, selfNotify);
+ }
+
+ int N = mChildren.size();
+ for (int i = 0; i < N; i++) {
+ MockObserverNode node = mChildren.get(i);
+ if (segment == null || node.mName.equals(segment)) {
+ // We found the child,
+ node.notifyMyObservers(uri, index + 1, observer, selfNotify);
+ if (segment != null) {
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/browser/tests/utils/ProviderTestCase3.java b/tests/src/com/android/browser/tests/utils/ProviderTestCase3.java
new file mode 100644
index 0000000..5799b0f
--- /dev/null
+++ b/tests/src/com/android/browser/tests/utils/ProviderTestCase3.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2011 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.tests.utils;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+import android.test.IsolatedContext;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContext;
+
+import java.io.File;
+
+/**
+ * Replacement for ProviderTestCase2 that keeps calls to ContentResolver.notifyChanged
+ * internal to observers registered with ProviderTestCase3.registerContentObserver
+ */
+public abstract class ProviderTestCase3<T extends ContentProvider> extends AndroidTestCase {
+
+ Class<T> mProviderClass;
+ String[] mProviderAuthority;
+
+ private IsolatedContext mProviderContext;
+ private MockContentResolver2 mResolver;
+
+ private class MockContext2 extends MockContext {
+
+ @Override
+ public Resources getResources() {
+ return getContext().getResources();
+ }
+
+ @Override
+ public File getDir(String name, int mode) {
+ // name the directory so the directory will be separated from
+ // one created through the regular Context
+ return getContext().getDir("mockcontext2_" + name, mode);
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+ }
+ /**
+ * Constructor.
+ *
+ * @param providerClass The class name of the provider under test
+ * @param providerAuthorities The provider's authority string
+ */
+ public ProviderTestCase3(Class<T> providerClass, String... providerAuthorities) {
+ mProviderClass = providerClass;
+ mProviderAuthority = providerAuthorities;
+ }
+
+ private T mProvider;
+
+ /**
+ * Returns the content provider created by this class in the {@link #setUp()} method.
+ * @return T An instance of the provider class given as a parameter to the test case class.
+ */
+ public T getProvider() {
+ return mProvider;
+ }
+
+ /**
+ * Sets up the environment for the test fixture.
+ * <p>
+ * Creates a new
+ * {@link com.android.browser.tests.utils.MockContentResolver2}, a new IsolatedContext
+ * that isolates the provider's file operations, and a new instance of
+ * the provider under test within the isolated environment.
+ * </p>
+ *
+ * @throws Exception
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mResolver = new MockContentResolver2();
+ final String filenamePrefix = "test.";
+ RenamingDelegatingContext targetContextWrapper = new
+ RenamingDelegatingContext(
+ new MockContext2(), // The context that most methods are
+ //delegated to
+ getContext(), // The context that file methods are delegated to
+ filenamePrefix);
+ mProviderContext = new IsolatedContext(mResolver, targetContextWrapper);
+
+ mProvider = mProviderClass.newInstance();
+ mProvider.attachInfo(mProviderContext, null);
+ assertNotNull(mProvider);
+ for (String auth : mProviderAuthority) {
+ mResolver.addProvider(auth, getProvider());
+ }
+ }
+
+ /**
+ * Tears down the environment for the test fixture.
+ * <p>
+ * Calls {@link android.content.ContentProvider#shutdown()} on the
+ * {@link android.content.ContentProvider} represented by mProvider.
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ mProvider.shutdown();
+ super.tearDown();
+ }
+
+ /**
+ * Gets the {@link MockContentResolver2} created by this class during initialization. You
+ * must use the methods of this resolver to access the provider under test.
+ *
+ * @return A {@link MockContentResolver2} instance.
+ */
+ public MockContentResolver2 getMockContentResolver() {
+ return mResolver;
+ }
+
+ /**
+ * Gets the {@link IsolatedContext} created by this class during initialization.
+ * @return The {@link IsolatedContext} instance
+ */
+ public IsolatedContext getMockContext() {
+ return mProviderContext;
+ }
+
+ public void registerContentObserver(Uri uri, boolean notifyForDescendents,
+ ContentObserver observer) {
+ mResolver.safeRegisterContentObserver(uri, notifyForDescendents, observer);
+ }
+
+ public void unregisterContentObserver(ContentObserver observer) {
+ mResolver.safeUnregisterContentObserver(observer);
+ }
+
+}