Merge "Import revised translations."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c76cb92..0f11f8c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,6 +20,10 @@
<original-package android:name="com.android.browser" />
+ <permission android:name="com.android.browser.permission.PRELOAD"
+ android:label="@string/permission_preload_label"
+ android:protectionLevel="signatureOrSystem" />
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
@@ -242,6 +246,18 @@
</intent-filter>
</receiver>
+ <receiver android:name=".PreloadRequestReceiver"
+ android:permission="com.android.browser.permission.PRELOAD" >
+ <intent-filter>
+ <action android:name="android.intent.action.PRELOAD"/>
+ <data android:scheme="http" />
+ </intent-filter>
+ </receiver>
+
+ <provider android:name=".provider.SnapshotProvider"
+ android:authorities="com.android.browser.snapshots"
+ android:exported="false" />
+
</application>
</manifest>
diff --git a/res/drawable-hdpi/geolocation_permissions_prompt_background.9.png b/res/drawable-hdpi/geolocation_permissions_prompt_background.9.png
new file mode 100644
index 0000000..44a2e2f
--- /dev/null
+++ b/res/drawable-hdpi/geolocation_permissions_prompt_background.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_chevron.png b/res/drawable-hdpi/ic_chevron.png
deleted file mode 100644
index 6dc842f..0000000
--- a/res/drawable-hdpi/ic_chevron.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_chevron.png b/res/drawable-mdpi/ic_chevron.png
deleted file mode 100644
index 786899a..0000000
--- a/res/drawable-mdpi/ic_chevron.png
+++ /dev/null
Binary files differ
diff --git a/res/layout/custom_screen.xml b/res/layout/custom_screen.xml
index 2105501..7a22530 100644
--- a/res/layout/custom_screen.xml
+++ b/res/layout/custom_screen.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android">
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout android:id="@+id/fullscreen_custom_content"
android:visibility="gone"
android:background="@color/black"
@@ -36,5 +37,5 @@
android:layout_height="match_parent"
/>
</LinearLayout>
-</FrameLayout>
+</merge>
diff --git a/res/layout/geolocation_permissions_prompt.xml b/res/layout/geolocation_permissions_prompt.xml
index babde3a..1920c05 100755
--- a/res/layout/geolocation_permissions_prompt.xml
+++ b/res/layout/geolocation_permissions_prompt.xml
@@ -18,74 +18,67 @@
<com.android.browser.GeolocationPermissionsPrompt
xmlns:android="http://schemas.android.com/apk/res/android"
- android:fitsSystemWindows="true"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:background="@drawable/geolocation_permissions_prompt_background"
+ android:visibility="gone">
- <!-- Use an inner element as we can't show a hidden outermost element -->
- <LinearLayout android:id="@+id/inner"
- android:orientation="vertical"
+ <!-- 'google.com wants to know your location' -->
+ <TextView android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@color/black"
- android:paddingTop="1px"
- android:visibility="gone">
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:padding="6dip"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
- <!-- White line -->
- <View
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="1px"
- android:background="@color/white" />
+ <CheckBox android:id="@+id/remember"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/message"
+ android:layout_alignLeft="@id/message" />
+ <TextView
+ android:paddingLeft="4dip"
+ android:text="@string/geolocation_permissions_prompt_remember"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_alignBaseline="@id/remember"
+ android:layout_toRightOf="@id/remember" />
- <!-- Container for content -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/remember"
+ android:orientation="vertical"
+ android:divider="?android:attr/dividerHorizontal"
+ android:showDividers="beginning"
+ android:dividerPadding="16dip"
+ android:background="@null">
<LinearLayout
- android:orientation="vertical"
+ style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/geolocation_permissions_prompt_background"
- android:padding="6dip">
-
- <!-- 'google.com wants to know your location' -->
- <TextView android:id="@+id/message"
- android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingLeft="2dip"
+ android:paddingRight="2dip"
+ android:measureWithLargestChild="true"
+ android:background="@null">
+ <Button
+ android:id="@+id/dont_share_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_weight="1"
+ android:layout_width="0dip"
android:layout_height="wrap_content"
- android:textSize="14dip"
- android:textColor="@color/black" />
-
- <!-- Checkbox -->
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <CheckBox android:id="@+id/remember"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <TextView
- android:paddingLeft="4dip"
- android:text="@string/geolocation_permissions_prompt_remember"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="14dip"
- android:textColor="@color/black" />
- </LinearLayout>
-
- <!-- Buttons -->
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <Button android:id="@+id/share_button"
- android:text="@string/geolocation_permissions_prompt_share"
- android:layout_weight="1"
- android:layout_width="0dip"
- android:layout_height="wrap_content" />
- <Button android:id="@+id/dont_share_button"
- android:text="@string/geolocation_permissions_prompt_dont_share"
- android:layout_weight="1"
- android:layout_width="0dip"
- android:layout_height="wrap_content" />
- </LinearLayout>
-
+ android:text="@string/geolocation_permissions_prompt_dont_share" />
+ <Button
+ android:id="@+id/share_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:text="@string/geolocation_permissions_prompt_share" />
</LinearLayout>
</LinearLayout>
+
</com.android.browser.GeolocationPermissionsPrompt>
diff --git a/res/layout/pref_homepage_buttons.xml b/res/layout/pref_homepage_buttons.xml
deleted file mode 100644
index fe21beb..0000000
--- a/res/layout/pref_homepage_buttons.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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.
-
-
- This layout provides the structure for a browser tab. A tab contains the
- WebView and any number of other UI elements specific to that tab.
- Currently, the only such element is the Geolocation permissions prompt.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal">
-
- <Button android:id="@+id/use_current"
- android:text="@string/pref_use_current"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="16dp" />
-
- <Button android:id="@+id/use_default"
- android:text="@string/pref_use_default"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
-</LinearLayout>
diff --git a/res/layout/snapshot_item.xml b/res/layout/snapshot_item.xml
new file mode 100644
index 0000000..2fc6ca8
--- /dev/null
+++ b/res/layout/snapshot_item.xml
@@ -0,0 +1,77 @@
+<?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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/combo_horizontalSpacing">
+ <ImageView
+ android:id="@+id/thumb"
+ android:src="@drawable/thumbnail_bookmarks_widget_no_bookmark_holo"
+ android:layout_width="@dimen/bookmarkThumbnailWidth"
+ android:layout_height="@dimen/bookmarkThumbnailHeight"
+ android:scaleType="centerCrop"
+ android:cropToPadding="true"
+ android:background="@drawable/border_thumb_bookmarks_widget_holo" />
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@id/thumb"
+ android:layout_alignLeft="@id/thumb"
+ android:layout_alignRight="@id/thumb"
+ android:background="@drawable/overlay_url_bookmark_widget_holo"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textSize="12sp"
+ android:typeface="sans"
+ android:textColor="@android:color/white"
+ android:paddingLeft="6dip"
+ android:paddingRight="2dip"
+ android:gravity="center_vertical" />
+ <ImageView
+ android:id="@+id/divider"
+ android:src="?android:attr/dividerVertical"
+ android:layout_width="wrap_content"
+ android:layout_height="24dip"
+ android:layout_below="@+id/thumb"
+ android:layout_alignLeft="@+id/thumb"
+ android:scaleType="fitXY"
+ android:layout_marginTop="12dip" />
+ <TextView android:id="@+id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/divider"
+ android:layout_alignTop="@id/divider"
+ android:layout_alignBottom="@id/divider"
+ android:paddingLeft="8dip"
+ android:gravity="center_vertical"
+ android:typeface="sans"
+ android:textSize="14sp"
+ android:textColor="#AAAAAA" />
+ <TextView android:id="@+id/size"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@id/divider"
+ android:layout_alignBottom="@id/divider"
+ android:layout_alignRight="@+id/thumb"
+ android:paddingRight="2dip"
+ android:gravity="center_vertical"
+ android:typeface="sans"
+ android:textSize="14sp"
+ android:textColor="#AAAAAA" />
+</RelativeLayout>
diff --git a/res/layout/snapshots.xml b/res/layout/snapshots.xml
new file mode 100644
index 0000000..48d2883
--- /dev/null
+++ b/res/layout/snapshots.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/combo_paddingTop">
+
+ <GridView
+ android:id="@+id/grid"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:numColumns="auto_fit"
+ android:stretchMode="none"
+ android:gravity="center" />
+ <TextView
+ android:id="@android:id/empty"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="@string/empty_bookmarks_folder"
+ android:visibility="gone" />
+
+</FrameLayout>
diff --git a/res/layout/tab_title.xml b/res/layout/tab_title.xml
index fcae2bc..7ac2ba0 100644
--- a/res/layout/tab_title.xml
+++ b/res/layout/tab_title.xml
@@ -17,13 +17,6 @@
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
- android:id="@+id/chevron"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:src="@drawable/ic_chevron"
- android:visibility="gone" />
- <ImageView
android:id="@+id/incognito"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/res/layout/title_bar.xml b/res/layout/title_bar.xml
index 649a4a0..9096418 100644
--- a/res/layout/title_bar.xml
+++ b/res/layout/title_bar.xml
@@ -24,11 +24,7 @@
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_height"
android:orientation="horizontal"
- android:background="@drawable/bg_urlbar"
- android:paddingLeft="4dip"
- android:paddingRight="4dip"
- android:paddingTop="2dip"
- android:paddingBottom="2dip">
+ android:background="@drawable/bg_urlbar">
<LinearLayout
android:id="@+id/title_bg"
android:layout_width="0dip"
@@ -36,53 +32,63 @@
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
+ <FrameLayout
+ android:id="@+id/iconcombo"
+ android:layout_width="52dip"
+ android:layout_height="match_parent"
+ style="@style/HoloButton">
+ <ImageView
+ android:id="@+id/favicon"
+ android:layout_width="36dip"
+ android:layout_height="36dip"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip"
+ android:layout_gravity="center_vertical" />
+ <ImageView
+ android:id="@+id/lock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|right"
+ android:visibility="gone" />
+ </FrameLayout>
<ImageView
- android:id="@+id/favicon"
- android:layout_width="20dip"
- android:layout_height="20dip" />
- <ImageView
- android:id="@+id/lock"
+ android:id="@+id/stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="2dip"
- android:visibility="gone" />
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/ic_stop_holo_dark"
+ style="@style/HoloButton" />
<com.android.browser.UrlInputView
android:id="@+id/url"
android:focusable="true"
android:layout_width="0dip"
android:layout_weight="1.0"
android:layout_height="match_parent"
- android:layout_marginLeft="2dip"
- android:paddingLeft="4dip"
- android:paddingRight="4dip"
- android:background="@*android:drawable/edit_text_holo_dark"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="24dip"
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:inputType="text"
android:imeOptions="actionGo"
- style="@style/Suggestions" />
+ style="@style/Suggestions"
+ android:background="@null" />
+ <ImageView
+ android:id="@+id/voice"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:src="@drawable/ic_voice_search_holo_dark"
+ style="@style/HoloButton"
+ android:visibility="gone" />
</LinearLayout>
- <ImageView
- android:id="@+id/voice"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:src="@drawable/ic_voice_search_holo_dark"
- android:visibility="gone" />
- <ImageView
- android:id="@+id/stop"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:src="@drawable/ic_stop_holo_dark" />
<ImageButton
- android:id="@+id/forward"
+ android:id="@+id/tab_switcher"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:src="@drawable/ic_forward_holo_dark"
+ android:src="@drawable/ic_windows_holo_dark"
style="@style/HoloButton" />
</LinearLayout>
<LinearLayout
diff --git a/res/menu-sw600dp/browser.xml b/res/menu-sw600dp/browser.xml
index 23366e7..29b6117 100644
--- a/res/menu-sw600dp/browser.xml
+++ b/res/menu-sw600dp/browser.xml
@@ -32,11 +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/freeze_tab_menu_id"
- android:title="@string/menu_freeze_tab" />
+ <item android:id="@+id/save_snapshot_menu_id"
+ android:title="@string/menu_save_snapshot" />
<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 6eadcba..630bb87 100644
--- a/res/menu/browser.xml
+++ b/res/menu/browser.xml
@@ -62,11 +62,8 @@
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/freeze_tab_menu_id"
- android:title="@string/menu_freeze_tab" />
+ android:id="@+id/save_snapshot_menu_id"
+ android:title="@string/menu_save_snapshot" />
<item
android:id="@+id/page_info_menu_id"
android:title="@string/page_info"
diff --git a/res/menu/snapshots_context.xml b/res/menu/snapshots_context.xml
new file mode 100644
index 0000000..01880dd
--- /dev/null
+++ b/res/menu/snapshots_context.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <group android:id="@+id/CONTEXT_MENU">
+ <item
+ android:id="@+id/delete_context_menu_id"
+ android:title="@string/remove_bookmark"/>
+ </group>
+</menu>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 322a80a..4ab971f 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -23,8 +23,6 @@
<color name="white">#ffffffff</color>
<color name="black">#ff000000</color>
- <color name="geolocation_permissions_prompt_background">#ffdddddd</color>
-
<color name="bookmarkWidgetHeader">#383847</color>
<color name="bookmarkWidgetDivider">#383847</color>
<color name="bookmarkWidgetItemBackground">#2b2b3c</color>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e1fe0c5..d0e81ac 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -34,6 +34,8 @@
<string name="tab_most_visited">Most visited</string>
<!-- Name of tab containing the user's complete history, organized by time of last visit -->
<string name="tab_history">History</string>
+ <!-- Name of tab containing the user's saved pages, organized by time created [CHAR LIMIT=20] -->
+ <string name="tab_snapshots">Saved Pages</string>
<!-- Toast shown when a history item's star is clicked, converting it to a bookmark -->
<string name="added_to_bookmarks">Added to bookmarks</string>
<!-- Toast shown when a history item's star is clicked off, removing its bookmark -->
@@ -213,15 +215,10 @@
<string name="copy_page_url">Copy page url</string>
<!-- Menu item -->
<string name="share_page">Share page</string>
- <!-- Menu item to freeze a tab. This will make a view-only
- snapshot of the page in a new tab. [CHAR LIMIT=50] -->
- <string name="menu_freeze_tab">Freeze tab</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>
+ <!-- Menu item for saving a page for offline reading. This is a view-only snapshot of the page. [CHAR LIMIT=50] -->
+ <string name="menu_save_snapshot">Save for offline reading</string>
+ <!-- Toast informing the user that saving the page for offline reading has failed. [CHAR LIMIT=50] -->
+ <string name="snapshot_failed">Failed to save for offline reading.</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] -->
@@ -645,6 +642,12 @@
<!-- Summary for the fullscreen lab feature [CHAR LIMIT=120] -->
<string name="pref_lab_fullscreen_summary">
Use fullscreen mode to hide the status bar.</string>
+ <!-- Title for bandwidth management preference [CHAR LIMIT=25] -->
+ <string name="pref_data_title">Bandwidth Management</string>
+ <!-- Title for search preloading [CHAR LIMIT=40] -->
+ <string name="pref_data_preload_title">Search result preloading</string>
+ <!-- Summary for search preloading [CHAR LIMIT=80] -->
+ <string name="pref_data_preload_summary">Allow the browser to preload high confidence search results in the background</string>
<!-- Title for a dialog displayed when the browser has a data connectivity
problem -->
<string name="browserFrameNetworkErrorLabel">Data connectivity problem</string>
@@ -822,7 +825,7 @@
http://www.google.com/webhp?client={CID}&source=android-home</string>
<!-- The default url for the instant_base_page. -->
<string name="instant_base" translatable="false">
- http://www.google.com/webhp?client={CID}&source=android-omnibox-instant&ion=1</string>
+ http://www.google.com/webhp?client={CID}&source=android-instant&ion=1</string>
<!-- Bookmarks -->
<string-array name="bookmarks" translatable="false">
@@ -995,4 +998,6 @@
<string name="ua_switcher_mobile">Mobile</string>
<!-- Popup menu option that allows the user to select the desktop version of a webpage [CHAR LIMIT=50] -->
<string name="ua_switcher_desktop">Desktop</string>
+ <!-- Preload permission label [CHAR LIMIT=40] -->
+ <string name="permission_preload_label">Preload results</string>
</resources>
diff --git a/res/xml/bandwidth_preferences.xml b/res/xml/bandwidth_preferences.xml
new file mode 100644
index 0000000..0eb4c21
--- /dev/null
+++ b/res/xml/bandwidth_preferences.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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <CheckBoxPreference
+ android:key="preload_enabled"
+ android:title="@string/pref_data_preload_title"
+ android:summary="@string/pref_data_preload_summary"
+ android:defaultValue="false" />
+
+</PreferenceScreen>
diff --git a/res/xml/preference_headers.xml b/res/xml/preference_headers.xml
index e58b90a..2c80835 100644
--- a/res/xml/preference_headers.xml
+++ b/res/xml/preference_headers.xml
@@ -32,6 +32,10 @@
android:title="@string/pref_extras_title"
/>
+ <header android:fragment="com.android.browser.preferences.BandwidthPreferencesFragment"
+ android:title="@string/pref_data_title"
+ />
+
<header android:fragment="com.android.browser.preferences.LabPreferencesFragment"
android:title="@string/pref_lab_title"
/>
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index bcb18f1..0777efd 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -16,11 +16,6 @@
package com.android.browser;
-import com.android.browser.BrowserWebView.ScrollListener;
-import com.android.browser.Tab.LockIcon;
-import com.android.internal.view.menu.MenuBuilder;
-
-import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
@@ -40,9 +35,11 @@
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
@@ -54,12 +51,15 @@
import android.widget.LinearLayout;
import android.widget.Toast;
+import com.android.browser.Tab.LockIcon;
+import com.android.internal.view.menu.MenuBuilder;
+
import java.util.List;
/**
* UI interface definitions
*/
-public abstract class BaseUi implements UI, WebViewFactory, ScrollListener {
+public abstract class BaseUi implements UI, OnTouchListener {
private static final String LOGTAG = "BaseUi";
@@ -87,7 +87,6 @@
private Drawable mMixLockIcon;
protected Drawable mGenericFavicon;
- private FrameLayout mBrowserFrameLayout;
protected FrameLayout mContentView;
protected FrameLayout mCustomViewContainer;
@@ -101,7 +100,7 @@
private Toast mStopToast;
- private int mLastScrollY;
+ private float mInitialY;
private int mTitlebarScrollTriggerSlop;
// the default <video> poster
@@ -110,6 +109,7 @@
private View mVideoProgressView;
private boolean mActivityPaused;
+ protected boolean mUseQuickControls;
public BaseUi(Activity browser, UiController controller) {
mActivity = browser;
@@ -123,15 +123,14 @@
FrameLayout frameLayout = (FrameLayout) mActivity.getWindow()
.getDecorView().findViewById(android.R.id.content);
- mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(mActivity)
- .inflate(R.layout.custom_screen, null);
- mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(
+ LayoutInflater.from(mActivity)
+ .inflate(R.layout.custom_screen, frameLayout);
+ mContentView = (FrameLayout) frameLayout.findViewById(
R.id.main_content);
- mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout
+ mErrorConsoleContainer = (LinearLayout) frameLayout
.findViewById(R.id.error_console);
- mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
+ mCustomViewContainer = (FrameLayout) frameLayout
.findViewById(R.id.fullscreen_custom_content);
- frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
setFullscreen(BrowserSettings.getInstance().useFullscreen());
mGenericFavicon = res.getDrawable(
R.drawable.app_web_browser_sm);
@@ -143,41 +142,6 @@
config.getScaledTouchSlop());
}
- @Override
- public WebView createWebView(boolean privateBrowsing) {
- // Create a new WebView
- BrowserWebView w = new BrowserWebView(mActivity, null,
- android.R.attr.webViewStyle, privateBrowsing);
- initWebViewSettings(w);
- return w;
- }
-
- @Override
- public WebView createSubWebView(boolean privateBrowsing) {
- return createWebView(privateBrowsing);
- }
-
- /**
- * common webview initialization
- * @param w the webview to initialize
- */
- protected void initWebViewSettings(WebView w) {
- w.setScrollbarFadingEnabled(true);
- w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
- w.setMapTrackballToArrowKeys(false); // use trackball directly
- // Enable the built-in zoom
- w.getSettings().setBuiltInZoomControls(true);
- boolean supportsMultiTouch = mActivity.getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
- w.getSettings().setDisplayZoomControls(!supportsMultiTouch);
- w.setExpandedTileBounds(true); // smoother scrolling
-
- // Add this WebView to the settings observer list and update the
- // settings
- final BrowserSettings s = BrowserSettings.getInstance();
- s.startManagingSettings(w.getSettings());
- }
-
private void cancelStopToast() {
if (mStopToast != null) {
mStopToast.cancel();
@@ -269,8 +233,16 @@
mHandler.removeMessages(MSG_HIDE_TITLEBAR);
if ((tab != mActiveTab) && (mActiveTab != null)) {
removeTabFromContentView(mActiveTab);
+ WebView web = mActiveTab.getWebView();
+ if (web != null) {
+ web.setOnTouchListener(null);
+ }
}
mActiveTab = tab;
+ WebView web = mActiveTab.getWebView();
+ if (web != null && !mUseQuickControls) {
+ web.setOnTouchListener(this);
+ }
attachTabToContentView(tab);
setShouldShowErrorConsole(tab, mUiController.shouldShowErrorConsole());
onTabDataChanged(tab);
@@ -278,6 +250,10 @@
boolean incognito = mActiveTab.getWebView().isPrivateBrowsingEnabled();
getTitleBar().setIncognitoMode(incognito);
updateAutoLogin(tab, false);
+ if (web != null && web.getVisibleTitleHeight()
+ != getTitleBar().getEmbeddedHeight()) {
+ showTitleBarForDuration();
+ }
}
Tab getActiveTab() {
@@ -374,7 +350,7 @@
// The tab consists of a container view, which contains the main
// WebView, as well as any other UI elements associated with the tab.
container = mActivity.getLayoutInflater().inflate(R.layout.tab,
- null);
+ mContentView, false);
tab.setViewContainer(container);
}
if (tab.getWebView() != webView) {
@@ -504,15 +480,13 @@
}
@Override
- public void showComboView(boolean startWithHistory, Bundle extras) {
+ public void showComboView(ComboViews startingView, Bundle extras) {
if (mComboView != null) {
return;
}
mComboView = new CombinedBookmarkHistoryView(mActivity,
mUiController,
- startWithHistory ?
- CombinedBookmarkHistoryView.FRAGMENT_ID_HISTORY
- : CombinedBookmarkHistoryView.FRAGMENT_ID_BOOKMARKS,
+ startingView,
extras);
FrameLayout wrapper =
(FrameLayout) mContentView.findViewById(R.id.webview_wrapper);
@@ -523,11 +497,6 @@
if (mActiveTab != null) {
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);
}
@@ -854,29 +823,39 @@
}
}
+ private void showTitleBarForDuration() {
+ mHandler.removeMessages(MSG_HIDE_TITLEBAR);
+ showTitleBar();
+ Message msg = Message.obtain(mHandler, MSG_HIDE_TITLEBAR);
+ mHandler.sendMessageDelayed(msg, HIDE_TITLEBAR_DELAY);
+ }
+
@Override
- public void onScroll(int visibleTitleHeight, boolean userInitiated) {
- WebView view = mActiveTab != null ? mActiveTab.getWebView() : null;
- if (view == null) {
- return;
- }
- int scrollY = view.getScrollY();
- if (isTitleBarShowing()
- || scrollY < (mLastScrollY - mTitlebarScrollTriggerSlop)) {
- mLastScrollY = scrollY;
- if (visibleTitleHeight == 0 && userInitiated) {
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mInitialY = event.getY();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ WebView web = (WebView) v;
+ if (!isTitleBarShowing()
+ && web.getVisibleTitleHeight() == 0
+ && event.getY() > (mInitialY + mTitlebarScrollTriggerSlop)) {
mHandler.removeMessages(MSG_HIDE_TITLEBAR);
showTitleBar();
+ } else if (event.getY() < mInitialY) {
+ mInitialY = event.getY();
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ if (isTitleBarShowing()) {
Message msg = Message.obtain(mHandler, MSG_HIDE_TITLEBAR);
mHandler.sendMessageDelayed(msg, HIDE_TITLEBAR_DELAY);
- } else if (visibleTitleHeight == getTitleBar().getEmbeddedHeight()
- && mHandler.hasMessages(MSG_HIDE_TITLEBAR)) {
- mHandler.removeMessages(MSG_HIDE_TITLEBAR);
- suggestHideTitleBar();
}
- } else if (scrollY > mLastScrollY) {
- mLastScrollY = scrollY;
+ break;
}
+ return false;
}
private Handler mHandler = new Handler() {
diff --git a/src/com/android/browser/Browser.java b/src/com/android/browser/Browser.java
index 65eb0ce..c4412e2 100644
--- a/src/com/android/browser/Browser.java
+++ b/src/com/android/browser/Browser.java
@@ -18,6 +18,7 @@
import android.app.Application;
import android.content.Intent;
+import android.os.AsyncTask;
import android.util.Log;
import android.webkit.CookieSyncManager;
@@ -52,12 +53,15 @@
if (LOGV_ENABLED)
Log.v(LOGTAG, "Browser.onCreate: this=" + this);
+ // Fix AsyncTask to use multiple threads
+ AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// Fix heap utilization for better heap size characteristics.
VMRuntime.getRuntime().setTargetHeapUtilization(
TARGET_HEAP_UTILIZATION);
// create CookieSyncManager with current Context
CookieSyncManager.createInstance(this);
BrowserSettings.initialize(getApplicationContext());
+ Preloader.initialize(getApplicationContext());
}
static Intent createBrowserViewIntent() {
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index dbcae2e..13b8b06 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -92,7 +92,6 @@
mUi = new PhoneUi(this, mController);
}
mController.setUi(mUi);
- mController.setWebViewFactory((BaseUi) mUi);
Bundle state = getIntent().getBundleExtra(EXTRA_STATE);
if (state != null && icicle == null) {
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 4928e61..3a6349a 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -689,4 +689,11 @@
return mPrefs.getBoolean(PREF_REMEMBER_PASSWORDS, true);
}
+ // -----------------------------
+ // getter/setters for bandwidth_preferences.xml
+ // -----------------------------
+
+ public boolean isPreloadEnabled() {
+ return mPrefs.getBoolean(PREF_DATA_PRELOAD, false);
+ }
}
diff --git a/src/com/android/browser/BrowserSnapshotPage.java b/src/com/android/browser/BrowserSnapshotPage.java
new file mode 100644
index 0000000..72da15b
--- /dev/null
+++ b/src/com/android/browser/BrowserSnapshotPage.java
@@ -0,0 +1,233 @@
+/*
+ * 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;
+
+import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import com.android.browser.provider.SnapshotProvider.Snapshots;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+public class BrowserSnapshotPage extends Fragment implements
+ LoaderCallbacks<Cursor>, OnItemClickListener {
+
+ public static final String EXTRA_ANIMATE_ID = "animate_id";
+
+ private static final int LOADER_SNAPSHOTS = 1;
+ private static final String[] PROJECTION = new String[] {
+ Snapshots._ID,
+ Snapshots.TITLE,
+ "length(" + Snapshots.VIEWSTATE + ")",
+ Snapshots.THUMBNAIL,
+ Snapshots.FAVICON,
+ Snapshots.URL,
+ Snapshots.DATE_CREATED,
+ };
+ private static final int SNAPSHOT_TITLE = 1;
+ private static final int SNAPSHOT_VIEWSTATE_LENGTH = 2;
+ private static final int SNAPSHOT_THUMBNAIL = 3;
+ private static final int SNAPSHOT_FAVICON = 4;
+ private static final int SNAPSHOT_URL = 5;
+ private static final int SNAPSHOT_DATE_CREATED = 6;
+
+ GridView mGrid;
+ View mEmpty;
+ SnapshotAdapter mAdapter;
+ UiController mUiController;
+
+ public static BrowserSnapshotPage newInstance(UiController uiController,
+ Bundle extras) {
+ BrowserSnapshotPage instance = new BrowserSnapshotPage();
+ instance.mUiController = uiController;
+ instance.setArguments(extras);
+ return instance;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.snapshots, container, false);
+ mEmpty = view.findViewById(android.R.id.empty);
+ mGrid = (GridView) view.findViewById(R.id.grid);
+ setupGrid(inflater);
+ getLoaderManager().initLoader(LOADER_SNAPSHOTS, null, this);
+ return view;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ getLoaderManager().destroyLoader(LOADER_SNAPSHOTS);
+ mAdapter.changeCursor(null);
+ mAdapter = null;
+ }
+
+ void setupGrid(LayoutInflater inflater) {
+ View item = inflater.inflate(R.layout.snapshot_item, mGrid, false);
+ int mspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ item.measure(mspec, mspec);
+ int width = item.getMeasuredWidth();
+ mGrid.setColumnWidth(width);
+ mGrid.setOnItemClickListener(this);
+ mGrid.setOnCreateContextMenuListener(this);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ if (id == LOADER_SNAPSHOTS) {
+ return new CursorLoader(getActivity(),
+ Snapshots.CONTENT_URI, PROJECTION,
+ null, null, Snapshots.DATE_CREATED + " DESC");
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ if (loader.getId() == LOADER_SNAPSHOTS) {
+ if (mAdapter == null) {
+ mAdapter = new SnapshotAdapter(getActivity(), data);
+ mGrid.setAdapter(mAdapter);
+ } else {
+ mAdapter.changeCursor(data);
+ }
+ boolean empty = mAdapter.isEmpty();
+ mGrid.setVisibility(empty ? View.GONE : View.VISIBLE);
+ mEmpty.setVisibility(empty ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ MenuInflater inflater = getActivity().getMenuInflater();
+ inflater.inflate(R.menu.snapshots_context, menu);
+ // Create the header, re-use BookmarkItem (has the layout we want)
+ BookmarkItem header = new BookmarkItem(getActivity());
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
+ populateBookmarkItem(mAdapter.getItem(info.position), header);
+ menu.setHeaderView(header);
+ }
+
+ private void populateBookmarkItem(Cursor cursor, BookmarkItem item) {
+ item.setName(cursor.getString(SNAPSHOT_TITLE));
+ item.setUrl(cursor.getString(SNAPSHOT_URL));
+ item.setFavicon(getBitmap(cursor, SNAPSHOT_FAVICON));
+ }
+
+ static Bitmap getBitmap(Cursor cursor, int columnIndex) {
+ byte[] data = cursor.getBlob(columnIndex);
+ if (data == null) {
+ return null;
+ }
+ return BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.delete_context_menu_id) {
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+ deleteSnapshot(info.id);
+ return true;
+ }
+ return super.onContextItemSelected(item);
+ }
+
+ void deleteSnapshot(long id) {
+ final Uri uri = ContentUris.withAppendedId(Snapshots.CONTENT_URI, id);
+ final ContentResolver cr = getActivity().getContentResolver();
+ new Thread() {
+ @Override
+ public void run() {
+ cr.delete(uri, null, null);
+ }
+ }.start();
+
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ mUiController.removeComboView();
+ mUiController.createNewSnapshotTab(id, true);
+ }
+
+ private static class SnapshotAdapter extends ResourceCursorAdapter {
+
+ public SnapshotAdapter(Context context, Cursor c) {
+ super(context, R.layout.snapshot_item, c, 0);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ ImageView thumbnail = (ImageView) view.findViewById(R.id.thumb);
+ byte[] thumbBlob = cursor.getBlob(SNAPSHOT_THUMBNAIL);
+ if (thumbBlob == null) {
+ thumbnail.setImageResource(R.drawable.browser_thumbnail);
+ } else {
+ Bitmap thumbBitmap = BitmapFactory.decodeByteArray(
+ thumbBlob, 0, thumbBlob.length);
+ thumbnail.setImageBitmap(thumbBitmap);
+ }
+ TextView title = (TextView) view.findViewById(R.id.title);
+ title.setText(cursor.getString(SNAPSHOT_TITLE));
+ TextView size = (TextView) view.findViewById(R.id.size);
+ int stateLen = cursor.getInt(SNAPSHOT_VIEWSTATE_LENGTH);
+ size.setText(String.format("%.2fMB", stateLen / 1024f / 1024f));
+ long timestamp = cursor.getLong(SNAPSHOT_DATE_CREATED);
+ TextView date = (TextView) view.findViewById(R.id.date);
+ DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
+ date.setText(dateFormat.format(new Date(timestamp)));
+ }
+
+ @Override
+ public Cursor getItem(int position) {
+ return (Cursor) super.getItem(position);
+ }
+ }
+
+}
diff --git a/src/com/android/browser/BrowserWebView.java b/src/com/android/browser/BrowserWebView.java
index 55dd24a..80f4a53 100644
--- a/src/com/android/browser/BrowserWebView.java
+++ b/src/com/android/browser/BrowserWebView.java
@@ -20,7 +20,6 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.util.AttributeSet;
-import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;
@@ -31,12 +30,9 @@
/**
* Manage WebView scroll events
*/
-public class BrowserWebView extends WebView implements Runnable {
+public class BrowserWebView extends WebView {
- private ScrollListener mScrollListener;
- private boolean mIsCancelled;
private boolean mBackgroundRemoved = false;
- private boolean mUserInitiated = false;
private TitleBarBase mTitleBar;
private int mCaptureSize;
private Bitmap mCapture;
@@ -104,13 +100,6 @@
return (mTitleBar != null) ? mTitleBar.getEmbeddedHeight() : 0;
}
- // scroll runnable implementation
- public void run() {
- if (!mIsCancelled && (mScrollListener != null)) {
- mScrollListener.onScroll(getVisibleTitleHeight(), mUserInitiated);
- }
- }
-
void hideEmbeddedTitleBar() {
scrollBy(0, getVisibleTitleHeight());
}
@@ -119,53 +108,12 @@
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;
- super.stopScroll();
- }
-
- @Override
- protected void onScrollChanged(int l, final int t, int ol, int ot) {
- super.onScrollChanged(l, t, ol, ot);
- if (!mIsCancelled) {
- post(this);
- } else {
- mIsCancelled = false;
- }
- }
-
- void setScrollListener(ScrollListener l) {
- mScrollListener = l;
- }
-
- // callback for scroll events
-
- interface ScrollListener {
- public void onScroll(int visibleTitleHeight, boolean userInitiated);
- }
-
protected Bitmap capture() {
if (mCapture == null) return null;
Canvas c = new Canvas(mCapture);
diff --git a/src/com/android/browser/BrowserWebViewFactory.java b/src/com/android/browser/BrowserWebViewFactory.java
new file mode 100644
index 0000000..fbd26a9
--- /dev/null
+++ b/src/com/android/browser/BrowserWebViewFactory.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.AttributeSet;
+import android.view.View;
+import android.webkit.WebView;
+
+/**
+ * Web view factory class for creating {@link BrowserWebView}'s.
+ */
+public class BrowserWebViewFactory implements WebViewFactory {
+
+ private final Context mContext;
+
+ public BrowserWebViewFactory(Context context) {
+ mContext = context;
+ }
+
+ protected WebView instantiateWebView(AttributeSet attrs, int defStyle,
+ boolean privateBrowsing) {
+ return new BrowserWebView(mContext, attrs, defStyle, privateBrowsing);
+ }
+
+ @Override
+ public WebView createSubWebView(boolean privateBrowsing) {
+ return createWebView(privateBrowsing);
+ }
+
+ @Override
+ public WebView createWebView(boolean privateBrowsing) {
+ WebView w = instantiateWebView(null, android.R.attr.webViewStyle, privateBrowsing);
+ initWebViewSettings(w);
+ return w;
+ }
+
+ protected void initWebViewSettings(WebView w) {
+ w.setScrollbarFadingEnabled(true);
+ w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
+ w.setMapTrackballToArrowKeys(false); // use trackball directly
+ // Enable the built-in zoom
+ w.getSettings().setBuiltInZoomControls(true);
+ boolean supportsMultiTouch = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
+ w.getSettings().setDisplayZoomControls(!supportsMultiTouch);
+ w.setExpandedTileBounds(true); // smoother scrolling
+
+ // Add this WebView to the settings observer list and update the
+ // settings
+ final BrowserSettings s = BrowserSettings.getInstance();
+ s.startManagingSettings(w.getSettings());
+ }
+
+}
diff --git a/src/com/android/browser/CombinedBookmarkHistoryView.java b/src/com/android/browser/CombinedBookmarkHistoryView.java
index 785b2cd..184314e 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryView.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryView.java
@@ -17,6 +17,8 @@
package com.android.browser;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.ActionBar.TabListener;
@@ -28,7 +30,6 @@
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Browser;
@@ -45,6 +46,8 @@
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import com.android.browser.UI.ComboViews;
+
import java.util.HashMap;
import java.util.Vector;
@@ -61,6 +64,7 @@
final static int INVALID_ID = 0;
final static int FRAGMENT_ID_BOOKMARKS = 1;
final static int FRAGMENT_ID_HISTORY = 2;
+ final static int FRAGMENT_ID_SNAPSHOTS = 3;
private UiController mUiController;
private Activity mActivity;
@@ -72,10 +76,13 @@
ActionBar.Tab mTabBookmarks;
ActionBar.Tab mTabHistory;
+ ActionBar.Tab mTabSnapshots;
ViewGroup mBookmarksHeader;
BrowserBookmarksPage mBookmarks;
BrowserHistoryPage mHistory;
+ BrowserSnapshotPage mSnapshots;
+ boolean mIsAnimating;
static class IconListenerSet implements IconListener {
// Used to store favicons as we get them from the database
@@ -115,7 +122,7 @@
}
public CombinedBookmarkHistoryView(Activity activity, UiController controller,
- int startingFragment, Bundle extras) {
+ ComboViews startingView, Bundle extras) {
super(activity);
mUiController = controller;
mActivity = activity;
@@ -124,7 +131,6 @@
View v = LayoutInflater.from(activity).inflate(R.layout.bookmarks_history, this);
v.setOnTouchListener(this);
- Resources res = activity.getResources();
// setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
@@ -152,11 +158,28 @@
}
}).execute();
- setupActionBar(startingFragment);
+ mIsAnimating = true;
+ setAlpha(0f);
+ Resources res = mActivity.getResources();
+ animate().alpha(1f)
+ .setDuration(res.getInteger(R.integer.comboViewFadeInDuration))
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mIsAnimating = false;
+ FragmentManager fm = mActivity.getFragmentManager();
+ FragmentTransaction ft = fm.beginTransaction();
+ onTabSelected(mActionBar.getSelectedTab(), ft);
+ ft.commit();
+ }
+ });
+
+ setupActionBar(startingView);
mUiController.registerOptionsMenuHandler(this);
}
- void setupActionBar(int startingFragment) {
+ void setupActionBar(ComboViews startingView) {
if (BrowserActivity.isTablet(mContext)) {
mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
| ActionBar.DISPLAY_USE_LOGO);
@@ -168,15 +191,32 @@
mTabBookmarks = mActionBar.newTab();
mTabBookmarks.setText(R.string.tab_bookmarks);
mTabBookmarks.setTabListener(this);
- mActionBar.addTab(mTabBookmarks, FRAGMENT_ID_BOOKMARKS == startingFragment);
+ mActionBar.addTab(mTabBookmarks, ComboViews.Bookmarks == startingView);
mTabHistory = mActionBar.newTab();
mTabHistory.setText(R.string.tab_history);
mTabHistory.setTabListener(this);
- mActionBar.addTab(mTabHistory, FRAGMENT_ID_HISTORY == startingFragment);
+ mActionBar.addTab(mTabHistory, ComboViews.History == startingView);
+ mTabSnapshots = mActionBar.newTab();
+ mTabSnapshots.setText(R.string.tab_snapshots);
+ mTabSnapshots.setTabListener(this);
+ mActionBar.addTab(mTabSnapshots, ComboViews.Snapshots == startingView);
mActionBar.setCustomView(mBookmarksHeader);
mActionBar.show();
}
+ void tearDownActionBar() {
+ if (mActionBar != null) {
+ mActionBar.removeAllTabs();
+ mTabBookmarks.setTabListener(null);
+ mTabHistory.setTabListener(null);
+ mTabSnapshots.setTabListener(null);
+ mTabBookmarks = null;
+ mTabHistory = null;
+ mTabSnapshots = null;
+ mActionBar = null;
+ }
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -210,6 +250,7 @@
mBookmarks = BrowserBookmarksPage.newInstance(mBookmarkCallbackWrapper,
extras, mBookmarksHeader);
mHistory = BrowserHistoryPage.newInstance(mUiController, extras);
+ mSnapshots = BrowserSnapshotPage.newInstance(mUiController, extras);
}
private void loadFragment(int id, FragmentTransaction ft) {
@@ -222,6 +263,9 @@
case FRAGMENT_ID_HISTORY:
ft.replace(R.id.fragment, mHistory);
break;
+ case FRAGMENT_ID_SNAPSHOTS:
+ ft.replace(R.id.fragment, mSnapshots);
+ break;
default:
throw new IllegalArgumentException();
}
@@ -231,6 +275,7 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ tearDownActionBar();
if (mCurrentFragment != INVALID_ID) {
try {
FragmentManager fm = mActivity.getFragmentManager();
@@ -239,6 +284,8 @@
transaction.remove(mBookmarks);
} else if (mCurrentFragment == FRAGMENT_ID_HISTORY) {
transaction.remove(mHistory);
+ } else if (mCurrentFragment == FRAGMENT_ID_SNAPSHOTS) {
+ transaction.remove(mSnapshots);
}
transaction.commit();
} catch (IllegalStateException ex) {
@@ -256,6 +303,9 @@
* callback for back key presses
*/
boolean onBackPressed() {
+ if (mIsAnimating) {
+ return true;
+ }
if (mCurrentFragment == FRAGMENT_ID_BOOKMARKS) {
return mBookmarks.onBackPressed();
}
@@ -278,10 +328,19 @@
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
+ if (mIsAnimating) {
+ // We delay set while animating (smooth animations)
+ // TODO: Signal to the fragment in advance so that it can start
+ // loading its data asynchronously
+ return;
+ }
+
if (tab == mTabBookmarks) {
loadFragment(FRAGMENT_ID_BOOKMARKS, ft);
} else if (tab == mTabHistory) {
loadFragment(FRAGMENT_ID_HISTORY, ft);
+ } else if (tab == mTabSnapshots) {
+ loadFragment(FRAGMENT_ID_SNAPSHOTS, ft);
}
}
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 447e61b..003d2d8 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -40,7 +40,6 @@
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;
@@ -68,6 +67,7 @@
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.HttpAuthHandler;
+import android.webkit.SearchBox;
import android.webkit.SslErrorHandler;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
@@ -77,9 +77,10 @@
import android.widget.Toast;
import com.android.browser.IntentHandler.UrlData;
+import com.android.browser.UI.ComboViews;
import com.android.browser.UI.DropdownChangeListener;
import com.android.browser.provider.BrowserProvider;
-import com.android.browser.provider.BrowserProvider2.Snapshots;
+import com.android.browser.provider.SnapshotProvider.Snapshots;
import com.android.browser.search.SearchEngine;
import com.android.common.Search;
@@ -229,6 +230,7 @@
mTabControl = new TabControl(this);
mSettings.setController(this);
mCrashRecoveryHandler = CrashRecoveryHandler.initialize(this);
+ mFactory = new BrowserWebViewFactory(browser);
mUrlHandler = new UrlHandler(this);
mIntentHandler = new IntentHandler(mActivity, this);
@@ -312,7 +314,7 @@
// If the intent is ACTION_VIEW and data is not null, the Browser is
// invoked to view the content by another application. In this case,
// the tab will be close when exit.
- UrlData urlData = mIntentHandler.getUrlDataFromIntent(intent);
+ UrlData urlData = IntentHandler.getUrlDataFromIntent(intent);
Tab t = null;
if (urlData.isEmpty()) {
t = openTabToHomePage();
@@ -329,7 +331,6 @@
webView.setInitialScale(scale);
}
}
- mTabControl.loadSnapshotTabs();
mUi.updateTabs(mTabControl.getTabs());
} else {
mTabControl.restoreState(icicle, currentTabId, restoreIncognitoTabs,
@@ -356,10 +357,6 @@
}
}
- void setWebViewFactory(WebViewFactory factory) {
- mFactory = factory;
- }
-
@Override
public WebViewFactory getWebViewFactory() {
return mFactory;
@@ -381,6 +378,11 @@
}
@Override
+ public Context getContext() {
+ return mActivity;
+ }
+
+ @Override
public Activity getActivity() {
return mActivity;
}
@@ -1177,7 +1179,8 @@
// Disable opening in a new window if we have maxed out the windows
extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW,
!mTabControl.canCreateNewTab());
- mUi.showComboView(startWithHistory, extras);
+ mUi.showComboView(startWithHistory
+ ? ComboViews.History : ComboViews.Bookmarks, extras);
}
// combo view callbacks
@@ -1518,11 +1521,9 @@
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"));
+ MenuItem saveSnapshot = menu.findItem(R.id.save_snapshot_menu_id);
+ Tab tab = getCurrentTab();
+ saveSnapshot.setVisible(tab != null && !tab.isSnapshot());
break;
}
mCurrentMenuState = mMenuState;
@@ -1617,82 +1618,32 @@
getCurrentTopWebView().showFindDialog(null, true);
break;
- case R.id.freeze_tab_menu_id:
- // TODO: Show error messages
- Tab source = getTabControl().getCurrentTab();
+ case R.id.save_snapshot_menu_id:
+ final Tab source = getTabControl().getCurrentTab();
if (source == null) break;
final ContentResolver cr = mActivity.getContentResolver();
final ContentValues values = source.createSnapshotValues();
- new AsyncTask<Tab, Void, Long>() {
- @Override
- protected Long doInBackground(Tab... params) {
- Tab t = params[0];
- if (values == null) {
- return t.isSnapshot()
- ? ((SnapshotTab)t).getSnapshotId()
- : -1;
- }
- Uri result = cr.insert(Snapshots.CONTENT_URI, values);
- long id = ContentUris.parseId(result);
- return id;
- }
+ if (values != null) {
+ new AsyncTask<Tab, Void, Long>() {
- protected void onPostExecute(Long id) {
- if (id > 0) {
- createNewSnapshotTab(id, true);
+ @Override
+ protected Long doInBackground(Tab... params) {
+ Uri result = cr.insert(Snapshots.CONTENT_URI, values);
+ long id = ContentUris.parseId(result);
+ return id;
}
- };
- }.execute(source);
- 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,
+ @Override
+ protected void onPostExecute(Long id) {
+ Bundle b = new Bundle();
+ b.putLong(BrowserSnapshotPage.EXTRA_ANIMATE_ID, id);
+ mUi.showComboView(ComboViews.Snapshots, b);
+ };
+ }.execute(source);
+ } else {
+ Toast.makeText(mActivity, R.string.snapshot_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.addCompletedDownload(
- 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:
@@ -1995,7 +1946,7 @@
return null;
}
- private static Bitmap createScreenshot(WebView view, int width, int height) {
+ static Bitmap createScreenshot(WebView view, int width, int height) {
// We render to a bitmap 2x the desired size so that we can then
// re-scale it with filtering since canvas.scale doesn't filter
// This helps reduce aliasing at the cost of being slightly blurry
@@ -2163,18 +2114,6 @@
mUi.removeTab(tab);
mTabControl.removeTab(tab);
mCrashRecoveryHandler.backupState();
- if (tab.isSnapshot()) {
- SnapshotTab st = (SnapshotTab) tab;
- final Uri uri = ContentUris.withAppendedId(
- Snapshots.CONTENT_URI, st.getSnapshotId());
- final ContentResolver cr = mActivity.getContentResolver();
- new Thread() {
- @Override
- public void run() {
- cr.delete(uri, null, null);
- }
- }.start();
- }
}
@Override
@@ -2244,12 +2183,35 @@
}
}
+ private Tab showPreloadedTab(final UrlData urlData) {
+ if (!urlData.isPreloaded()) {
+ return null;
+ }
+ final PreloadedTabControl tabControl = urlData.getPreloadedTab();
+ final String sbQuery = urlData.getSearchBoxQueryToSubmit();
+ if (sbQuery != null) {
+ if (!tabControl.searchBoxSubmit(sbQuery, urlData.mUrl, urlData.mHeaders)) {
+ // Could not submit query. Fallback to regular tab creation
+ tabControl.destroy();
+ return null;
+ }
+ }
+ Tab t = tabControl.getTab();
+ mTabControl.addPreloadedTab(t);
+ addTab(t);
+ setActiveTab(t);
+ return t;
+ }
+
// open a non inconito tab with the given url data
// and set as active tab
public Tab openTab(UrlData urlData) {
- Tab tab = createNewTab(false, true, true);
- if ((tab != null) && !urlData.isEmpty()) {
- loadUrlDataIn(tab, urlData);
+ Tab tab = showPreloadedTab(urlData);
+ if (tab == null) {
+ tab = createNewTab(false, true, true);
+ if ((tab != null) && !urlData.isEmpty()) {
+ loadUrlDataIn(tab, urlData);
+ }
}
return tab;
}
@@ -2315,11 +2277,17 @@
return tab;
}
- private SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive) {
- SnapshotTab tab = mTabControl.createSnapshotTab(snapshotId);
- addTab(tab);
- if (setActive) {
- setActiveTab(tab);
+ @Override
+ public SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive) {
+ SnapshotTab tab = null;
+ if (mTabControl.canCreateNewTab()) {
+ tab = mTabControl.createSnapshotTab(snapshotId);
+ addTab(tab);
+ if (setActive) {
+ setActiveTab(tab);
+ }
+ } else {
+ mUi.showMaxTabsWarning();
}
return tab;
}
@@ -2396,7 +2364,8 @@
* @param view The WebView used to load url.
* @param url The URL to load.
*/
- protected void loadUrl(Tab tab, String url) {
+ @Override
+ public void loadUrl(Tab tab, String url) {
loadUrl(tab, url, null);
}
@@ -2417,6 +2386,8 @@
if (data != null) {
if (data.mVoiceIntent != null) {
t.activateVoiceSearchMode(data.mVoiceIntent);
+ } else if (data.isPreloaded()) {
+ // this isn't called for preloaded tabs
} else {
loadUrl(t, data.mUrl, data.mHeaders);
}
diff --git a/src/com/android/browser/CrashRecoveryHandler.java b/src/com/android/browser/CrashRecoveryHandler.java
index 5140952..ca538bd 100644
--- a/src/com/android/browser/CrashRecoveryHandler.java
+++ b/src/com/android/browser/CrashRecoveryHandler.java
@@ -31,6 +31,7 @@
import android.util.Log;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -120,8 +121,8 @@
Parcel p = Parcel.obtain();
try {
mState.writeToParcel(p, 0);
- FileOutputStream fout = mContext.openFileOutput(STATE_FILE,
- Context.MODE_PRIVATE);
+ File state = new File(mContext.getCacheDir(), STATE_FILE);
+ FileOutputStream fout = new FileOutputStream(state);
fout.write(p.marshall());
fout.close();
} catch (Throwable e) {
@@ -134,7 +135,10 @@
}
private static void clearState(Context context) {
- context.deleteFile(STATE_FILE);
+ File state = new File(context.getCacheDir(), STATE_FILE);
+ if (state.exists()) {
+ state.delete();
+ }
}
public void promptToRecover(final Bundle state, final Intent intent) {
@@ -166,6 +170,8 @@
}
private boolean shouldPrompt() {
+ // STOPSHIP TODO: Remove once b/4971724 is fixed
+ if (true) return false;
Context context = mController.getActivity();
SharedPreferences prefs = context.getSharedPreferences(
RECOVERY_PREFERENCES, Context.MODE_PRIVATE);
@@ -192,7 +198,8 @@
Parcel parcel = Parcel.obtain();
try {
Context context = mController.getActivity();
- FileInputStream fin = context.openFileInput(STATE_FILE);
+ File stateFile = new File(context.getCacheDir(), STATE_FILE);
+ FileInputStream fin = new FileInputStream(stateFile);
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
byte[] buffer = new byte[BUFFER_SIZE];
int read;
diff --git a/src/com/android/browser/GeolocationPermissionsPrompt.java b/src/com/android/browser/GeolocationPermissionsPrompt.java
index 95c5415..afbf39f 100755
--- a/src/com/android/browser/GeolocationPermissionsPrompt.java
+++ b/src/com/android/browser/GeolocationPermissionsPrompt.java
@@ -17,22 +17,18 @@
package com.android.browser;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.LayoutInflater;
import android.view.View;
-import android.webkit.WebView;
import android.webkit.GeolocationPermissions;
import android.widget.Button;
import android.widget.CheckBox;
-import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
-public class GeolocationPermissionsPrompt extends LinearLayout {
- private LinearLayout mInner;
+public class GeolocationPermissionsPrompt extends RelativeLayout {
private TextView mMessage;
private Button mShareButton;
private Button mDontShareButton;
@@ -48,22 +44,26 @@
super(context, attrs);
}
- void init() {
- mInner = (LinearLayout) findViewById(R.id.inner);
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ init();
+ }
+
+ private void init() {
mMessage = (TextView) findViewById(R.id.message);
mShareButton = (Button) findViewById(R.id.share_button);
mDontShareButton = (Button) findViewById(R.id.dont_share_button);
mRemember = (CheckBox) findViewById(R.id.remember);
- final GeolocationPermissionsPrompt me = this;
mShareButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
- me.handleButtonClick(true);
+ handleButtonClick(true);
}
});
mDontShareButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
- me.handleButtonClick(false);
+ handleButtonClick(false);
}
});
}
@@ -79,21 +79,21 @@
setMessage("http".equals(uri.getScheme()) ? mOrigin.substring(7) : mOrigin);
// The checkbox should always be intially checked.
mRemember.setChecked(true);
- showDialog(true);
+ setVisibility(View.VISIBLE);
}
/**
* Hides the prompt.
*/
public void hide() {
- showDialog(false);
+ setVisibility(View.GONE);
}
/**
* Handles a click on one the buttons by invoking the callback.
*/
private void handleButtonClick(boolean allow) {
- showDialog(false);
+ hide();
boolean remember = mRemember.isChecked();
if (remember) {
@@ -117,11 +117,4 @@
getResources().getString(R.string.geolocation_permissions_prompt_message),
origin));
}
-
- /**
- * Shows or hides the prompt.
- */
- private void showDialog(boolean shown) {
- mInner.setVisibility(shown ? View.VISIBLE : View.GONE);
- }
}
diff --git a/src/com/android/browser/IntentHandler.java b/src/com/android/browser/IntentHandler.java
index 088a788..1a72a23 100644
--- a/src/com/android/browser/IntentHandler.java
+++ b/src/com/android/browser/IntentHandler.java
@@ -135,8 +135,9 @@
urlData = new UrlData(mSettings.getHomePage());
}
- if (intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false)) {
- mController.openTab(urlData);
+ if (intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false)
+ || urlData.isPreloaded()) {
+ Tab t = mController.openTab(urlData);
return;
}
/*
@@ -220,9 +221,11 @@
}
}
- protected UrlData getUrlDataFromIntent(Intent intent) {
+ protected static UrlData getUrlDataFromIntent(Intent intent) {
String url = "";
Map<String, String> headers = null;
+ PreloadedTabControl preloaded = null;
+ String preloadedSearchBoxQuery = null;
if (intent != null
&& (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
final String action = intent.getAction();
@@ -241,6 +244,12 @@
}
}
}
+ if (intent.hasExtra(PreloadRequestReceiver.EXTRA_PRELOAD_ID)) {
+ String id = intent.getStringExtra(PreloadRequestReceiver.EXTRA_PRELOAD_ID);
+ preloadedSearchBoxQuery = intent.getStringExtra(
+ PreloadRequestReceiver.EXTRA_SEARCHBOX_SETQUERY);
+ preloaded = Preloader.getInstance().getPreloadedTab(id);
+ }
} else if (Intent.ACTION_SEARCH.equals(action)
|| MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
|| Intent.ACTION_WEB_SEARCH.equals(action)) {
@@ -265,7 +274,7 @@
}
}
}
- return new UrlData(url, headers, intent);
+ return new UrlData(url, headers, intent, preloaded, preloadedSearchBoxQuery);
}
/**
@@ -348,14 +357,23 @@
final String mUrl;
final Map<String, String> mHeaders;
final Intent mVoiceIntent;
+ final PreloadedTabControl mPreloadedTab;
+ final String mSearchBoxQueryToSubmit;
UrlData(String url) {
this.mUrl = url;
this.mHeaders = null;
this.mVoiceIntent = null;
+ this.mPreloadedTab = null;
+ this.mSearchBoxQueryToSubmit = null;
}
UrlData(String url, Map<String, String> headers, Intent intent) {
+ this(url, headers, intent, null, null);
+ }
+
+ UrlData(String url, Map<String, String> headers, Intent intent,
+ PreloadedTabControl preloaded, String searchBoxQueryToSubmit) {
this.mUrl = url;
this.mHeaders = headers;
if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
@@ -364,11 +382,25 @@
} else {
this.mVoiceIntent = null;
}
+ this.mPreloadedTab = preloaded;
+ this.mSearchBoxQueryToSubmit = searchBoxQueryToSubmit;
}
boolean isEmpty() {
return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0);
}
+
+ boolean isPreloaded() {
+ return mPreloadedTab != null;
+ }
+
+ PreloadedTabControl getPreloadedTab() {
+ return mPreloadedTab;
+ }
+
+ String getSearchBoxQueryToSubmit() {
+ return mSearchBoxQueryToSubmit;
+ }
}
}
diff --git a/src/com/android/browser/NfcHandler.java b/src/com/android/browser/NfcHandler.java
index bdfe25e..bc19950 100644
--- a/src/com/android/browser/NfcHandler.java
+++ b/src/com/android/browser/NfcHandler.java
@@ -20,6 +20,7 @@
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
+import android.os.AsyncTask;
/** This class implements sharing the URL of the currently
* shown browser page over NFC. Sharing is only active
@@ -31,6 +32,37 @@
private Activity mActivity;
private Controller mController;
+ /** We need an async task to check whether the tab is private
+ * on the UI thread.
+ */
+ private class CreateMessageTask extends AsyncTask<Void, Void, NdefMessage> {
+ private boolean mIsPrivate = false;
+ private Tab mCurrentTab;
+
+ @Override
+ protected void onPreExecute() {
+ mCurrentTab = mController.getCurrentTab();
+ if ((mCurrentTab != null) && (mCurrentTab.getWebView() != null)) {
+ mIsPrivate = mCurrentTab.getWebView().isPrivateBrowsingEnabled();
+ }
+ }
+
+ @Override
+ protected NdefMessage doInBackground(Void... params) {
+ if ((mCurrentTab == null) || mIsPrivate) {
+ return null;
+ }
+ String currentUrl = mCurrentTab.getUrl();
+ if (currentUrl != null) {
+ NdefRecord record = NdefRecord.createUri(currentUrl);
+ NdefMessage msg = new NdefMessage(new NdefRecord[] { record });
+ return msg;
+ } else {
+ return null;
+ }
+ }
+ }
+
public NfcHandler(Activity browser, Controller controller) {
mActivity = browser;
mController = controller;
@@ -51,18 +83,11 @@
@Override
public NdefMessage createMessage() {
- Tab currentTab = mController.getCurrentTab();
- if (currentTab == null) {
- return null;
- }
- String currentUrl = currentTab.getUrl();
- if (currentUrl != null && currentTab.getWebView() != null &&
- !currentTab.getWebView().isPrivateBrowsingEnabled()) {
- NdefRecord record = new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI,
- NdefRecord.RTD_URI, new byte[] {}, currentUrl.getBytes());
- NdefMessage msg = new NdefMessage(new NdefRecord[] { record });
- return msg;
- } else {
+ CreateMessageTask task = new CreateMessageTask();
+ task.execute();
+ try {
+ return task.get();
+ } catch (Exception e) {
return null;
}
}
diff --git a/src/com/android/browser/PageDialogsHandler.java b/src/com/android/browser/PageDialogsHandler.java
index b39d863..015a9bb 100644
--- a/src/com/android/browser/PageDialogsHandler.java
+++ b/src/com/android/browser/PageDialogsHandler.java
@@ -249,7 +249,7 @@
LayoutInflater factory = LayoutInflater.from(mContext);
final LinearLayout placeholder =
- (LinearLayout)certificateView.findViewById(R.id.placeholder);
+ (LinearLayout)certificateView.findViewById(com.android.internal.R.id.placeholder);
LinearLayout ll = (LinearLayout) factory.inflate(
R.layout.ssl_success, placeholder);
@@ -304,7 +304,7 @@
LayoutInflater factory = LayoutInflater.from(mContext);
final LinearLayout placeholder =
- (LinearLayout)certificateView.findViewById(R.id.placeholder);
+ (LinearLayout)certificateView.findViewById(com.android.internal.R.id.placeholder);
if (error.hasError(SslError.SSL_UNTRUSTED)) {
LinearLayout ll = (LinearLayout)factory
diff --git a/src/com/android/browser/PhoneUi.java b/src/com/android/browser/PhoneUi.java
index d0b0f77..0c236af 100644
--- a/src/com/android/browser/PhoneUi.java
+++ b/src/com/android/browser/PhoneUi.java
@@ -36,7 +36,6 @@
private TitleBarPhone mTitleBar;
private ActiveTabsPage mActiveTabsPage;
- private boolean mUseQuickControls;
private PieControl mPieControl;
private NavScreen mNavScreen;
@@ -164,13 +163,11 @@
// Request focus on the top window.
if (mUseQuickControls) {
mPieControl.forceToTop(mContentView);
- view.setScrollListener(null);
} else {
// check if title bar is already attached by animation
if (mTitleBar.getParent() == null) {
view.setEmbeddedTitleBar(mTitleBar);
}
- view.setScrollListener(this);
}
if (tab.isInVoiceSearchMode()) {
showVoiceTitleBar(tab.getVoiceDisplayTitle(), tab.getVoiceSearchResults());
@@ -209,11 +206,11 @@
}
@Override
- public void showComboView(boolean startWithHistory, Bundle extras) {
+ public void showComboView(ComboViews startWith, Bundle extras) {
if (mNavScreen != null) {
hideNavScreen(false);
}
- super.showComboView(startWithHistory, extras);
+ super.showComboView(startWith, extras);
}
@Override
diff --git a/src/com/android/browser/PreferenceKeys.java b/src/com/android/browser/PreferenceKeys.java
index bc8d38f..144d505 100644
--- a/src/com/android/browser/PreferenceKeys.java
+++ b/src/com/android/browser/PreferenceKeys.java
@@ -94,4 +94,9 @@
static final String PREF_SAVE_FORMDATA = "save_formdata";
static final String PREF_SHOW_SECURITY_WARNINGS = "show_security_warnings";
+ // ----------------------
+ // Keys for bandwidth_preferences.xml
+ // ----------------------
+ static final String PREF_DATA_PRELOAD = "preload_enabled";
+
}
diff --git a/src/com/android/browser/PreloadController.java b/src/com/android/browser/PreloadController.java
new file mode 100644
index 0000000..6528410
--- /dev/null
+++ b/src/com/android/browser/PreloadController.java
@@ -0,0 +1,224 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.net.http.SslError;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.view.View;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient.CustomViewCallback;
+import android.webkit.WebView;
+
+import java.util.List;
+
+public class PreloadController implements WebViewController {
+
+ private Context mContext;
+
+ public PreloadController(Context ctx) {
+ mContext = ctx;
+
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public Activity getActivity() {
+ return null;
+ }
+
+ @Override
+ public TabControl getTabControl() {
+ return null;
+ }
+
+ @Override
+ public WebViewFactory getWebViewFactory() {
+ return null;
+ }
+
+ @Override
+ public void onSetWebView(Tab tab, WebView view) {
+ }
+
+ @Override
+ public void createSubWindow(Tab tab) {
+ }
+
+ @Override
+ public void onPageStarted(Tab tab, WebView view, Bitmap favicon) {
+ }
+
+ @Override
+ public void onPageFinished(Tab tab) {
+ }
+
+ @Override
+ public void onProgressChanged(Tab tab) {
+ }
+
+ @Override
+ public void onReceivedTitle(Tab tab, String title) {
+ }
+
+ @Override
+ public void onFavicon(Tab tab, WebView view, Bitmap icon) {
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
+ return false;
+ }
+
+ @Override
+ public boolean shouldOverrideKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public void onUnhandledKeyEvent(KeyEvent event) {
+ }
+
+ @Override
+ public void doUpdateVisitedHistory(Tab tab, boolean isReload) {
+ }
+
+ @Override
+ public void getVisitedHistory(ValueCallback<String[]> callback) {
+ }
+
+ @Override
+ public void onReceivedHttpAuthRequest(Tab tab, WebView view,
+ HttpAuthHandler handler, String host,
+ String realm) {
+ }
+
+ @Override
+ public void onDownloadStart(Tab tab, String url, String useragent,
+ String contentDisposition, String mimeType,
+ long contentLength) {
+ }
+
+ @Override
+ public void showCustomView(Tab tab, View view, int requestedOrientation,
+ CustomViewCallback callback) {
+ }
+
+ @Override
+ public void hideCustomView() {
+ }
+
+ @Override
+ public Bitmap getDefaultVideoPoster() {
+ return null;
+ }
+
+ @Override
+ public View getVideoLoadingProgressView() {
+ return null;
+ }
+
+ @Override
+ public void showSslCertificateOnError(WebView view,
+ SslErrorHandler handler, SslError error) {
+ }
+
+ @Override
+ public void onUserCanceledSsl(Tab tab) {
+ }
+
+ @Override
+ public void activateVoiceSearchMode(String title, List<String> results) {
+ }
+
+ @Override
+ public void revertVoiceSearchMode(Tab tab) {
+ }
+
+ @Override
+ public boolean shouldShowErrorConsole() {
+ return false;
+ }
+
+ @Override
+ public void onUpdatedLockIcon(Tab tab) {
+ }
+
+ @Override
+ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
+ }
+
+ @Override
+ public void endActionMode() {
+ }
+
+ @Override
+ public void attachSubWindow(Tab tab) {
+ }
+
+ @Override
+ public void dismissSubWindow(Tab tab) {
+ }
+
+ @Override
+ public Tab openTab(String url, boolean incognito, boolean setActive,
+ boolean useCurrent) {
+ return null;
+ }
+
+ @Override
+ public Tab openTab(String url, Tab parent, boolean setActive,
+ boolean useCurrent) {
+ return null;
+ }
+
+ @Override
+ public boolean switchToTab(Tab tab) {
+ return false;
+ }
+
+ @Override
+ public void closeTab(Tab tab) {
+ }
+
+ @Override
+ public void setupAutoFill(Message message) {
+ }
+
+ @Override
+ public void bookmarkedStatusHasChanged(Tab tab) {
+ }
+
+ @Override
+ public void showAutoLogin(Tab tab) {
+ }
+
+ @Override
+ public void hideAutoLogin(Tab tab) {
+ }
+
+}
diff --git a/src/com/android/browser/PreloadRequestReceiver.java b/src/com/android/browser/PreloadRequestReceiver.java
new file mode 100644
index 0000000..5176176
--- /dev/null
+++ b/src/com/android/browser/PreloadRequestReceiver.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.Browser;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Broadcast receiver for receiving browser preload requests
+ */
+public class PreloadRequestReceiver extends BroadcastReceiver {
+
+ private final static String LOGTAG = "browser.preloader";
+ private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
+
+ private static final String ACTION_PRELOAD = "android.intent.action.PRELOAD";
+ static final String EXTRA_PRELOAD_ID = "preload_id";
+ static final String EXTRA_PRELOAD_DISCARD = "preload_discard";
+ static final String EXTRA_SEARCHBOX_SETQUERY = "searchbox_query";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "received intent " + intent);
+ if (BrowserSettings.getInstance().isPreloadEnabled()
+ && intent.getAction().equals(ACTION_PRELOAD)) {
+ handlePreload(context, intent);
+ }
+ }
+
+ private void handlePreload(Context context, Intent i) {
+ String url = UrlUtils.smartUrlFilter(i.getData());
+ String id = i.getStringExtra(EXTRA_PRELOAD_ID);
+ Map<String, String> headers = null;
+ if (id == null) {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Preload request has no " + EXTRA_PRELOAD_ID);
+ return;
+ }
+ if (i.getBooleanExtra(EXTRA_PRELOAD_DISCARD, false)) {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Got " + id + " preload discard request");
+ Preloader.getInstance().discardPreload(id);
+ } else {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Got " + id + " preload request for " + url);
+ if (url != null && url.startsWith("http")) {
+ final Bundle pairs = i.getBundleExtra(Browser.EXTRA_HEADERS);
+ if (pairs != null && !pairs.isEmpty()) {
+ Iterator<String> iter = pairs.keySet().iterator();
+ headers = new HashMap<String, String>();
+ while (iter.hasNext()) {
+ String key = iter.next();
+ headers.put(key, pairs.getString(key));
+ }
+ }
+ }
+ String sbQuery = i.getStringExtra(EXTRA_SEARCHBOX_SETQUERY);
+ if (url != null) {
+ if (LOGD_ENABLED){
+ Log.d(LOGTAG, "Preload request(" + id + ", " + url + ", " +
+ headers + ", " + sbQuery + ")");
+ }
+ Preloader.getInstance().handlePreloadRequest(id, url, headers, sbQuery);
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/browser/PreloadedTabControl.java b/src/com/android/browser/PreloadedTabControl.java
new file mode 100644
index 0000000..99592fb
--- /dev/null
+++ b/src/com/android/browser/PreloadedTabControl.java
@@ -0,0 +1,107 @@
+/*
+ * 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;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.SearchBox;
+
+import java.util.Map;
+
+/**
+ * Class to manage the controlling of preloaded tab.
+ */
+public class PreloadedTabControl {
+ private static final boolean LOGD_ENABLED = true;//com.android.browser.Browser.LOGD_ENABLED;
+ private static final String LOGTAG = "PreloadedTabControl";
+
+ final Tab mTab;
+ private String mLastQuery;
+ private boolean mDestroyed;
+
+ public PreloadedTabControl(Tab t) {
+ mTab = t;
+ }
+
+ private void maybeSetQuery(String query, SearchBox sb) {
+ if (!TextUtils.equals(mLastQuery, query)) {
+ if (sb != null) {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Changing searchbox query to " + query);
+ sb.setVerbatim(true);
+ sb.setQuery(query);
+ sb.onchange();
+ mLastQuery = query;
+ } else {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Cannot set query: no searchbox interface");
+ }
+ }
+ }
+
+ public void setQuery(String query) {
+ maybeSetQuery(query, mTab.getWebView().getSearchBox());
+ }
+
+ public boolean searchBoxSubmit(final String query,
+ final String fallbackUrl, final Map<String, String> fallbackHeaders) {
+ final SearchBox sb = mTab.getWebView().getSearchBox();
+ if (sb == null) {
+ // no searchbox, cannot submit. Fallback to regular tab creation
+ if (LOGD_ENABLED) Log.d(LOGTAG, "No searchbox, cannot submit query");
+ return false;
+ }
+ sb.isSupported(new SearchBox.IsSupportedCallback() {
+ @Override
+ public void searchBoxIsSupported(boolean supported) {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "SearchBox supported: " + supported);
+ if (mDestroyed) {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "tab has been destroyed");
+ return;
+ }
+ if (supported) {
+ maybeSetQuery(query, sb);
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Submitting query " + query);
+ sb.onsubmit();
+ } else {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "SearchBox not supported; falling back");
+ loadUrl(fallbackUrl, fallbackHeaders);
+ }
+ mTab.getWebView().clearHistory();
+ }
+ });
+ return true;
+ }
+
+ public void loadUrlIfChanged(String url, Map<String, String> headers) {
+ if (!TextUtils.equals(url, mTab.getUrl())) {
+ loadUrl(url, headers);
+ }
+ }
+
+ public void loadUrl(String url, Map<String, String> headers) {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Preloading " + url);
+ mTab.loadUrl(url, headers);
+ }
+
+ public void destroy() {
+ mDestroyed = true;
+ mTab.destroy();
+ }
+
+ public Tab getTab() {
+ return mTab;
+ }
+
+}
diff --git a/src/com/android/browser/Preloader.java b/src/com/android/browser/Preloader.java
new file mode 100644
index 0000000..336b77a
--- /dev/null
+++ b/src/com/android/browser/Preloader.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Singleton class for handling preload requests.
+ */
+public class Preloader {
+
+ private final static String LOGTAG = "browser.preloader";
+ private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
+
+ private static final int PRERENDER_TIMEOUT_MILLIS = 30 * 1000; // 30s
+
+ private static Preloader sInstance;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final BrowserWebViewFactory mFactory;
+ private final HashMap<String, PreloaderSession> mSessions;
+
+ public static void initialize(Context context) {
+ sInstance = new Preloader(context);
+ }
+
+ public static Preloader getInstance() {
+ return sInstance;
+ }
+
+ private Preloader(Context context) {
+ mContext = context;
+ mHandler = new Handler(Looper.getMainLooper());
+ mSessions = new HashMap<String, PreloaderSession>();
+ mFactory = new BrowserWebViewFactory(context);
+
+ }
+
+ private PreloaderSession getSession(String id) {
+ PreloaderSession s = mSessions.get(id);
+ if (s == null) {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Create new preload session " + id);
+ s = new PreloaderSession(id);
+ mSessions.put(id, s);
+ }
+ return s;
+ }
+
+ private PreloaderSession takeSession(String id) {
+ PreloaderSession s = mSessions.remove(id);
+ if (s != null) {
+ s.cancelTimeout();
+ }
+ return s;
+ }
+
+ public void handlePreloadRequest(String id, String url, Map<String, String> headers,
+ String searchBoxQuery) {
+ PreloaderSession s = getSession(id);
+ s.touch(); // reset timer
+ PreloadedTabControl tab = s.getTabControl();
+ if (searchBoxQuery != null) {
+ tab.loadUrlIfChanged(url, headers);
+ tab.setQuery(searchBoxQuery);
+ } else {
+ tab.loadUrl(url, headers);
+ }
+ }
+
+ public void discardPreload(String id) {
+ PreloaderSession s = takeSession(id);
+ if (s != null) {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Discard preload session " + id);
+ PreloadedTabControl t = s.getTabControl();
+ t.destroy();
+ } else {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Ignored discard request " + id);
+ }
+ }
+
+ /**
+ * Return a preloaded tab, and remove it from the preloader. This is used when the
+ * view is about to be displayed.
+ */
+ public PreloadedTabControl getPreloadedTab(String id) {
+ PreloaderSession s = takeSession(id);
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Showing preload session " + id + "=" + s);
+ return s == null ? null : s.getTabControl();
+ }
+
+ private class PreloaderSession {
+ private final String mId;
+ private final PreloadedTabControl mTabControl;
+
+ private final Runnable mTimeoutTask = new Runnable(){
+ @Override
+ public void run() {
+ if (LOGD_ENABLED) Log.d(LOGTAG, "Preload session timeout " + mId);
+ discardPreload(mId);
+ }};
+
+ public PreloaderSession(String id) {
+ mId = id;
+ mTabControl = new PreloadedTabControl(
+ new Tab(new PreloadController(mContext), mFactory.createWebView(false)));
+ touch();
+ }
+
+ public void cancelTimeout() {
+ mHandler.removeCallbacks(mTimeoutTask);
+ }
+
+ public void touch() {
+ cancelTimeout();
+ mHandler.postDelayed(mTimeoutTask, PRERENDER_TIMEOUT_MILLIS);
+ }
+
+ public PreloadedTabControl getTabControl() {
+ return mTabControl;
+ }
+
+ }
+
+}
diff --git a/src/com/android/browser/SnapshotTab.java b/src/com/android/browser/SnapshotTab.java
index 52a5c5f..f0abf58 100644
--- a/src/com/android/browser/SnapshotTab.java
+++ b/src/com/android/browser/SnapshotTab.java
@@ -20,19 +20,21 @@
import android.content.ContentValues;
import android.database.Cursor;
import android.graphics.BitmapFactory;
-import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Bundle;
+import android.util.Log;
import android.webkit.WebView;
-import com.android.browser.provider.BrowserProvider2.Snapshots;
+import com.android.browser.provider.SnapshotProvider.Snapshots;
import java.io.ByteArrayInputStream;
+import java.util.zip.GZIPInputStream;
public class SnapshotTab extends Tab {
+ private static final String LOGTAG = "SnapshotTab";
+
private long mSnapshotId;
private LoadData mLoadTask;
private WebViewFactory mWebViewFactory;
@@ -76,7 +78,7 @@
void loadData() {
if (mLoadTask == null) {
- mLoadTask = new LoadData(this, mActivity.getContentResolver());
+ mLoadTask = new LoadData(this, mContext.getContentResolver());
mLoadTask.execute();
}
}
@@ -145,8 +147,13 @@
WebView web = mTab.getWebView();
if (web != null) {
byte[] data = result.getBlob(4);
- ByteArrayInputStream stream = new ByteArrayInputStream(data);
- web.loadViewState(stream);
+ ByteArrayInputStream bis = new ByteArrayInputStream(data);
+ try {
+ GZIPInputStream stream = new GZIPInputStream(bis);
+ web.loadViewState(stream);
+ } catch (Exception e) {
+ Log.w(LOGTAG, "Failed to load view state", e);
+ }
}
mTab.mBackgroundColor = result.getInt(5);
mTab.mWebViewController.onPageFinished(mTab);
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index e672e2b..911726c 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
@@ -63,7 +64,7 @@
import android.widget.Toast;
import com.android.browser.homepages.HomeProvider;
-import com.android.browser.provider.BrowserProvider2.Snapshots;
+import com.android.browser.provider.SnapshotProvider.Snapshots;
import com.android.common.speech.LoggingEvents;
import java.io.ByteArrayOutputStream;
@@ -73,6 +74,7 @@
import java.util.LinkedList;
import java.util.Map;
import java.util.Vector;
+import java.util.zip.GZIPOutputStream;
/**
* Class for maintaining Tabs with a main WebView and a subwindow.
@@ -92,7 +94,7 @@
LOCK_ICON_MIXED,
}
- Activity mActivity;
+ Context mContext;
protected WebViewController mWebViewController;
// The tab ID
@@ -304,7 +306,7 @@
logIntent.putExtra(
LoggingEvents.VoiceSearch.EXTRA_N_BEST_CHOOSE_INDEX,
index);
- mActivity.sendBroadcast(logIntent);
+ mContext.sendBroadcast(logIntent);
}
if (mVoiceSearchData.mVoiceSearchIntent != null) {
// Copy the Intent, so that each history item will have its own
@@ -487,7 +489,7 @@
private void showError(ErrorDialog errDialog) {
if (mInForeground) {
- AlertDialog d = new AlertDialog.Builder(mActivity)
+ AlertDialog d = new AlertDialog.Builder(mContext)
.setTitle(errDialog.mTitle)
.setMessage(errDialog.mDescription)
.setPositiveButton(R.string.ok, null)
@@ -508,7 +510,7 @@
public void onPageStarted(WebView view, String url, Bitmap favicon) {
mInPageLoad = true;
mPageLoadProgress = 0;
- mCurrentState = new PageState(mActivity,
+ mCurrentState = new PageState(mContext,
view.isPrivateBrowsingEnabled(), url, favicon);
mLoadStartTime = SystemClock.uptimeMillis();
if (mVoiceSearchData != null
@@ -516,7 +518,7 @@
if (mVoiceSearchData.mSourceIsGoogle) {
Intent i = new Intent(LoggingEvents.ACTION_LOG_EVENT);
i.putExtra(LoggingEvents.EXTRA_FLUSH, true);
- mActivity.sendBroadcast(i);
+ mContext.sendBroadcast(i);
}
revertVoiceSearchMode();
}
@@ -587,7 +589,7 @@
Intent logIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT);
logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
LoggingEvents.VoiceSearch.RESULT_CLICKED);
- mActivity.sendBroadcast(logIntent);
+ mContext.sendBroadcast(logIntent);
}
if (mInForeground) {
return mWebViewController.shouldOverrideUrlLoading(Tab.this,
@@ -659,7 +661,7 @@
}
mDontResend = dontResend;
mResend = resend;
- new AlertDialog.Builder(mActivity).setTitle(
+ new AlertDialog.Builder(mContext).setTitle(
R.string.browserFrameFormResubmitLabel).setMessage(
R.string.browserFrameFormResubmitMessage)
.setPositiveButton(R.string.ok,
@@ -718,7 +720,7 @@
}
if (mSettings.showSecurityWarnings()) {
final LayoutInflater factory =
- LayoutInflater.from(mActivity);
+ LayoutInflater.from(mContext);
final View warningsView =
factory.inflate(R.layout.ssl_warnings, null);
final LinearLayout placeholder =
@@ -756,7 +758,7 @@
placeholder.addView(ll);
}
- new AlertDialog.Builder(mActivity).setTitle(
+ new AlertDialog.Builder(mContext).setTitle(
R.string.security_warning).setIcon(
android.R.drawable.ic_dialog_alert).setView(
warningsView).setPositiveButton(R.string.ssl_continue,
@@ -817,13 +819,14 @@
port = -1;
}
}
- KeyChain.choosePrivateKeyAlias(mActivity, new KeyChainAliasCallback() {
+ KeyChain.choosePrivateKeyAlias(
+ mWebViewController.getActivity(), new KeyChainAliasCallback() {
@Override public void alias(String alias) {
if (alias == null) {
handler.cancel();
return;
}
- new KeyChainLookup(mActivity, handler, alias).execute();
+ new KeyChainLookup(mContext, handler, alias).execute();
}
}, null, null, host, port, null);
}
@@ -846,7 +849,7 @@
public WebResourceResponse shouldInterceptRequest(WebView view,
String url) {
WebResourceResponse res = HomeProvider.shouldInterceptRequest(
- mActivity, url);
+ mContext, url);
return res;
}
@@ -869,7 +872,7 @@
@Override
public void onReceivedLoginRequest(WebView view, String realm,
String account, String args) {
- new DeviceAccountLogin(mActivity, view, Tab.this, mWebViewController)
+ new DeviceAccountLogin(mWebViewController.getActivity(), view, Tab.this, mWebViewController)
.handleLogin(realm, account, args);
}
@@ -916,7 +919,7 @@
}
// Short-circuit if we can't create any more tabs or sub windows.
if (dialog && mSubView != null) {
- new AlertDialog.Builder(mActivity)
+ new AlertDialog.Builder(mContext)
.setTitle(R.string.too_many_subwindows_dialog_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.too_many_subwindows_dialog_message)
@@ -924,7 +927,7 @@
.show();
return false;
} else if (!mWebViewController.getTabControl().canCreateNewTab()) {
- new AlertDialog.Builder(mActivity)
+ new AlertDialog.Builder(mContext)
.setTitle(R.string.too_many_windows_dialog_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.too_many_windows_dialog_message)
@@ -958,7 +961,7 @@
// Build a confirmation dialog to display to the user.
final AlertDialog d =
- new AlertDialog.Builder(mActivity)
+ new AlertDialog.Builder(mContext)
.setTitle(R.string.attention)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.popup_window_attempt)
@@ -1011,7 +1014,7 @@
@Override
public void onReceivedTouchIconUrl(WebView view, String url,
boolean precomposed) {
- final ContentResolver cr = mActivity.getContentResolver();
+ final ContentResolver cr = mContext.getContentResolver();
// Let precomposed icons take precedence over non-composed
// icons.
if (precomposed && mTouchIconLoader != null) {
@@ -1021,7 +1024,7 @@
// Have only one async task at a time.
if (mTouchIconLoader == null) {
mTouchIconLoader = new DownloadTouchIcon(Tab.this,
- mActivity, cr, view);
+ mContext, cr, view);
mTouchIconLoader.execute(url);
}
}
@@ -1029,7 +1032,10 @@
@Override
public void onShowCustomView(View view,
WebChromeClient.CustomViewCallback callback) {
- onShowCustomView(view, mActivity.getRequestedOrientation(), callback);
+ Activity activity = mWebViewController.getActivity();
+ if (activity != null) {
+ onShowCustomView(view, activity.getRequestedOrientation(), callback);
+ }
}
@Override
@@ -1202,8 +1208,8 @@
public void setupAutoFill(Message message) {
// Prompt the user to set up their profile.
final Message msg = message;
- AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
- LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
final View layout = inflater.inflate(R.layout.setup_autofill_dialog, null);
@@ -1217,7 +1223,7 @@
if (disableAutoFill.isChecked()) {
// Disable autofill and show a toast with how to turn it on again.
mSettings.setAutofillEnabled(false);
- Toast.makeText(mActivity,
+ Toast.makeText(mContext,
R.string.autofill_setup_dialog_negative_toast,
Toast.LENGTH_LONG).show();
} else {
@@ -1338,10 +1344,10 @@
// Construct a new tab
Tab(WebViewController wvcontroller, WebView w) {
mWebViewController = wvcontroller;
- mActivity = mWebViewController.getActivity();
+ mContext = mWebViewController.getContext();
mSettings = BrowserSettings.getInstance();
- mDataController = DataController.getInstance(mActivity);
- mCurrentState = new PageState(mActivity, w != null
+ mDataController = DataController.getInstance(mContext);
+ mCurrentState = new PageState(mContext, w != null
? w.isPrivateBrowsingEnabled() : false);
mInPageLoad = false;
mInForeground = false;
@@ -1373,6 +1379,10 @@
setWebView(w);
}
+ public void setController(WebViewController ctl) {
+ mWebViewController = ctl;
+ }
+
public void setId(long id) {
mId = id;
}
@@ -1468,7 +1478,7 @@
}
}
});
- mSubView.setOnCreateContextMenuListener(mActivity);
+ mSubView.setOnCreateContextMenuListener(mWebViewController.getActivity());
return true;
}
return false;
@@ -1558,9 +1568,10 @@
void putInForeground() {
mInForeground = true;
resume();
- mMainView.setOnCreateContextMenuListener(mActivity);
+ Activity activity = mWebViewController.getActivity();
+ mMainView.setOnCreateContextMenuListener(activity);
if (mSubView != null) {
- mSubView.setOnCreateContextMenuListener(mActivity);
+ mSubView.setOnCreateContextMenuListener(activity);
}
// Show the pending error dialog if the queue is not empty
if (mQueuedErrors != null && mQueuedErrors.size() > 0) {
@@ -1654,7 +1665,6 @@
.findViewById(R.id.geolocation_permissions_prompt);
mGeolocationPermissionsPrompt = (GeolocationPermissionsPrompt) stub
.inflate();
- mGeolocationPermissionsPrompt.init();
}
return mGeolocationPermissionsPrompt;
}
@@ -1690,7 +1700,7 @@
*/
String getTitle() {
if (mCurrentState.mTitle == null && mInPageLoad) {
- return mActivity.getString(R.string.title_bar_loading);
+ return mContext.getString(R.string.title_bar_loading);
}
return mCurrentState.mTitle;
}
@@ -1716,7 +1726,7 @@
*/
ErrorConsoleView getErrorConsole(boolean createIfNecessary) {
if (createIfNecessary && mErrorConsole == null) {
- mErrorConsole = new ErrorConsoleView(mActivity);
+ mErrorConsole = new ErrorConsoleView(mContext);
mErrorConsole.setWebView(mMainView);
}
return mErrorConsole;
@@ -1867,22 +1877,45 @@
public ContentValues createSnapshotValues() {
if (mMainView == null) return null;
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- if (!mMainView.saveViewState(stream)) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try {
+ GZIPOutputStream stream = new GZIPOutputStream(bos);
+ if (!mMainView.saveViewState(stream)) {
+ return null;
+ }
+ stream.flush();
+ stream.close();
+ } catch (Exception e) {
+ Log.w(LOGTAG, "Failed to save view state", e);
return null;
}
- byte[] data = stream.toByteArray();
+ byte[] data = bos.toByteArray();
ContentValues values = new ContentValues();
values.put(Snapshots.TITLE, mCurrentState.mTitle);
values.put(Snapshots.URL, mCurrentState.mUrl);
values.put(Snapshots.VIEWSTATE, data);
values.put(Snapshots.BACKGROUND, mMainView.getPageBackgroundColor());
+ values.put(Snapshots.DATE_CREATED, System.currentTimeMillis());
+ values.put(Snapshots.FAVICON, compressBitmap(getFavicon()));
+ Bitmap screenshot = Controller.createScreenshot(mMainView,
+ Controller.getDesiredThumbnailWidth(mContext),
+ Controller.getDesiredThumbnailHeight(mContext));
+ values.put(Snapshots.THUMBNAIL, compressBitmap(screenshot));
return values;
}
+ public byte[] compressBitmap(Bitmap bitmap) {
+ if (bitmap == null) {
+ return null;
+ }
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ bitmap.compress(CompressFormat.PNG, 100, stream);
+ return stream.toByteArray();
+ }
+
public void loadUrl(String url, Map<String, String> headers) {
if (mMainView != null) {
- mCurrentState = new PageState(mActivity, false, url, null);
+ mCurrentState = new PageState(mContext, false, url, null);
mWebViewController.onPageStarted(this, mMainView, null);
mMainView.loadUrl(url, headers);
}
diff --git a/src/com/android/browser/TabBar.java b/src/com/android/browser/TabBar.java
index 8584afa..b2c2af8 100644
--- a/src/com/android/browser/TabBar.java
+++ b/src/com/android/browser/TabBar.java
@@ -16,8 +16,6 @@
package com.android.browser;
-import com.android.browser.BrowserWebView.ScrollListener;
-
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
@@ -43,7 +41,6 @@
import android.view.MenuInflater;
import android.view.View;
import android.view.View.OnClickListener;
-import android.webkit.WebView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -56,8 +53,7 @@
/**
* tabbed title bar for xlarge screen browser
*/
-public class TabBar extends LinearLayout
- implements ScrollListener, OnClickListener {
+public class TabBar extends LinearLayout implements OnClickListener {
private static final int PROGRESS_MAX = 100;
@@ -235,60 +231,6 @@
mUi.showTitleBar();
}
- void showTitleBarIndicator(boolean show) {
- Tab tab = mTabControl.getCurrentTab();
- if (tab != null && !tab.isSnapshot()) {
- TabView tv = mTabMap.get(tab);
- if (tv != null) {
- tv.showIndicator(show);
- }
- }
- }
-
- boolean showsTitleBarIndicator() {
- Tab tab = mTabControl.getCurrentTab();
- if (tab != null) {
- TabView tv = mTabMap.get(tab);
- if (tv != null) {
- return tv.showsIndicator();
- }
- }
- return false;
- }
-
- // callback after fake titlebar is shown
- void onShowTitleBar() {
- showTitleBarIndicator(false);
- }
-
- // callback after fake titlebar is hidden
- void onHideTitleBar() {
- Tab tab = mTabControl.getCurrentTab();
- WebView w = tab.getWebView();
- if (w != null) {
- showTitleBarIndicator(w.getVisibleTitleHeight() == 0);
- }
- }
-
- // webview scroll listener
-
- @Override
- 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) {
- if (visibleTitleHeight == 0 && !mUi.isTitleBarShowing()) {
- if (!showsTitleBarIndicator()) {
- showTitleBarIndicator(true);
- }
- } else {
- if (showsTitleBarIndicator()) {
- showTitleBarIndicator(false);
- }
- }
- }
- }
-
@Override
public void createContextMenu(ContextMenu menu) {
MenuInflater inflater = mActivity.getMenuInflater();
@@ -319,7 +261,6 @@
Tab mTab;
View mTabContent;
TextView mTitle;
- View mIndicator;
View mIncognito;
View mSnapshot;
ImageView mIconView;
@@ -353,33 +294,12 @@
mClose.setOnClickListener(this);
mIncognito = mTabContent.findViewById(R.id.incognito);
mSnapshot = mTabContent.findViewById(R.id.snapshot);
- mIndicator = mTabContent.findViewById(R.id.chevron);
mSelected = false;
mInLoad = false;
// update the status
updateFromTab();
}
- void showIndicator(boolean show) {
- if (mSelected) {
- mIndicator.setVisibility(show ? View.VISIBLE : View.GONE);
- LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
- if (show) {
- lp.width = mTabWidthSelected + mIndicator.getWidth();
- } else {
- lp.width = mTabWidthSelected;
- }
- lp.height = LayoutParams.MATCH_PARENT;
- setLayoutParams(lp);
- } else {
- mIndicator.setVisibility(View.GONE);
- }
- }
-
- boolean showsIndicator() {
- return (mIndicator.getVisibility() == View.VISIBLE);
- }
-
@Override
public void onClick(View v) {
if (v == mClose) {
@@ -412,7 +332,6 @@
public void setActivated(boolean selected) {
mSelected = selected;
mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE);
- mIndicator.setVisibility(View.GONE);
mTitle.setTextAppearance(mActivity, mSelected ?
R.style.TabTitleSelected : R.style.TabTitleUnselected);
setHorizontalFadingEdgeEnabled(!mSelected);
@@ -623,12 +542,6 @@
TabView tv = mTabMap.get(tab);
if (tv != null) {
tv.setProgress(tv.mTab.getLoadProgress());
- // update the scroll state
- WebView webview = tab.getWebView();
- if (webview != null) {
- int h = webview.getVisibleTitleHeight();
- onScroll(h, true);
- }
}
}
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 2eb24e9..5f3995f 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -16,14 +16,10 @@
package com.android.browser;
-import android.content.ContentResolver;
-import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
-import com.android.browser.provider.BrowserProvider2.Snapshots;
-
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
@@ -176,6 +172,14 @@
return false;
}
+ void addPreloadedTab(Tab tab) {
+ tab.setId(getNextId());
+ mTabs.add(tab);
+ tab.setController(mController);
+ mController.onSetWebView(tab, tab.getWebView());
+ tab.putInBackground();
+ }
+
/**
* Create a new tab.
* @return The newly createTab or null if we have reached the maximum
@@ -207,7 +211,6 @@
}
SnapshotTab createSnapshotTab(long snapshotId) {
- // TODO: Don't count this against the limit
SnapshotTab t = new SnapshotTab(mController, snapshotId);
t.setId(getNextId());
mTabs.add(t);
@@ -427,21 +430,6 @@
}
}
}
- loadSnapshotTabs();
-
- }
-
- void loadSnapshotTabs() {
- ContentResolver cr = mController.getActivity().getContentResolver();
- Cursor c = cr.query(Snapshots.CONTENT_URI, new String[] { "_id" },
- null, null, null);
- try {
- while (c.moveToNext()) {
- createSnapshotTab(c.getLong(0));
- }
- } finally {
- c.close();
- }
}
/**
diff --git a/src/com/android/browser/TitleBarBase.java b/src/com/android/browser/TitleBarBase.java
index c7fb9c6..de44306 100644
--- a/src/com/android/browser/TitleBarBase.java
+++ b/src/com/android/browser/TitleBarBase.java
@@ -493,13 +493,6 @@
}
protected void setFocusState(boolean focus) {
- if (focus) {
- updateSearchMode(false);
- }
- }
-
- protected void updateSearchMode(boolean userEdited) {
- setSearchMode(!userEdited || TextUtils.isEmpty(mUrlInput.getUserText()));
}
protected void setSearchMode(boolean voiceSearchEnabled) {}
@@ -523,8 +516,6 @@
@Override
public void onTextChanged(String newText) {
if (mUrlInput.hasFocus()) {
- // check if input field is empty and adjust voice search state
- updateSearchMode(true);
// clear voice mode when user types
setInVoiceMode(false, null);
}
@@ -554,7 +545,15 @@
@Override
public void onAction(String text, String extra, String source) {
mUiController.getCurrentTopWebView().requestFocus();
- mBaseUi.hideTitleBar();
+ if (UrlInputView.TYPED.equals(source)) {
+ String url = UrlUtils.smartUrlFilter(text, false);
+ Tab t = mBaseUi.getActiveTab();
+ if (url != null && t != null) {
+ mUiController.loadUrl(t, url);
+ setDisplayTitle(text);
+ return;
+ }
+ }
Intent i = new Intent();
String action = null;
if (UrlInputView.VOICE.equals(source)) {
diff --git a/src/com/android/browser/TitleBarPhone.java b/src/com/android/browser/TitleBarPhone.java
index 1fcaf4d..f41eca7 100644
--- a/src/com/android/browser/TitleBarPhone.java
+++ b/src/com/android/browser/TitleBarPhone.java
@@ -16,8 +16,6 @@
package com.android.browser;
-import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
-
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
@@ -29,9 +27,11 @@
import android.view.View.OnFocusChangeListener;
import android.webkit.WebView;
import android.widget.FrameLayout;
-import android.widget.ImageButton;
import android.widget.ImageView;
+import com.android.browser.UrlInputView.StateListener;
+import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
+
import java.util.List;
/**
@@ -39,15 +39,17 @@
* browser.
*/
public class TitleBarPhone extends TitleBarBase implements OnFocusChangeListener,
- OnClickListener, TextChangeWatcher {
+ OnClickListener, TextChangeWatcher, StateListener {
private Activity mActivity;
private ImageView mStopButton;
private ImageView mVoiceButton;
- private boolean mHasLockIcon;
- private ImageButton mForward;
private Drawable mStopDrawable;
private Drawable mRefreshDrawable;
+ private View mTabSwitcher;
+ private View mComboIcon;
+ private View mTitleContainer;
+ private Drawable mTextfieldBgDrawable;
public TitleBarPhone(Activity activity, UiController controller, PhoneUi ui,
FrameLayout parent) {
@@ -65,14 +67,18 @@
mStopButton.setOnClickListener(this);
mVoiceButton = (ImageView) findViewById(R.id.voice);
mVoiceButton.setOnClickListener(this);
- mForward = (ImageButton) findViewById(R.id.forward);
- mForward.setOnClickListener(this);
+ mTabSwitcher = findViewById(R.id.tab_switcher);
+ mTabSwitcher.setOnClickListener(this);
+ mComboIcon = findViewById(R.id.iconcombo);
+ mTitleContainer = findViewById(R.id.title_bg);
setFocusState(false);
Resources res = context.getResources();
mStopDrawable = res.getDrawable(R.drawable.ic_stop_holo_dark);
mRefreshDrawable = res.getDrawable(R.drawable.ic_refresh_holo_dark);
- setUaSwitcher(mFavicon);
+ mTextfieldBgDrawable = res.getDrawable(R.drawable.textfield_active_holo_dark);
+ setUaSwitcher(mComboIcon);
mUrlInput.setContainer(this);
+ mUrlInput.setStateListener(this);
}
@Override
@@ -96,29 +102,23 @@
}
@Override
- protected void setFocusState(boolean focus) {
- super.setFocusState(focus);
- if (focus) {
- mHasLockIcon = (mLockIcon.getVisibility() == View.VISIBLE);
- mLockIcon.setVisibility(View.GONE);
- mStopButton.setVisibility(View.GONE);
- mVoiceButton.setVisibility(View.VISIBLE);
- } else {
- mLockIcon.setVisibility(mHasLockIcon ? View.VISIBLE : View.GONE);
- mStopButton.setVisibility(View.VISIBLE);
- mVoiceButton.setVisibility(View.GONE);
- }
- }
-
- @Override
void setProgress(int progress) {
super.setProgress(progress);
if (progress == 100) {
+ mStopButton.setVisibility(View.GONE);
mStopButton.setImageDrawable(mRefreshDrawable);
- } else if (mStopButton.getDrawable() != mStopDrawable) {
- mStopButton.setImageDrawable(mStopDrawable);
+ if (!isEditingUrl()) {
+ mComboIcon.setVisibility(View.VISIBLE);
+ }
+ } else {
+ if (mStopButton.getDrawable() != mStopDrawable) {
+ mStopButton.setImageDrawable(mStopDrawable);
+ if (mStopButton.getVisibility() != View.VISIBLE) {
+ mComboIcon.setVisibility(View.GONE);
+ mStopButton.setVisibility(View.VISIBLE);
+ }
+ }
}
- updateNavigationState();
}
/**
@@ -135,7 +135,6 @@
mUrlInput.setText(title);
}
mUrlInput.setSelection(0);
- updateNavigationState();
}
}
@@ -157,25 +156,43 @@
} else {
WebView web = mBaseUi.getWebView();
if (web != null) {
+ stopEditingUrl();
web.reload();
}
}
} else if (v == mVoiceButton) {
mUiController.startVoiceSearch();
- } else if (v == mForward) {
- WebView web = mBaseUi.getWebView();
- if (web != null) {
- web.goForward();
- }
+ } else if (v == mTabSwitcher) {
+ mBaseUi.onMenuKey();
} else {
super.onClick(v);
}
}
- private void updateNavigationState() {
- WebView web = mBaseUi.getWebView();
- if (web != null) {
- mForward.setVisibility(web.canGoForward() ? View.VISIBLE : View.GONE);
+ @Override
+ public void onStateChanged(int state) {
+ switch(state) {
+ case StateListener.STATE_NORMAL:
+ mComboIcon.setVisibility(View.VISIBLE);
+ mStopButton.setVisibility(View.GONE);
+ setSearchMode(false);
+ mTabSwitcher.setVisibility(View.VISIBLE);
+ mTitleContainer.setBackgroundDrawable(null);
+ break;
+ case StateListener.STATE_HIGHLIGHTED:
+ mComboIcon.setVisibility(View.GONE);
+ mStopButton.setVisibility(View.VISIBLE);
+ setSearchMode(true);
+ mTabSwitcher.setVisibility(View.GONE);
+ mTitleContainer.setBackgroundDrawable(mTextfieldBgDrawable);
+ break;
+ case StateListener.STATE_EDITED:
+ mComboIcon.setVisibility(View.GONE);
+ mStopButton.setVisibility(View.GONE);
+ setSearchMode(false);
+ mTabSwitcher.setVisibility(View.GONE);
+ mTitleContainer.setBackgroundDrawable(mTextfieldBgDrawable);
+ break;
}
}
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
index 3d4d2ec..6aed86c 100644
--- a/src/com/android/browser/TitleBarXLarge.java
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -218,7 +218,6 @@
mStopButton.setImageDrawable(mReloadDrawable);
}
- @Override
protected void updateSearchMode(boolean userEdited) {
setSearchMode(!userEdited || TextUtils.isEmpty(mUrlInput.getUserText()));
}
diff --git a/src/com/android/browser/UI.java b/src/com/android/browser/UI.java
index a6356a3..93f6e63 100644
--- a/src/com/android/browser/UI.java
+++ b/src/com/android/browser/UI.java
@@ -33,6 +33,12 @@
*/
public interface UI {
+ public static enum ComboViews {
+ History,
+ Bookmarks,
+ Snapshots,
+ }
+
public void onPause();
public void onResume();
@@ -77,7 +83,7 @@
public void removeActiveTabsPage();
- public void showComboView(boolean startWithHistory, Bundle extra);
+ public void showComboView(ComboViews startingView, Bundle extra);
public void hideComboView();
diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java
index 05eaf8e..6045d86 100644
--- a/src/com/android/browser/UiController.java
+++ b/src/com/android/browser/UiController.java
@@ -97,4 +97,8 @@
boolean onOptionsItemSelected(MenuItem item);
+ SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive);
+
+ void loadUrl(Tab tab, String url);
+
}
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
index 7545e6a..8b5c292 100644
--- a/src/com/android/browser/UrlInputView.java
+++ b/src/com/android/browser/UrlInputView.java
@@ -16,14 +16,6 @@
package com.android.browser;
-import com.android.browser.SuggestionsAdapter.CompletionListener;
-import com.android.browser.SuggestionsAdapter.SuggestItem;
-import com.android.browser.UI.DropdownChangeListener;
-import com.android.browser.autocomplete.SuggestiveAutoCompleteTextView;
-import com.android.browser.search.SearchEngine;
-import com.android.browser.search.SearchEngineInfo;
-import com.android.browser.search.SearchEngines;
-
import android.content.Context;
import android.content.res.Configuration;
import android.database.DataSetObserver;
@@ -32,6 +24,7 @@
import android.util.AttributeSet;
import android.util.Patterns;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -40,6 +33,15 @@
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
+import com.android.browser.SuggestionsAdapter.CompletionListener;
+import com.android.browser.SuggestionsAdapter.SuggestItem;
+import com.android.browser.UI.DropdownChangeListener;
+import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
+import com.android.browser.autocomplete.SuggestiveAutoCompleteTextView;
+import com.android.browser.search.SearchEngine;
+import com.android.browser.search.SearchEngineInfo;
+import com.android.browser.search.SearchEngines;
+
import java.util.List;
/**
@@ -48,13 +50,20 @@
*/
public class UrlInputView extends SuggestiveAutoCompleteTextView
implements OnEditorActionListener,
- CompletionListener, OnItemClickListener {
-
+ CompletionListener, OnItemClickListener, TextChangeWatcher {
static final String TYPED = "browser-type";
static final String SUGGESTED = "browser-suggest";
static final String VOICE = "voice-search";
+ static interface StateListener {
+ static final int STATE_NORMAL = 0;
+ static final int STATE_HIGHLIGHTED = 1;
+ static final int STATE_EDITED = 2;
+
+ public void onStateChanged(int state);
+ }
+
private UrlInputListener mListener;
private InputMethodManager mInputManager;
private SuggestionsAdapter mAdapter;
@@ -64,6 +73,9 @@
private boolean mNeedsUpdate;
private DropdownChangeListener mDropdownListener;
+ private int mState;
+ private StateListener mStateListener;
+
public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
@@ -90,6 +102,7 @@
setOnItemClickListener(this);
mNeedsUpdate = false;
mDropdownListener = null;
+ addQueryTextWatcher(this);
mAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
@@ -105,6 +118,32 @@
dispatchChange();
}
});
+ mState = StateListener.STATE_NORMAL;
+ }
+
+ protected void onFocusChanged(boolean focused, int direction, Rect prevRect) {
+ super.onFocusChanged(focused, direction, prevRect);
+ if (focused) {
+ if (hasSelection()) {
+ changeState(StateListener.STATE_HIGHLIGHTED);
+ } else {
+ changeState(StateListener.STATE_EDITED);
+ }
+ } else {
+ // reset the selection state
+ changeState(StateListener.STATE_NORMAL);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ boolean hasSelection = hasSelection();
+ boolean res = super.onTouchEvent(evt);
+ if ((MotionEvent.ACTION_DOWN == evt.getActionMasked())
+ && hasSelection) {
+ changeState(StateListener.STATE_EDITED);
+ }
+ return res;
}
/**
@@ -135,6 +174,19 @@
mListener = listener;
}
+ public void setStateListener(StateListener listener) {
+ mStateListener = listener;
+ // update listener
+ changeState(mState);
+ }
+
+ private void changeState(int newState) {
+ mState = newState;
+ if (mStateListener != null) {
+ mStateListener.onStateChanged(mState);
+ }
+ }
+
void setVoiceResults(List<String> voiceResults) {
mAdapter.setVoiceResults(voiceResults);
}
@@ -305,4 +357,12 @@
public boolean requestRectangleOnScreen(Rect rect, boolean immediate) {
return false;
}
+
+ @Override
+ public void onTextChanged(String newText) {
+ if (StateListener.STATE_HIGHLIGHTED == mState) {
+ changeState(StateListener.STATE_EDITED);
+ }
+ }
+
}
diff --git a/src/com/android/browser/UrlUtils.java b/src/com/android/browser/UrlUtils.java
index 26f8e0e..c922e55 100644
--- a/src/com/android/browser/UrlUtils.java
+++ b/src/com/android/browser/UrlUtils.java
@@ -85,7 +85,22 @@
*
*/
public static String smartUrlFilter(String url) {
+ return smartUrlFilter(url, true);
+ }
+ /**
+ * Attempts to determine whether user input is a URL or search
+ * terms. Anything with a space is passed to search if canBeSearch is true.
+ *
+ * Converts to lowercase any mistakenly uppercased schema (i.e.,
+ * "Http://" converts to "http://"
+ *
+ * @param canBeSearch If true, will return a search url if it isn't a valid
+ * URL. If false, invalid URLs will return null
+ * @return Original or modified URL
+ *
+ */
+ public static String smartUrlFilter(String url, boolean canBeSearch) {
String inUrl = url.trim();
boolean hasSpace = inUrl.indexOf(' ') != -1;
@@ -97,7 +112,7 @@
if (!lcScheme.equals(scheme)) {
inUrl = lcScheme + matcher.group(2);
}
- if (hasSpace) {
+ if (hasSpace && Patterns.WEB_URL.matcher(inUrl).matches()) {
inUrl = inUrl.replace(" ", "%20");
}
return inUrl;
@@ -107,12 +122,11 @@
return URLUtil.guessUrl(inUrl);
}
}
-
- // FIXME: Is this the correct place to add to searches?
- // what if someone else calls this function?
-
-// Browser.addSearchUrl(mBrowser.getContentResolver(), inUrl);
- return URLUtil.composeSearchUrl(inUrl, QUICKSEARCH_G, QUERY_PLACE_HOLDER);
+ if (canBeSearch) {
+ return URLUtil.composeSearchUrl(inUrl,
+ QUICKSEARCH_G, QUERY_PLACE_HOLDER);
+ }
+ return null;
}
/* package */ static String fixUrl(String inUrl) {
diff --git a/src/com/android/browser/WebViewController.java b/src/com/android/browser/WebViewController.java
index 018af99..175cbf8 100644
--- a/src/com/android/browser/WebViewController.java
+++ b/src/com/android/browser/WebViewController.java
@@ -17,6 +17,7 @@
package com.android.browser;
import android.app.Activity;
+import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
@@ -36,6 +37,8 @@
*/
public interface WebViewController {
+ Context getContext();
+
Activity getActivity();
TabControl getTabControl();
@@ -72,7 +75,7 @@
void onDownloadStart(Tab tab, String url, String useragent, String contentDisposition,
String mimeType, long contentLength);
- void showCustomView(Tab tab, View view, int requestedOrientation,
+ void showCustomView(Tab tab, View view, int requestedOrientation,
WebChromeClient.CustomViewCallback callback);
void hideCustomView();
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index 84ad6ea..6fcfab7 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -16,8 +16,6 @@
package com.android.browser;
-import com.android.browser.BrowserWebView.ScrollListener;
-
import android.app.ActionBar;
import android.app.Activity;
import android.os.Bundle;
@@ -44,7 +42,6 @@
private TitleBarXLarge mTitleBar;
- private boolean mUseQuickControls;
private PieControl mPieControl;
private Handler mHandler;
@@ -70,9 +67,8 @@
mActionBar.setCustomView(mTabBar);
}
- @Override
- public void showComboView(boolean startWithHistory, Bundle extras) {
- super.showComboView(startWithHistory, extras);
+ public void showComboView(ComboViews startWith, Bundle extras) {
+ super.showComboView(startWith, extras);
if (mUseQuickControls) {
mActionBar.show();
}
@@ -136,27 +132,6 @@
hideTitleBar();
}
- // webview factory
-
- @Override
- public WebView createWebView(boolean privateBrowsing) {
- // Create a new WebView
- BrowserWebView w = (BrowserWebView) super.createWebView(privateBrowsing);
- w.setScrollListener(this);
- return w;
- }
-
- @Override
- public WebView createSubWebView(boolean privateBrowsing) {
- return super.createWebView(privateBrowsing);
- }
-
- @Override
- public void onScroll(int visibleTitleHeight, boolean userInitiated) {
- super.onScroll(visibleTitleHeight, userInitiated);
- mTabBar.onScroll(visibleTitleHeight, userInitiated);
- }
-
void stopWebViewScrolling() {
BrowserWebView web = (BrowserWebView) mUiController.getCurrentWebView();
if (web != null) {
@@ -204,14 +179,11 @@
// Request focus on the top window.
if (mUseQuickControls) {
mPieControl.forceToTop(mContentView);
- view.setScrollListener(null);
- mTabBar.showTitleBarIndicator(false);
} else {
// check if title bar is already attached by animation
if (mTitleBar.getParent() == null && !tab.isSnapshot()) {
view.setEmbeddedTitleBar(mTitleBar);
}
- view.setScrollListener(this);
}
mTabBar.onSetActiveTab(tab);
if (tab.isInVoiceSearchMode()) {
@@ -266,14 +238,12 @@
protected void showTitleBar() {
if (canShowTitleBar()) {
mTitleBar.show();
- mTabBar.onShowTitleBar();
}
}
@Override
protected void hideTitleBar() {
if (isTitleBarShowing()) {
- mTabBar.onHideTitleBar();
mTitleBar.hide();
}
}
diff --git a/src/com/android/browser/preferences/BandwidthPreferencesFragment.java b/src/com/android/browser/preferences/BandwidthPreferencesFragment.java
new file mode 100644
index 0000000..18b9fa4
--- /dev/null
+++ b/src/com/android/browser/preferences/BandwidthPreferencesFragment.java
@@ -0,0 +1,39 @@
+/*
+ * 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.preferences;
+
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.util.Log;
+
+import com.android.browser.PreferenceKeys;
+import com.android.browser.R;
+
+public class BandwidthPreferencesFragment extends PreferenceFragment {
+
+ static final String TAG = "BandwidthPreferencesFragment";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Load the XML preferences file
+ addPreferencesFromResource(R.xml.bandwidth_preferences);
+ }
+
+}
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index 06e4e4a..b974c0e 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -68,18 +68,6 @@
public class BrowserProvider2 extends SQLiteContentProvider {
- public static interface Snapshots {
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(
- BrowserContract.AUTHORITY_URI, "snapshots");
- public static final String _ID = "_id";
- public static final String VIEWSTATE = "view_state";
- public static final String BACKGROUND = "background";
- public static final String TITLE = History.TITLE;
- public static final String URL = History.URL;
- public static final String FAVICON = History.FAVICON;
- }
-
public static final String PARAM_GROUP_BY = "groupBy";
public static final String LEGACY_AUTHORITY = "browser";
@@ -151,9 +139,6 @@
static final int LEGACY = 9000;
static final int LEGACY_ID = 9001;
- static final int SNAPSHOTS = 10000;
- static final int SNAPSHOTS_ID = 10001;
-
public static final long FIXED_ID_ROOT = 1;
// Default sort order for unsync'd bookmarks
@@ -215,9 +200,6 @@
"bookmarks/" + SearchManager.SUGGEST_URI_PATH_QUERY,
BOOKMARKS_SUGGESTIONS);
- matcher.addURI(authority, "snapshots", SNAPSHOTS);
- matcher.addURI(authority, "snapshots/#", SNAPSHOTS_ID);
-
// Projection maps
HashMap<String, String> map;
@@ -351,7 +333,7 @@
final class DatabaseHelper extends SQLiteOpenHelper {
static final String DATABASE_NAME = "browser2.db";
- static final int DATABASE_VERSION = 29;
+ static final int DATABASE_VERSION = 30;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@@ -422,8 +404,6 @@
}
enableSync(db);
-
- createSnapshots(db);
}
void enableSync(SQLiteDatabase db) {
@@ -520,8 +500,9 @@
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion < 29) {
- createSnapshots(db);
+ if (oldVersion < 30) {
+ db.execSQL("DROP VIEW IF EXISTS " + VIEW_SNAPSHOTS_COMBINED);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
}
if (oldVersion < 28) {
enableSync(db);
@@ -543,23 +524,6 @@
}
}
- void createSnapshots(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
- db.execSQL("CREATE TABLE " + TABLE_SNAPSHOTS + " (" +
- Snapshots._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Snapshots.URL + " TEXT NOT NULL," +
- Snapshots.TITLE + " TEXT," +
- Snapshots.BACKGROUND + " INTEGER," +
- Snapshots.VIEWSTATE + " BLOB NOT NULL" +
- ");");
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_SNAPSHOTS_COMBINED +
- " AS SELECT * FROM " + TABLE_SNAPSHOTS +
- " LEFT OUTER JOIN " + TABLE_IMAGES +
- " ON " + TABLE_SNAPSHOTS + "." + Snapshots.URL +
- " = images.url_key");
- }
-
- @Override
public void onOpen(SQLiteDatabase db) {
db.enableWriteAheadLogging();
mSyncHelper.onDatabaseOpened(db);
@@ -1010,17 +974,6 @@
break;
}
- case SNAPSHOTS_ID: {
- selection = DatabaseUtils.concatenateWhere(selection, "_id=?");
- selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- }
- case SNAPSHOTS: {
- qb.setTables(VIEW_SNAPSHOTS_COMBINED);
- break;
- }
-
default: {
throw new UnsupportedOperationException("Unknown URL " + uri.toString());
}
@@ -1220,16 +1173,6 @@
}
break;
}
- case SNAPSHOTS_ID: {
- selection = DatabaseUtils.concatenateWhere(selection, TABLE_SNAPSHOTS + "._id=?");
- selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- }
- case SNAPSHOTS: {
- deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs);
- break;
- }
default: {
throw new UnsupportedOperationException("Unknown delete URI " + uri);
}
@@ -1367,11 +1310,6 @@
break;
}
- case SNAPSHOTS: {
- id = db.insertOrThrow(TABLE_SNAPSHOTS, Snapshots.TITLE, values);
- break;
- }
-
default: {
throw new UnsupportedOperationException("Unknown insert URI " + uri);
}
diff --git a/src/com/android/browser/provider/SnapshotProvider.java b/src/com/android/browser/provider/SnapshotProvider.java
new file mode 100644
index 0000000..49557f7
--- /dev/null
+++ b/src/com/android/browser/provider/SnapshotProvider.java
@@ -0,0 +1,258 @@
+/*
+ * 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.provider;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.BrowserContract;
+
+import java.io.File;
+
+/**
+ * This provider is expected to be potentially flaky. It uses a database
+ * stored on external storage, which could be yanked unexpectedly.
+ */
+public class SnapshotProvider extends ContentProvider {
+
+ public static interface Snapshots {
+
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ SnapshotProvider.AUTHORITY_URI, "snapshots");
+ public static final String _ID = "_id";
+ public static final String VIEWSTATE = "view_state";
+ public static final String BACKGROUND = "background";
+ public static final String TITLE = "title";
+ public static final String URL = "url";
+ public static final String FAVICON = "favicon";
+ public static final String THUMBNAIL = "thumbnail";
+ public static final String DATE_CREATED = "date_created";
+ }
+
+ public static final String AUTHORITY = "com.android.browser.snapshots";
+ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ static final String TABLE_SNAPSHOTS = "snapshots";
+ static final int SNAPSHOTS = 10;
+ static final int SNAPSHOTS_ID = 11;
+ static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+ SnapshotDatabaseHelper mOpenHelper;
+
+ static {
+ URI_MATCHER.addURI(AUTHORITY, "snapshots", SNAPSHOTS);
+ URI_MATCHER.addURI(AUTHORITY, "snapshots/#", SNAPSHOTS_ID);
+ }
+
+ final static class SnapshotDatabaseHelper extends SQLiteOpenHelper {
+
+ static final String DATABASE_NAME = "snapshots.db";
+ static final int DATABASE_VERSION = 1;
+
+ public SnapshotDatabaseHelper(Context context) {
+ super(context, getFullDatabaseName(context), null, DATABASE_VERSION);
+ }
+
+ static String getFullDatabaseName(Context context) {
+ File dir = context.getExternalFilesDir(null);
+ return new File(dir, DATABASE_NAME).getAbsolutePath();
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_SNAPSHOTS + "(" +
+ Snapshots._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Snapshots.TITLE + " TEXT," +
+ Snapshots.URL + " TEXT NOT NULL," +
+ Snapshots.DATE_CREATED + " INTEGER," +
+ Snapshots.FAVICON + " BLOB," +
+ Snapshots.THUMBNAIL + " BLOB," +
+ Snapshots.BACKGROUND + " INTEGER," +
+ Snapshots.VIEWSTATE + " BLOB NOT NULL" +
+ ");");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // Not needed yet
+ }
+
+ }
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new SnapshotDatabaseHelper(getContext());
+ IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_EJECT);
+ filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+ getContext().registerReceiver(mExternalStorageReceiver, filter);
+ return true;
+ }
+
+ final BroadcastReceiver mExternalStorageReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ mOpenHelper.close();
+ } catch (Throwable t) {
+ // We failed to close the open helper, which most likely means
+ // another thread is busy attempting to open the database
+ // or use the database. Let that thread try to gracefully
+ // deal with the error
+ }
+ }
+ };
+
+ SQLiteDatabase getWritableDatabase() {
+ String state = Environment.getExternalStorageState();
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ try {
+ return mOpenHelper.getWritableDatabase();
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ SQLiteDatabase getReadableDatabase() {
+ String state = Environment.getExternalStorageState();
+ if (Environment.MEDIA_MOUNTED.equals(state)
+ || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+ try {
+ return mOpenHelper.getReadableDatabase();
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ SQLiteDatabase db = getReadableDatabase();
+ if (db == null) {
+ return null;
+ }
+ final int match = URI_MATCHER.match(uri);
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
+ switch (match) {
+ case SNAPSHOTS_ID:
+ selection = DatabaseUtils.concatenateWhere(selection, "_id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ case SNAPSHOTS:
+ qb.setTables(TABLE_SNAPSHOTS);
+ break;
+
+ default:
+ throw new UnsupportedOperationException("Unknown URL " + uri.toString());
+ }
+ try {
+ Cursor cursor = qb.query(db, projection, selection, selectionArgs,
+ null, null, sortOrder, limit);
+ cursor.setNotificationUri(getContext().getContentResolver(),
+ AUTHORITY_URI);
+ return cursor;
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ SQLiteDatabase db = getWritableDatabase();
+ if (db == null) {
+ return null;
+ }
+ int match = URI_MATCHER.match(uri);
+ long id = -1;
+ switch (match) {
+ case SNAPSHOTS:
+ try {
+ id = db.insert(TABLE_SNAPSHOTS, Snapshots.TITLE, values);
+ } catch (Throwable t) {
+ id = -1;
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown insert URI " + uri);
+ }
+ if (id < 0) {
+ return null;
+ }
+ Uri inserted = ContentUris.withAppendedId(uri, id);
+ getContext().getContentResolver().notifyChange(inserted, null, false);
+ return inserted;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = getWritableDatabase();
+ if (db == null) {
+ return 0;
+ }
+ int match = URI_MATCHER.match(uri);
+ int deleted = 0;
+ switch (match) {
+ case SNAPSHOTS_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_SNAPSHOTS + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case SNAPSHOTS:
+ try {
+ deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs);
+ } catch (Throwable t) {
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown delete URI " + uri);
+ }
+ if (deleted > 0) {
+ getContext().getContentResolver().notifyChange(uri, null, false);
+ }
+ return deleted;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+}