am a0b0e2a2: (-s ours) Do not merge When opening a new tab from the context menu, treat the new tab as a child of the current tab. This change was cherry-picked from master.
Merge commit 'a0b0e2a2cd78ae78ec50eab2e532d0102facc52d'
* commit 'a0b0e2a2cd78ae78ec50eab2e532d0102facc52d':
Do not merge
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3f61151..cdac176 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,20 +1,19 @@
<!--
-/* //device/apps/Browser/AndroidManifest.xml
-**
-** Copyright 2006, 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.
-*/
+/*
+ * Copyright 2006, 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.
+ */
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.browser">
@@ -38,6 +37,7 @@
<application android:name="Browser"
android:label="@string/application_name"
android:icon="@drawable/ic_launcher_browser"
+ android:backupAgent=".BrowserBackupAgent"
android:taskAffinity="android.task.browser" >
<provider android:name="BrowserProvider"
@@ -124,7 +124,6 @@
<activity android:name="BrowserBookmarksPage" android:label="@string/bookmarks"
android:launchMode="singleTop" android:configChanges="orientation|keyboardHidden">
</activity>
-
<activity android:name="MostVisitedActivity" android:label=""
android:launchMode="singleTop" android:configChanges="orientation|keyboardHidden"/>
@@ -156,11 +155,20 @@
android:configChanges="orientation|keyboardHidden">
</activity>
+ <activity android:name="WebsiteSettingsActivity" android:label=""
+ android:configChanges="orientation|keyboardHidden">
+ </activity>
+
<activity android:name="GearsDialog" android:process=":dialog"
android:configChanges="orientation|keyboardHidden"
android:theme="@android:style/Theme.Dialog">
</activity>
+ <activity android:name="PermissionDialog"
+ android:configChanges="orientation|keyboardHidden"
+ android:theme="@android:style/Theme.Dialog">
+ </activity>
+
<activity android:name="GearsNativeDialog"
android:configChanges="orientation|keyboardHidden"
android:theme="@android:style/Theme.Dialog">
diff --git a/res/drawable/background_titlebar.png b/res/drawable/background_titlebar.png
new file mode 100644
index 0000000..1fdb078
--- /dev/null
+++ b/res/drawable/background_titlebar.png
Binary files differ
diff --git a/res/drawable/blank.xml b/res/drawable/blank.xml
new file mode 100644
index 0000000..724f5e7
--- /dev/null
+++ b/res/drawable/blank.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape>
+ <corners android:radius="5dip"/>
+ <solid android:color="@color/white"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/res/drawable/button_line.xml b/res/drawable/button_line.xml
new file mode 100644
index 0000000..67869d0
--- /dev/null
+++ b/res/drawable/button_line.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:width="2dip">
+ <solid android:color="#ff000000"/>
+ </shape>
+ </item>
+ <item android:left="1dip" android:width="1dip">
+ <shape>
+ <solid android:color="#ffffffff"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/res/drawable/ic_titlebar_zoom.png b/res/drawable/ic_titlebar_zoom.png
new file mode 100644
index 0000000..b497dab
--- /dev/null
+++ b/res/drawable/ic_titlebar_zoom.png
Binary files differ
diff --git a/res/drawable/ic_titlebar_zoom_out.png b/res/drawable/ic_titlebar_zoom_out.png
new file mode 100644
index 0000000..4b5a3dd
--- /dev/null
+++ b/res/drawable/ic_titlebar_zoom_out.png
Binary files differ
diff --git a/res/layout/application.xml b/res/layout/application.xml
new file mode 100644
index 0000000..d8de265
--- /dev/null
+++ b/res/layout/application.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawingCacheQuality="auto"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:padding="0dip">
+
+ <ImageView android:id="@+id/icon"
+ android:layout_width="32px"
+ android:layout_height="32px"
+ android:padding="2dip" />
+
+ <TwoLineListItem
+ android:paddingTop="2dip"
+ android:paddingBottom="2dip"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:mode="twoLine">
+
+ <TextView android:id="@+id/title"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="6dip"
+ android:layout_marginTop="6dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+ <TextView android:id="@+id/subtitle"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/title"
+ android:layout_alignLeft="@+id/title"
+ android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+ </TwoLineListItem>
+
+</LinearLayout>
diff --git a/res/layout/bookmark_thumbnail.xml b/res/layout/bookmark_thumbnail.xml
new file mode 100644
index 0000000..2f8a09d
--- /dev/null
+++ b/res/layout/bookmark_thumbnail.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:orientation="vertical"
+ android:padding="4dip"
+ >
+
+ <ImageView android:id="@+id/thumb"
+ android:src="@drawable/blank"
+ android:scaleType="center"
+ android:layout_width="100dip"
+ android:layout_height="80dip"
+ />
+
+ <ImageView android:id="@+id/fav"
+ android:scaleType="center"
+ android:layout_width="20dip"
+ android:layout_height="20dip"
+ android:layout_alignBottom="@+id/thumb"
+ android:background="@color/white"
+ />
+ <LinearLayout android:id="@+id/holder"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:orientation="horizontal"
+ android:background="#cc000000"
+ android:gravity="center"
+ android:layout_alignBottom="@+id/thumb"
+ >
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginTop="3dip"
+ android:layout_marginBottom="3dip"
+ android:src="@drawable/ic_tab_browser_bookmark_selected"
+ />
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textStyle="bold"
+ android:textColor="@color/white"
+ android:text="@string/add_bookmark_short"
+ />
+ </LinearLayout>
+ <!-- Keep the width in sync with BrowserBookmarksPage and
+ BrowserActivity.updateScreenshot -->
+ <TextView android:id="@+id/label"
+ android:layout_width="100dip"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/thumb"
+ android:layout_gravity="bottom"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textStyle="bold"
+ android:textColor="@color/white"
+ android:maxLines="1"
+ android:paddingTop="3dip"
+ android:paddingBottom="3dip"
+ android:paddingLeft="2dip"
+ android:paddingRight="2dip"
+ android:scrollHorizontally="true"
+ android:ellipsize="marquee"
+ />
+</RelativeLayout>
diff --git a/res/layout/browser_downloads_page.xml b/res/layout/browser_downloads_page.xml
index c83a727..1d4d4e6 100644
--- a/res/layout/browser_downloads_page.xml
+++ b/res/layout/browser_downloads_page.xml
@@ -18,8 +18,14 @@
** limitations under the License.
*/
-->
-
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"/>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <ListView
+ android:id="@+id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"/>
+ <ViewStub
+ android:id="@+id/empty"
+ android:layout="@layout/no_downloads"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"/>
+</merge>
diff --git a/res/layout/custom_screen.xml b/res/layout/custom_screen.xml
new file mode 100644
index 0000000..3ea8ec9
--- /dev/null
+++ b/res/layout/custom_screen.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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">
+ <FrameLayout android:id="@+id/fullscreen_custom_content"
+ android:visibility="gone"
+ android:background="@color/black"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ />
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <com.android.browser.TitleBar android:id="@+id/title_bar"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <LinearLayout android:id="@+id/error_console"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <FrameLayout android:id="@+id/main_content"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:foreground="?android:attr/windowContentOverlay"
+ />
+ </LinearLayout>
+</FrameLayout>
diff --git a/res/layout/error_console.xml b/res/layout/error_console.xml
new file mode 100644
index 0000000..0fffcde
--- /dev/null
+++ b/res/layout/error_console.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/error_console_view_group_id"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:background="#000000">
+
+ <TextView android:id="@+id/error_console_header_id"
+ android:text="@string/error_console_header_text_minimized"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="2dip"
+ android:paddingBottom="2dip"
+ android:paddingLeft="5dip"
+ style="?android:attr/listSeparatorTextViewStyle"
+ android:visibility="gone"
+ />
+
+ <view class="com.android.browser.ErrorConsoleView$ErrorConsoleListView"
+ android:id="@+id/error_console_list_id"
+ android:layout_width="fill_parent"
+ android:layout_height="200dip"
+ android:visibility="gone"
+ android:layout_weight="1"
+ android:cacheColorHint="#000000"
+ />
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/error_console_eval_view_group_id"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+
+ <EditText android:id="@+id/error_console_eval_text_id"
+ android:hint="@string/error_console_eval_text_hint"
+ android:layout_height="wrap_content"
+ android:scrollHorizontally="true"
+ android:inputType="text"
+ android:layout_width="0dip"
+ android:layout_gravity="left"
+ android:layout_weight="1.0"
+ />
+
+ <Button android:id="@+id/error_console_eval_button_id"
+ android:text="@string/error_console_eval_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/permission_dialog.xml b/res/layout/permission_dialog.xml
new file mode 100644
index 0000000..ff24eaf
--- /dev/null
+++ b/res/layout/permission_dialog.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2009, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawingCacheQuality="auto"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="0dip">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="10dip">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:paddingRight="10dip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"/>
+
+ <TextView
+ android:id="@+id/dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ android:visibility="gone"
+ android:textSize="16dip"
+ android:textStyle="bold"
+ android:textColor="@color/white"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ImageView android:id="@+id/titleDivider"
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:scaleType="fitXY"
+ android:gravity="fill_horizontal"
+ android:src="@drawable/dialog_divider_horizontal_light"
+ android:layout_marginLeft="10dip"
+ android:layout_marginRight="10dip"/>
+
+ </LinearLayout>
+
+ <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="10dip"
+ android:layout_weight="1">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:paddingTop="10dip"
+ android:paddingLeft="10dip"
+ android:paddingRight="10dip"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/origin"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:textStyle="bold"
+ android:gravity="left"
+ android:textSize="16dip"
+ android:textColor="@color/white"/>
+
+ <TextView
+ android:id="@+id/dialog_message"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="4dip"
+ android:paddingTop="10dip"
+ android:gravity="left"
+ android:textSize="16dip"
+ android:textColor="@color/white"/>
+
+ </LinearLayout>
+
+ </ScrollView>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:background="@color/gray"
+ android:layout_height="wrap_content"
+ android:paddingTop="4dip"
+ android:paddingLeft="0dip"
+ android:paddingRight="0dip">
+
+ <LinearLayout android:id="@+id/leftSpacer"
+ android:layout_weight="0.25"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="gone"/>
+
+ <Button
+ android:id="@+id/button_allow"
+ android:layout_width="96dip"
+ android:layout_height="48dip"
+ android:layout_gravity="left"
+ android:layout_weight="1"
+ android:maxLines="2"
+ android:textSize="13dip"/>
+
+ <Button
+ android:id="@+id/button_alwaysdeny"
+ android:layout_width="96dip"
+ android:layout_height="48dip"
+ android:layout_gravity="left"
+ android:layout_weight="1"
+ android:maxLines="2"
+ android:textSize="13dip"/>
+
+ <Button
+ android:id="@+id/button_deny"
+ android:layout_width="96dip"
+ android:layout_height="48dip"
+ android:layout_gravity="right"
+ android:layout_weight="1"
+ android:maxLines="2"
+ android:textSize="13dip"/>
+
+ <LinearLayout android:id="@+id/rightSpacer"
+ android:layout_width="0dip"
+ android:layout_weight="0.25"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="gone" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/title_bar.xml b/res/layout/title_bar.xml
new file mode 100644
index 0000000..0f70519
--- /dev/null
+++ b/res/layout/title_bar.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fitsSystemWindows="true"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:background="@drawable/background_titlebar"
+ android:layout_height="45dip">
+ <ImageView android:id="@+id/favicon"
+ android:layout_height="20dip"
+ android:layout_width="20dip"
+ android:layout_marginLeft="6dip"
+ android:layout_marginRight="6dip"
+ android:layout_gravity="center_vertical"
+ />
+ <!-- layout which contains the title, progress bar, and url -->
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content">
+ <!-- This part contains the favicon and the progress bar -->
+ <RelativeLayout
+ android:layout_marginTop="3dip"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ >
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_marginTop="3dip"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <ProgressBar android:id="@+id/progress_horizontal"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:max="100" />
+ <ProgressBar android:id="@+id/progress_circular"
+ style="?android:attr/progressBarStyleSmallTitle"
+ android:layout_marginLeft="3dip"
+ android:layout_gravity="center_vertical"
+ android:max="100"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ <!-- need to make this no wider than the horizontal progress bar -->
+ <TextView android:id="@+id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginLeft="6dip"
+ android:layout_marginTop="4dip"
+ android:textSize="14dip"
+ android:textColor="@color/white"
+ android:textStyle="bold"
+ android:singleLine="true"
+ />
+ <TextView android:id="@+id/url"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="6dip"
+ android:layout_below="@id/title"
+ android:textSize="12dip"
+ android:textColor="@color/white"
+ android:singleLine="true"
+ />
+ <ImageView android:id="@+id/lock_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_alignBottom="@id/title"
+ android:layout_alignParentRight="true"
+ android:visibility="gone"/>
+ </RelativeLayout>
+ </LinearLayout>
+ <!-- These buttons will change look/functionality -->
+ <ImageView android:id="@+id/lft_button"
+ android:layout_width="52dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:src="@android:drawable/btn_star"/>
+ <!-- divider -->
+ <View android:id="@+id/divider"
+ android:layout_height="fill_parent"
+ android:layout_width="2dip"
+ android:background="@drawable/button_line"/>
+ <ImageView android:id="@+id/rt_button"
+ android:layout_width="52dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:src="@*android:drawable/btn_browser_zoom_page_overview"/>
+</LinearLayout>
diff --git a/res/menu/bookmarks.xml b/res/menu/bookmarks.xml
index 50dcfa9..8a6e32e 100644
--- a/res/menu/bookmarks.xml
+++ b/res/menu/bookmarks.xml
@@ -18,4 +18,6 @@
<item android:id="@+id/new_context_menu_id"
android:icon="@android:drawable/ic_menu_add"
android:title="@string/bookmark_page" />
+ <item android:id="@+id/switch_mode_menu_id"
+ android:title="@string/switch_mode"/>
</menu>
diff --git a/res/menu/bookmarkscontext.xml b/res/menu/bookmarkscontext.xml
index b43e242..ba5d1dc 100644
--- a/res/menu/bookmarkscontext.xml
+++ b/res/menu/bookmarkscontext.xml
@@ -34,5 +34,7 @@
android:title="@string/contextmenu_copylink"/>
<item android:id="@+id/delete_context_menu_id"
android:title="@string/remove_bookmark"/>
+ <item android:id="@+id/homepage_context_menu_id"
+ android:title="@string/set_as_homepage"/>
</group>
</menu>
diff --git a/res/menu/historycontext.xml b/res/menu/historycontext.xml
index dfda010..5306396 100644
--- a/res/menu/historycontext.xml
+++ b/res/menu/historycontext.xml
@@ -26,5 +26,7 @@
<item android:id="@+id/copy_context_menu_id"
android:title="@string/contextmenu_copylink"/>
<item android:id="@+id/delete_context_menu_id"
- android:title="@string/remove_history_item"/>
+ android:title="@string/remove_history_item"/>
+ <item android:id="@+id/homepage_context_menu_id"
+ android:title="@string/set_as_homepage"/>
</menu>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..219cc6b
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<resources>
+ <string-array name="webstorage_quota_entries">
+ <item>No quota allowed</item>
+ <item>5 MB</item>
+ <item>10 MB</item>
+ <item>30 MB</item>
+ <item>100 MB</item>
+ </string-array>
+ <string-array name="webstorage_quota_entries_values">
+ <item>0</item>
+ <item>5</item>
+ <item>10</item>
+ <item>30</item>
+ <item>100</item>
+ </string-array>
+</resources>
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 69c17cb..0d1c229 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -58,7 +58,10 @@
<!-- Case of several matches -->
<item quantity="other"><xliff:g id="number" example="137">%d</xliff:g> matches</item>
</plurals>
-
+
+ <!-- Displayed on the title bar while the page is loading -->
+ <string name="title_bar_loading">Loading\u2026</string>
+
<!-- Menu item -->
<string name="page_info">Page info</string>
<!-- Label for a button on an SSL error dialog that allows the user to see
@@ -143,8 +146,12 @@
<string name="remove_bookmark">Delete bookmark</string>
<!-- Menu item to remove the currently highlighted history entry from the list of previously visited sites -->
<string name="remove_history_item">Remove from history</string>
+ <!-- Context menu item for setting the bookmark/history item as the homepage -->
+ <string name="set_as_homepage">Set as homepage</string>
<!-- Toast informing the user that their action to save a bookmark has succeeded -->
<string name="bookmark_saved">Saved to bookmarks.</string>
+ <!-- Toast confirming that the homepage has been set -->
+ <string name="homepage_set">Homepage set</string>
<!-- Error that appears in the title of Bookmark dialog when user selects OK with empty Name field -->
<string name="bookmark_needs_title">"Bookmark must have a name."</string>
<!-- Error that appears in the title of Bookmark dialog when user selects OK with empty Location field -->
@@ -160,6 +167,9 @@
currently on, but is not visible because the bookmarks page is
showing. -->
<string name="bookmark_page">Bookmark last-viewed page</string>
+ <!-- Menu item in the page that displays all bookmarks. Switches between
+ a vertical list view and a grid view which shows thumbnails -->
+ <string name="switch_mode">Switch viewing mode</string>
<!-- Summary text under the New Bookmark item on the Bookmarks screen.
Tells the user that if they select this item, it will bring up a
dialog to bookmark the page that the browser is currently viewing,
@@ -284,14 +294,18 @@
<string name="pref_content_autofit">Auto-fit pages</string>
<!-- Settings summary -->
<string name="pref_content_autofit_summary">Format Web pages to fit the screen</string>
+ <!-- Settings label for enabling a mode where the browser is always set to landscape mode -->
+ <string name="pref_content_landscape_only">Landscape only display</string>
+ <!-- Settings summary -->
+ <string name="pref_content_landscape_only_summary">Always read pages in the wider, landscape screen orientation</string>
<!-- Settings screen, section title -->
<string name="pref_privacy_title">Privacy settings</string>
<!-- Settings label -->
<string name="pref_privacy_clear_cache">Clear cache</string>
<!-- Settings summary -->
- <string name="pref_privacy_clear_cache_summary">Delete all cached page content</string>
+ <string name="pref_privacy_clear_cache_summary">Clear locally cached content and databases</string>
<!-- Confirmation dialog message -->
- <string name="pref_privacy_clear_cache_dlg">The cache will be cleared.</string>
+ <string name="pref_privacy_clear_cache_dlg">Locally cached content and databases will be cleared.</string>
<!-- Settings label -->
<string name="pref_privacy_clear_cookies">Clear all cookie data</string>
<!-- Settings summary -->
@@ -316,6 +330,16 @@
<string name="pref_privacy_clear_passwords_summary">Clear all the saved passwords</string>
<!-- Confirmation dialog message -->
<string name="pref_privacy_clear_passwords_dlg">All saved passwords will be cleared.</string>
+ <!-- Settings label -->
+ <string name="pref_privacy_request_location">Enable location</string>
+ <!-- Settings summary -->
+ <string name="pref_privacy_request_location_summary">Allow sites to request access to your location</string>
+ <!-- Settings label -->
+ <string name="pref_privacy_clear_location_requests">Clear location access</string>
+ <!-- Settings summary -->
+ <string name="pref_privacy_clear_location_requests_summary">Clear location access for all websites</string>
+ <!-- Confirmation dialog message -->
+ <string name="pref_privacy_clear_location_requests_dlg">Clear all previous location requests</string>
<!-- Settings screen, section title -->
<string name="pref_security_title">Security settings</string>
<!-- Settings label -->
@@ -377,6 +401,10 @@
<!-- Settings summary -->
<string name="pref_extras_gears_enable_summary">Applications that extend browser functionality</string>
<!-- Settings label -->
+ <string name="pref_extras_website_settings">Website Settings</string>
+ <!-- Settings summary -->
+ <string name="pref_extras_website_settings_summary">View advanced settings for individual websites</string>
+ <!-- Settings label -->
<string name="pref_extras_gears_settings">Gears settings</string>
<!-- Settings summary -->
<string name="pref_plugin_installed">Plugins list</string>
@@ -404,6 +432,8 @@
<!-- Do not tranlsate. Development option -->
<string name="pref_development_nav_dump" translatable="false">Enable nav cache dump</string>
<!-- Do not tranlsate. Development option -->
+ <string name="js_engine_flags" translatable="false">Set JS flags</string>
+ <!-- Do not tranlsate. Development option -->
<string name="pref_development_uastring" translatable="false">UAString</string>
<!-- Do not tranlsate. Development option -->
<string-array name="pref_development_ua_choices" translatable="false">
@@ -417,6 +447,7 @@
<item>1</item>
<item>2</item>
</string-array>
+ <string name="pref_development_error_console" translatable="false">Show JavaScript Console</string>
<!-- Settings screen, setting option name -->
<string name="pref_default_text_encoding">Text encoding</string>
<!-- Options in the Default encoding dialog box -->
@@ -465,6 +496,9 @@
<string name="empty_history">Browser history is empty.</string>
<!-- Displayed at the top of the bookmarks page. When clicked, it bookmarks the page the browser is currently showing -->
<string name="add_new_bookmark">Add bookmark\u2026</string>
+ <!-- Verb placed in front of a screenshot of a web page that, when clicked,
+ will add that page to bookmarks -->
+ <string name="add_bookmark_short">Add</string>
<!-- Add bookmark dialog sets its title to this if we have no database.
This is an error case -->
<string name="no_database">No database!</string>
@@ -671,6 +705,9 @@
<!-- Gears Dialogs -->
<string name="query_data_prompt">Allow storage</string>
<string name="query_data_message">This web site would like to store information on your phone.</string>
+ <string name="query_storage_quota_prompt">Increase storage quota</string>
+ <string name="query_storage_quota_message">This web site is over its current
+ storage limit. Would you like to increase its quota limit ?</string>
<string name="location_prompt">Access your location</string>
<string name="location_message">This web site would like to have access to your location.</string>
<string name="shortcut_prompt">Create a shortcut</string>
@@ -727,7 +764,26 @@
<string name="unrecognized_dialog_message">Unrecognized dialog type</string>
<string name="default_button">OK</string>
+ <!-- HTML5 dialogs -->
+ <!-- Used as a toast notification after the user close the html5 webstorage permission dialog -->
+ <string name="webstorage_notification">The quota for this site can be changed in the Local Storage section of the Browser settings</string>
+ <!-- Used in the Browser Settings -->
+ <string name="webstorage_clear_data_title">Clear Stored Data</string>
+ <string name="webstorage_clear_data_summary">Remove all databases associated with this website</string>
+ <!-- Confirmation dialog when the user ask to clear all data for an origin -->
+ <string name="webstorage_clear_data_dialog_title">Clear All Data</string>
+ <string name="webstorage_clear_data_dialog_message">All stored data by this origin will be deleted</string>
+ <string name="webstorage_clear_data_dialog_ok_button">Clear All</string>
+ <string name="webstorage_clear_data_dialog_cancel_button">Cancel</string>
+ <!-- Strings used in the summary of origins -->
+ <string name="webstorage_origin_summary_mb_stored">MB stored on your phone</string>
+
<!-- Zoom-related strings --><skip />
<!-- Caption for a button that is shown when the zoom widget is showing. The button's action will switch to the zoom overview mode. -->
<string name="zoom_overview_button_text">Overview</string>
+
+ <string name="error_console_header_text_minimized" translatable="false">Show JavaScript Console</string>
+ <string name="error_console_header_text_maximized" translatable="false">JavaScript Console</string>
+ <string name="error_console_eval_text_hint" translatable="false">Evaluate JavaScript</string>
+ <string name="error_console_eval_button_text" translatable="false">Evaluate</string>
</resources>
diff --git a/res/xml/browser_preferences.xml b/res/xml/browser_preferences.xml
index 23618c5..dffb550 100644
--- a/res/xml/browser_preferences.xml
+++ b/res/xml/browser_preferences.xml
@@ -62,6 +62,12 @@
android:summary="@string/pref_content_autofit_summary" />
<CheckBoxPreference
+ android:key="landscape_only"
+ android:defaultValue="false"
+ android:title="@string/pref_content_landscape_only"
+ android:summary="@string/pref_content_landscape_only_summary" />
+
+ <CheckBoxPreference
android:key="enable_javascript"
android:defaultValue="true"
android:title="@string/pref_content_javascript" />
@@ -128,6 +134,24 @@
android:dialogTitle="@string/clear"
android:dialogIcon="@android:drawable/ic_dialog_alert"/>
+ <!-- below preferences will be shown when html5 location is implemented -->
+ <!--
+
+ <CheckBoxPreference
+ android:key="request_location"
+ android:defaultValue="true"
+ android:title="@string/pref_privacy_request_location"
+ android:summary="@string/pref_privacy_request_location_summary" />
+
+ <com.android.browser.BrowserYesNoPreference
+ android:key="privacy_clear_location_requests"
+ android:title="@string/pref_privacy_clear_location_requests"
+ android:summary="@string/pref_privacy_clear_location_requests_summary"
+ android:dialogMessage="@string/pref_privacy_clear_location_requests_dlg"
+ android:dialogTitle="@string/clear"
+ android:dialogIcon="@android:drawable/ic_dialog_alert"/>
+ -->
+
</PreferenceCategory>
<PreferenceCategory
@@ -173,6 +197,11 @@
android:title="@string/pref_extras_gears_settings"
android:summary="@string/pref_extras_gears_settings_summary" />
+ <PreferenceScreen
+ android:key="website_settings"
+ android:title="@string/pref_extras_website_settings"
+ android:summary="@string/pref_extras_website_settings_summary" />
+
<com.android.browser.BrowserYesNoPreference
android:key="reset_default_preferences"
android:title="@string/pref_extras_reset_default"
diff --git a/res/xml/debug_preferences.xml b/res/xml/debug_preferences.xml
index 22b5997..c1ed1e6 100644
--- a/res/xml/debug_preferences.xml
+++ b/res/xml/debug_preferences.xml
@@ -19,6 +19,13 @@
<PreferenceCategory
android:title="@string/pref_development_title"
android:key="debug_menu" >
+
+ <!-- The javascript console is enabled by default when the user has
+ also enabled debug mode by navigating to about:debug. -->
+ <CheckBoxPreference
+ android:key="javascript_console"
+ android:defaultValue="true"
+ android:title="@string/pref_development_error_console" />
<CheckBoxPreference
android:key="small_screen"
@@ -50,6 +57,11 @@
android:defaultValue="false"
android:title="@string/pref_development_nav_dump" />
+ <EditTextPreference
+ android:key="js_engine_flags"
+ android:title="@string/js_engine_flags"
+ android:singleLine="true" />
+
<ListPreference
android:key="user_agent"
android:title="@string/pref_development_uastring"
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index cf3fe70..e827a7e 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -18,18 +18,14 @@
import android.app.Activity;
import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
import android.content.Intent;
import android.content.res.Resources;
-import android.database.Cursor;
import android.net.ParseException;
import android.net.WebAddress;
import android.os.Bundle;
import android.provider.Browser;
import android.view.View;
import android.view.Window;
-import android.webkit.WebIconDatabase;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
@@ -46,11 +42,6 @@
private View mCancelButton;
private boolean mEditingExisting;
private Bundle mMap;
-
- private static final String[] mProjection =
- { "_id", "url", "bookmark", "created", "title", "visits" };
- private static final String WHERE_CLAUSE = "url = ?";
- private final String[] SELECTION_ARGS = new String[1];
private View.OnClickListener mSaveBookmark = new View.OnClickListener() {
public void onClick(View v) {
@@ -151,70 +142,7 @@
setResult(RESULT_OK, (new Intent()).setAction(
getIntent().toString()).putExtras(mMap));
} else {
- // Want to append to the beginning of the list
- long creationTime = new Date().getTime();
- SELECTION_ARGS[0] = url;
- ContentResolver cr = getContentResolver();
- Cursor c = cr.query(Browser.BOOKMARKS_URI,
- mProjection,
- WHERE_CLAUSE,
- SELECTION_ARGS,
- null);
- ContentValues map = new ContentValues();
- if (c.moveToFirst() && c.getInt(c.getColumnIndexOrThrow(
- Browser.BookmarkColumns.BOOKMARK)) == 0) {
- // This means we have been to this site but not bookmarked
- // it, so convert the history item to a bookmark
- map.put(Browser.BookmarkColumns.CREATED, creationTime);
- map.put(Browser.BookmarkColumns.TITLE, title);
- map.put(Browser.BookmarkColumns.BOOKMARK, 1);
- cr.update(Browser.BOOKMARKS_URI, map,
- "_id = " + c.getInt(0), null);
- } else {
- int count = c.getCount();
- boolean matchedTitle = false;
- for (int i = 0; i < count; i++) {
- // One or more bookmarks already exist for this site.
- // Check the names of each
- c.moveToPosition(i);
- if (c.getString(c.getColumnIndexOrThrow(
- Browser.BookmarkColumns.TITLE)).equals(title)) {
- // The old bookmark has the same name.
- // Update its creation time.
- map.put(Browser.BookmarkColumns.CREATED,
- creationTime);
- cr.update(Browser.BOOKMARKS_URI, map,
- "_id = " + c.getInt(0), null);
- matchedTitle = true;
- }
- }
- if (!matchedTitle) {
- // Adding a bookmark for a site the user has visited,
- // or a new bookmark (with a different name) for a site
- // the user has visited
- map.put(Browser.BookmarkColumns.TITLE, title);
- map.put(Browser.BookmarkColumns.URL, url);
- map.put(Browser.BookmarkColumns.CREATED, creationTime);
- map.put(Browser.BookmarkColumns.BOOKMARK, 1);
- map.put(Browser.BookmarkColumns.DATE, 0);
- int visits = 0;
- if (count > 0) {
- // The user has already bookmarked, and possibly
- // visited this site. However, they are creating
- // a new bookmark with the same url but a different
- // name. The new bookmark should have the same
- // number of visits as the already created bookmark.
- visits = c.getInt(c.getColumnIndexOrThrow(
- Browser.BookmarkColumns.VISITS));
- }
- // Bookmark starts with 3 extra visits so that it will
- // bubble up in the most visited and goto search box
- map.put(Browser.BookmarkColumns.VISITS, visits + 3);
- cr.insert(Browser.BOOKMARKS_URI, map);
- }
- }
- WebIconDatabase.getInstance().retainIconForPageUrl(url);
- c.deactivate();
+ Bookmarks.addBookmark(null, getContentResolver(), url, title);
setResult(RESULT_OK);
}
} catch (IllegalStateException e) {
diff --git a/src/com/android/browser/AddNewBookmark.java b/src/com/android/browser/AddNewBookmark.java
index a75d002..5308f6b 100644
--- a/src/com/android/browser/AddNewBookmark.java
+++ b/src/com/android/browser/AddNewBookmark.java
@@ -47,17 +47,7 @@
mUrlText = (TextView) findViewById(R.id.url);
mImageView = (ImageView) findViewById(R.id.favicon);
}
-
- /**
- * Copy this BookmarkItem to item.
- * @param item BookmarkItem to receive the info from this BookmarkItem.
- */
- /* package */ void copyTo(AddNewBookmark item) {
- item.mTextView.setText(mTextView.getText());
- item.mUrlText.setText(mUrlText.getText());
- item.mImageView.setImageDrawable(mImageView.getDrawable());
- }
-
+
/**
* Set the new url for the bookmark item.
* @param url The new url for the bookmark item.
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
new file mode 100644
index 0000000..97e6b20
--- /dev/null
+++ b/src/com/android/browser/Bookmarks.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2009 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.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Browser;
+import android.util.Log;
+import android.webkit.WebIconDatabase;
+import android.widget.Toast;
+
+import java.util.Date;
+
+/**
+ * This class is purely to have a common place for adding/deleting bookmarks.
+ */
+/* package */ class Bookmarks {
+ private static final String WHERE_CLAUSE
+ = "url = ? OR url = ? OR url = ? OR url = ?";
+ private static final String WHERE_CLAUSE_SECURE = "url = ? OR url = ?";
+
+ private static String[] SELECTION_ARGS;
+
+ /**
+ * Add a bookmark to the database.
+ * @param context Context of the calling Activity. This is used to make
+ * Toast confirming that the bookmark has been added. If the
+ * caller provides null, the Toast will not be shown.
+ * @param cr The ContentResolver being used to add the bookmark to the db.
+ * @param url URL of the website to be bookmarked.
+ * @param name Provided name for the bookmark.
+ */
+ /* package */ static void addBookmark(Context context,
+ ContentResolver cr, String url, String name) {
+ // Want to append to the beginning of the list
+ long creationTime = new Date().getTime();
+ // First we check to see if the user has already visited this
+ // site. They may have bookmarked it in a different way from
+ // how it's stored in the database, so allow different combos
+ // to map to the same url.
+ boolean secure = false;
+ String compareString = url;
+ if (compareString.startsWith("http://")) {
+ compareString = compareString.substring(7);
+ } else if (compareString.startsWith("https://")) {
+ compareString = compareString.substring(8);
+ secure = true;
+ }
+ if (compareString.startsWith("www.")) {
+ compareString = compareString.substring(4);
+ }
+ if (secure) {
+ SELECTION_ARGS = new String[2];
+ SELECTION_ARGS[0] = "https://" + compareString;
+ SELECTION_ARGS[1] = "https://www." + compareString;
+ } else {
+ SELECTION_ARGS = new String[4];
+ SELECTION_ARGS[0] = compareString;
+ SELECTION_ARGS[1] = "www." + compareString;
+ SELECTION_ARGS[2] = "http://" + compareString;
+ SELECTION_ARGS[3] = "http://" + SELECTION_ARGS[1];
+ }
+ Cursor cursor = cr.query(Browser.BOOKMARKS_URI,
+ Browser.HISTORY_PROJECTION,
+ secure ? WHERE_CLAUSE_SECURE : WHERE_CLAUSE,
+ SELECTION_ARGS,
+ null);
+ ContentValues map = new ContentValues();
+ if (cursor.moveToFirst() && cursor.getInt(
+ Browser.HISTORY_PROJECTION_BOOKMARK_INDEX) == 0) {
+ // This means we have been to this site but not bookmarked
+ // it, so convert the history item to a bookmark
+ map.put(Browser.BookmarkColumns.CREATED, creationTime);
+ map.put(Browser.BookmarkColumns.TITLE, name);
+ map.put(Browser.BookmarkColumns.BOOKMARK, 1);
+ cr.update(Browser.BOOKMARKS_URI, map,
+ "_id = " + cursor.getInt(0), null);
+ } else {
+ int count = cursor.getCount();
+ boolean matchedTitle = false;
+ for (int i = 0; i < count; i++) {
+ // One or more bookmarks already exist for this site.
+ // Check the names of each
+ cursor.moveToPosition(i);
+ if (cursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX)
+ .equals(name)) {
+ // The old bookmark has the same name.
+ // Update its creation time.
+ map.put(Browser.BookmarkColumns.CREATED,
+ creationTime);
+ cr.update(Browser.BOOKMARKS_URI, map,
+ "_id = " + cursor.getInt(0), null);
+ matchedTitle = true;
+ break;
+ }
+ }
+ if (!matchedTitle) {
+ // Adding a bookmark for a site the user has visited,
+ // or a new bookmark (with a different name) for a site
+ // the user has visited
+ map.put(Browser.BookmarkColumns.TITLE, name);
+ map.put(Browser.BookmarkColumns.URL, url);
+ map.put(Browser.BookmarkColumns.CREATED, creationTime);
+ map.put(Browser.BookmarkColumns.BOOKMARK, 1);
+ map.put(Browser.BookmarkColumns.DATE, 0);
+ int visits = 0;
+ if (count > 0) {
+ // The user has already bookmarked, and possibly
+ // visited this site. However, they are creating
+ // a new bookmark with the same url but a different
+ // name. The new bookmark should have the same
+ // number of visits as the already created bookmark.
+ visits = cursor.getInt(
+ Browser.HISTORY_PROJECTION_VISITS_INDEX);
+ }
+ // Bookmark starts with 3 extra visits so that it will
+ // bubble up in the most visited and goto search box
+ map.put(Browser.BookmarkColumns.VISITS, visits + 3);
+ cr.insert(Browser.BOOKMARKS_URI, map);
+ }
+ }
+ WebIconDatabase.getInstance().retainIconForPageUrl(url);
+ cursor.deactivate();
+ if (context != null) {
+ Toast.makeText(context, R.string.added_to_bookmarks,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Remove a bookmark from the database. If the url is a visited site, it
+ * will remain in the database, but only as a history item, and not as a
+ * bookmarked site.
+ * @param context Context of the calling Activity. This is used to make
+ * Toast confirming that the bookmark has been removed. If the
+ * caller provides null, the Toast will not be shown.
+ * @param cr The ContentResolver being used to remove the bookmark.
+ * @param url URL of the website to be removed.
+ */
+ /* package */ static void removeFromBookmarks(Context context,
+ ContentResolver cr, String url) {
+ Cursor cursor = cr.query(
+ Browser.BOOKMARKS_URI,
+ Browser.HISTORY_PROJECTION,
+ "url = ?",
+ new String[] { url },
+ null);
+ boolean first = cursor.moveToFirst();
+ // Should be in the database no matter what
+ if (!first) {
+ throw new AssertionError("URL is not in the database!");
+ }
+ // Remove from bookmarks
+ WebIconDatabase.getInstance().releaseIconForPageUrl(url);
+ Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
+ cursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
+ int numVisits = cursor.getInt(
+ Browser.HISTORY_PROJECTION_VISITS_INDEX);
+ if (0 == numVisits) {
+ cr.delete(uri, null, null);
+ } else {
+ // It is no longer a bookmark, but it is still a visited
+ // site.
+ ContentValues values = new ContentValues();
+ values.put(Browser.BookmarkColumns.BOOKMARK, 0);
+ try {
+ cr.update(uri, values, null, null);
+ } catch (IllegalStateException e) {
+ Log.e("removeFromBookmarks", "no database!");
+ }
+ }
+ if (context != null) {
+ Toast.makeText(context, R.string.removed_from_bookmarks,
+ Toast.LENGTH_LONG).show();
+ }
+ cursor.deactivate();
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index b444d0f..d148c0a 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -28,6 +28,7 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
@@ -35,6 +36,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.DialogInterface.OnCancelListener;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.AssetManager;
@@ -107,11 +109,14 @@
import android.webkit.CookieSyncManager;
import android.webkit.DownloadListener;
import android.webkit.HttpAuthHandler;
+import android.webkit.PluginManager;
import android.webkit.SslErrorHandler;
import android.webkit.URLUtil;
import android.webkit.WebChromeClient;
+import android.webkit.WebChromeClient.CustomViewCallback;
import android.webkit.WebHistoryItem;
import android.webkit.WebIconDatabase;
+import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
@@ -121,6 +126,7 @@
import android.widget.Toast;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -159,6 +165,8 @@
private SensorManager mSensorManager = null;
+ private WebStorage.QuotaUpdater mWebStorageQuotaUpdater = null;
+
// These are single-character shortcuts for searching popular sources.
private static final int SHORTCUT_INVALID = 0;
private static final int SHORTCUT_GOOGLE_SEARCH = 1;
@@ -583,11 +591,6 @@
}
copyBuildInfos();
-
- // Refresh the plugin list.
- if (mTabControl.getCurrentWebView() != null) {
- mTabControl.getCurrentWebView().refreshPlugins(false);
- }
} catch (IOException e) {
Log.e(TAG, "IO Exception: " + e);
}
@@ -634,16 +637,22 @@
}
}
+ // Flag to enable the touchable browser bar with buttons
+ private final boolean CUSTOM_BROWSER_BAR = true;
+
@Override public void onCreate(Bundle icicle) {
if (LOGV_ENABLED) {
Log.v(LOGTAG, this + " onStart");
}
super.onCreate(icicle);
- this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
- this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
- this.requestWindowFeature(Window.FEATURE_PROGRESS);
- this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-
+ if (CUSTOM_BROWSER_BAR) {
+ this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ } else {
+ this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
+ this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
+ this.requestWindowFeature(Window.FEATURE_PROGRESS);
+ this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ }
// test the browser in OpenGL
// requestWindowFeature(Window.FEATURE_OPENGL);
@@ -668,8 +677,37 @@
mGenericFavicon = getResources().getDrawable(
R.drawable.app_web_browser_sm);
- mContentView = (FrameLayout) getWindow().getDecorView().findViewById(
- com.android.internal.R.id.content);
+ FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
+ .findViewById(com.android.internal.R.id.content);
+ if (CUSTOM_BROWSER_BAR) {
+ // This FrameLayout will hold the custom FrameLayout and a LinearLayout
+ // that contains the title bar and a FrameLayout, which
+ // holds everything else.
+ FrameLayout browserFrameLayout = (FrameLayout) LayoutInflater.from(this)
+ .inflate(R.layout.custom_screen, null);
+ mTitleBar = (TitleBar) browserFrameLayout.findViewById(R.id.title_bar);
+ mTitleBar.setBrowserActivity(this);
+ mContentView = (FrameLayout) browserFrameLayout.findViewById(
+ R.id.main_content);
+ mErrorConsoleContainer = (LinearLayout) browserFrameLayout.findViewById(
+ R.id.error_console);
+ mCustomViewContainer = (FrameLayout) browserFrameLayout
+ .findViewById(R.id.fullscreen_custom_content);
+ frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS);
+ } else {
+ mCustomViewContainer = new FrameLayout(this);
+ mCustomViewContainer.setBackgroundColor(Color.BLACK);
+ mContentView = new FrameLayout(this);
+
+ LinearLayout linearLayout = new LinearLayout(this);
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ mErrorConsoleContainer = new LinearLayout(this);
+ linearLayout.addView(mErrorConsoleContainer, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ linearLayout.addView(mContentView, COVER_SCREEN_PARAMS);
+ frameLayout.addView(mCustomViewContainer, COVER_SCREEN_PARAMS);
+ frameLayout.addView(linearLayout, COVER_SCREEN_PARAMS);
+ }
// Create the tab control and our initial tab
mTabControl = new TabControl(this);
@@ -748,6 +786,11 @@
// are not animating from the tab picker.
attachTabToContentView(mTabControl.getCurrentTab());
}
+ // Read JavaScript flags if it exists.
+ String jsFlags = mSettings.getJsFlags();
+ if (jsFlags.trim().length() != 0) {
+ mTabControl.getCurrentWebView().setJsFlags(jsFlags);
+ }
/* enables registration for changes in network status from
http stack */
@@ -765,6 +808,52 @@
}
}
};
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mPackageInstallationReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final String packageName = intent.getData()
+ .getSchemeSpecificPart();
+ final boolean replacing = intent.getBooleanExtra(
+ Intent.EXTRA_REPLACING, false);
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
+ // if it is replacing, refreshPlugins() when adding
+ return;
+ }
+ PackageManager pm = BrowserActivity.this.getPackageManager();
+ PackageInfo pkgInfo = null;
+ try {
+ pkgInfo = pm.getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+ if (pkgInfo != null) {
+ String permissions[] = pkgInfo.requestedPermissions;
+ if (permissions == null) {
+ return;
+ }
+ boolean permissionOk = false;
+ for (String permit : permissions) {
+ if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
+ permissionOk = true;
+ break;
+ }
+ }
+ if (permissionOk) {
+ PluginManager.getInstance(BrowserActivity.this)
+ .refreshPlugins(
+ Intent.ACTION_PACKAGE_ADDED
+ .equals(action));
+ }
+ }
+ }
+ };
+ registerReceiver(mPackageInstallationReceiver, filter);
}
@Override
@@ -811,7 +900,7 @@
(flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
final String appId =
intent.getStringExtra(Browser.EXTRA_APPLICATION_ID);
- final TabControl.Tab appTab = mTabControl.getTabFromId(appId);
+ TabControl.Tab appTab = mTabControl.getTabFromId(appId);
if (appTab != null) {
Log.i(LOGTAG, "Reusing tab for " + appId);
// Dismiss the subwindow if applicable.
@@ -824,14 +913,14 @@
// page, it can be reused.
boolean needsLoad =
mTabControl.recreateWebView(appTab, urlData.mUrl);
-
+
if (current != appTab) {
showTab(appTab, needsLoad ? urlData : EMPTY_URL_DATA);
} else {
if (mTabOverview != null && mAnimationCount == 0) {
sendAnimateFromOverview(appTab, false,
- needsLoad ? urlData : EMPTY_URL_DATA, TAB_OVERVIEW_DELAY,
- null);
+ needsLoad ? urlData : EMPTY_URL_DATA,
+ TAB_OVERVIEW_DELAY, null);
} else {
// If the tab was the current tab, we have to attach
// it to the view system again.
@@ -842,12 +931,32 @@
}
}
return;
+ } else {
+ // No matching application tab, try to find a regular tab
+ // with a matching url.
+ appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
+ if (appTab != null) {
+ if (current != appTab) {
+ // Use EMPTY_URL_DATA so we do not reload the page
+ showTab(appTab, EMPTY_URL_DATA);
+ } else {
+ if (mTabOverview != null && mAnimationCount == 0) {
+ sendAnimateFromOverview(appTab, false,
+ EMPTY_URL_DATA, TAB_OVERVIEW_DELAY,
+ null);
+ }
+ // Don't do anything here since we are on the
+ // correct page.
+ }
+ } else {
+ // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
+ // will be opened in a new tab unless we have reached
+ // MAX_TABS. Then the url will be opened in the current
+ // tab. If a new tab is created, it will have "true" for
+ // exit on close.
+ openTabAndShow(urlData, null, true, appId);
+ }
}
- // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url will be
- // opened in a new tab unless we have reached MAX_TABS. Then the
- // url will be opened in the current tab. If a new tab is
- // created, it will have "true" for exit on close.
- openTabAndShow(urlData, null, true, appId);
} else {
if ("about:debug".equals(urlData.mUrl)) {
mSettings.toggleDebugSettings();
@@ -1105,8 +1214,9 @@
return;
}
+ mTabControl.resumeCurrentTab();
mActivityInPause = false;
- resumeWebView();
+ resumeWebViewTimers();
if (mWakeLock.isHeld()) {
mHandler.removeMessages(RELEASE_WAKELOCK);
@@ -1164,8 +1274,9 @@
return;
}
+ mTabControl.pauseCurrentTab();
mActivityInPause = true;
- if (mTabControl.getCurrentIndex() >= 0 && !pauseWebView()) {
+ if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
mWakeLock.acquire();
mHandler.sendMessageDelayed(mHandler
.obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
@@ -1216,6 +1327,8 @@
// "com.android.masfproxyservice",
// "com.android.masfproxyservice.MasfProxyService"));
//stopService(proxyServiceIntent);
+
+ unregisterReceiver(mPackageInstallationReceiver);
}
@Override
@@ -1264,7 +1377,7 @@
mTabControl.freeMemory();
}
- private boolean resumeWebView() {
+ private boolean resumeWebViewTimers() {
if ((!mActivityInPause && !mPageStarted) ||
(mActivityInPause && mPageStarted)) {
CookieSyncManager.getInstance().startSync();
@@ -1278,7 +1391,7 @@
}
}
- private boolean pauseWebView() {
+ private boolean pauseWebViewTimers() {
if (mActivityInPause && !mPageStarted) {
CookieSyncManager.getInstance().stopSync();
WebView w = mTabControl.getCurrentWebView();
@@ -1875,6 +1988,20 @@
final WebView main = t.getWebView();
// Attach the main WebView.
mContentView.addView(main, COVER_SCREEN_PARAMS);
+
+ if (mShouldShowErrorConsole) {
+ ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+ if (errorConsole.numberOfErrors() == 0) {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+ } else {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+ }
+
+ mErrorConsoleContainer.addView(errorConsole,
+ new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ }
+
// Attach the sub window if necessary
attachSubWindow(t);
// Request focus on the top window.
@@ -1896,6 +2023,11 @@
private void removeTabFromContentView(TabControl.Tab t) {
// Remove the main WebView.
mContentView.removeView(t.getWebView());
+
+ if (mTabControl.getCurrentErrorConsole(false) != null) {
+ mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
+ }
+
// Remove the sub window if it exists.
if (t.getSubWebView() != null) {
mContentView.removeView(t.getSubWebViewContainer());
@@ -2375,7 +2507,11 @@
// While the tab overview is animating or being shown, block changes
// to the title.
if (mAnimationCount == 0 && mTabOverview == null) {
- setTitle(buildUrlTitle(url, title));
+ if (CUSTOM_BROWSER_BAR) {
+ mTitleBar.setTitleAndUrl(title, url);
+ } else {
+ setTitle(buildUrlTitle(url, title));
+ }
}
}
@@ -2416,7 +2552,7 @@
* or an empty string if, for example, the URL in question is a
* file:// URL with no hostname.
*/
- private static String buildTitleUrl(String url) {
+ /* package */ static String buildTitleUrl(String url) {
String titleUrl = null;
if (url != null) {
@@ -2452,18 +2588,34 @@
if (mAnimationCount > 0 || mTabOverview != null) {
return;
}
- Drawable[] array = new Drawable[2];
- PaintDrawable p = new PaintDrawable(Color.WHITE);
- p.setCornerRadius(3f);
- array[0] = p;
- if (icon == null) {
- array[1] = mGenericFavicon;
+ if (CUSTOM_BROWSER_BAR) {
+ Drawable[] array = new Drawable[3];
+ array[0] = new PaintDrawable(Color.BLACK);
+ PaintDrawable p = new PaintDrawable(Color.WHITE);
+ array[1] = p;
+ if (icon == null) {
+ array[2] = mGenericFavicon;
+ } else {
+ array[2] = new BitmapDrawable(icon);
+ }
+ LayerDrawable d = new LayerDrawable(array);
+ d.setLayerInset(1, 1, 1, 1, 1);
+ d.setLayerInset(2, 2, 2, 2, 2);
+ mTitleBar.setFavicon(d);
} else {
- array[1] = new BitmapDrawable(icon);
+ Drawable[] array = new Drawable[2];
+ PaintDrawable p = new PaintDrawable(Color.WHITE);
+ p.setCornerRadius(3f);
+ array[0] = p;
+ if (icon == null) {
+ array[1] = mGenericFavicon;
+ } else {
+ array[1] = new BitmapDrawable(icon);
+ }
+ LayerDrawable d = new LayerDrawable(array);
+ d.setLayerInset(1, 2, 2, 2, 2);
+ getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
}
- LayerDrawable d = new LayerDrawable(array);
- d.setLayerInset(1, 2, 2, 2, 2);
- getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
}
/**
@@ -2541,9 +2693,9 @@
finish();
return;
}
- // call pauseWebView() now, we won't be able to call it in
- // onPause() as the WebView won't be valid.
- pauseWebView();
+ // call pauseWebViewTimers() now, we won't be able to call
+ // it in onPause() as the WebView won't be valid.
+ pauseWebViewTimers();
removeTabFromContentView(current);
mTabControl.removeTab(current);
}
@@ -2567,10 +2719,15 @@
// because of accumulated key events,
// we should ignore it as browser is not active any more.
WebView topWindow = getTopWindow();
- if (topWindow == null)
+ if (topWindow == null && mCustomView == null)
return KeyTracker.State.NOT_TRACKING;
if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // Check if a custom view is currently showing and, if it is, hide it.
+ if (mCustomView != null) {
+ mWebChromeClient.onHideCustomView();
+ return KeyTracker.State.DONE_TRACKING;
+ }
// During animations, block the back key so that other animations
// are not triggered and so that we don't end up destroying all the
// WebViews before finishing the animation.
@@ -2769,6 +2926,58 @@
}
};
+ private void updateScreenshot(WebView view) {
+ // If this is a bookmarked site, add a screenshot to the database.
+ // FIXME: When should we update? Every time?
+ // FIXME: Would like to make sure there is actually something to
+ // draw, but the API for that (WebViewCore.pictureReady()) is not
+ // currently accessible here.
+ String original = view.getOriginalUrl();
+ if (original != null) {
+ // copied from BrowserBookmarksAdapter
+ int query = original.indexOf('?');
+ String noQuery = original;
+ if (query != -1) {
+ noQuery = original.substring(0, query);
+ }
+ String URL = noQuery + '?';
+ String[] selArgs = new String[] { noQuery, URL };
+ final String where
+ = "(url == ? OR url GLOB ? || '*') AND bookmark == 1";
+ final String[] projection
+ = new String[] { Browser.BookmarkColumns._ID };
+ ContentResolver cr = getContentResolver();
+ final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection,
+ where, selArgs, null);
+ boolean succeed = c.moveToFirst();
+ ContentValues values = null;
+ while (succeed) {
+ if (values == null) {
+ final ByteArrayOutputStream os
+ = new ByteArrayOutputStream();
+ Picture thumbnail = view.capturePicture();
+ // Keep width and height in sync with BrowserBookmarksPage
+ // and bookmark_thumb
+ Bitmap bm = Bitmap.createBitmap(100, 80,
+ Bitmap.Config.ARGB_4444);
+ Canvas canvas = new Canvas(bm);
+ // May need to tweak these values to determine what is the
+ // best scale factor
+ canvas.scale(.5f, .5f);
+ thumbnail.draw(canvas);
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+ values = new ContentValues();
+ values.put(Browser.BookmarkColumns.THUMBNAIL,
+ os.toByteArray());
+ }
+ cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
+ c.getInt(0)), values, null, null);
+ succeed = c.moveToNext();
+ }
+ c.close();
+ }
+ }
+
// -------------------------------------------------------------------------
// WebViewClient implementation.
//-------------------------------------------------------------------------
@@ -2796,6 +3005,15 @@
public void onPageStarted(WebView view, String url, Bitmap favicon) {
resetLockIcon(url);
setUrlTitle(url, null);
+
+ ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
+ if (errorConsole != null) {
+ errorConsole.clearErrorMessages();
+ if (mShouldShowErrorConsole) {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+ }
+ }
+
// Call updateIcon instead of setFavicon so the bookmark
// database can be updated.
updateIcon(url, favicon);
@@ -2844,8 +3062,9 @@
if (!mPageStarted) {
mPageStarted = true;
- // if onResume() has been called, resumeWebView() does nothing.
- resumeWebView();
+ // if onResume() has been called, resumeWebViewTimers() does
+ // nothing.
+ resumeWebViewTimers();
}
// reset sync timer to avoid sync starts during loading a page
@@ -2879,6 +3098,7 @@
// Update the lock icon image only once we are done loading
updateLockIconImage(mLockIconType);
+ updateScreenshot(view);
// Performance probe
if (false) {
@@ -2966,9 +3186,9 @@
if (mPageStarted) {
mPageStarted = false;
- // pauseWebView() will do nothing and return false if onPause()
- // is not called yet.
- if (pauseWebView()) {
+ // pauseWebViewTimers() will do nothing and return false if
+ // onPause() is not called yet.
+ if (pauseWebViewTimers()) {
if (mWakeLock.isHeld()) {
mHandler.removeMessages(RELEASE_WAKELOCK);
mWakeLock.release();
@@ -3014,9 +3234,9 @@
if (url.startsWith("about:")) {
return false;
}
-
+
Intent intent;
-
+
// perform generic parsing of the URI to turn it into an Intent.
try {
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
@@ -3514,8 +3734,13 @@
// Block progress updates to the title bar while the tab overview
// is animating or being displayed.
if (mAnimationCount == 0 && mTabOverview == null) {
- getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
- newProgress * 100);
+ if (CUSTOM_BROWSER_BAR) {
+ mTitleBar.setProgress(newProgress);
+ } else {
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+ newProgress * 100);
+
+ }
}
if (newProgress == 100) {
@@ -3549,6 +3774,8 @@
url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
return;
}
+ // See if we can find the current url in our history database and
+ // add the new title to it.
if (url.startsWith("http://www.")) {
url = url.substring(11);
} else if (url.startsWith("http://")) {
@@ -3563,9 +3790,6 @@
Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
Browser.HISTORY_PROJECTION, where, selArgs, null);
if (c.moveToFirst()) {
- if (LOGV_ENABLED) {
- Log.v(LOGTAG, "updating cursor");
- }
// Current implementation of database only has one entry per
// url.
ContentValues map = new ContentValues();
@@ -3585,6 +3809,94 @@
public void onReceivedIcon(WebView view, Bitmap icon) {
updateIcon(view.getUrl(), icon);
}
+
+ @Override
+ public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
+ if (mCustomView != null)
+ return;
+
+ // Add the custom view to its container.
+ mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
+ mCustomView = view;
+ mCustomViewCallback = callback;
+ // Save the menu state and set it to empty while the custom
+ // view is showing.
+ mOldMenuState = mMenuState;
+ mMenuState = EMPTY_MENU;
+ // Hide the content view.
+ mContentView.setVisibility(View.GONE);
+ // Finally show the custom view container.
+ mCustomViewContainer.setVisibility(View.VISIBLE);
+ mCustomViewContainer.bringToFront();
+ }
+
+ @Override
+ public void onHideCustomView() {
+ if (mCustomView == null)
+ return;
+
+ // Hide the custom view.
+ mCustomView.setVisibility(View.GONE);
+ // Remove the custom view from its container.
+ mCustomViewContainer.removeView(mCustomView);
+ mCustomView = null;
+ // Reset the old menu state.
+ mMenuState = mOldMenuState;
+ mOldMenuState = EMPTY_MENU;
+ mCustomViewContainer.setVisibility(View.GONE);
+ mCustomViewCallback.onCustomViewHidden();
+ // Show the content view.
+ mContentView.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * The origin has exceeded it's database quota.
+ * @param url the URL that exceeded the quota
+ * @param databaseIdentifier the identifier of the database on
+ * which the transaction that caused the quota overflow was run
+ * @param currentQuota the current quota for the origin.
+ * @param quotaUpdater The callback to run when a decision to allow or
+ * deny quota has been made. Don't forget to call this!
+ */
+ @Override
+ public void onExceededDatabaseQuota(String url,
+ String databaseIdentifier, long currentQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ if(LOGV_ENABLED) {
+ Log.v(LOGTAG,
+ "BrowserActivity received onExceededDatabaseQuota for "
+ + url +
+ ":"
+ + databaseIdentifier +
+ "(current quota: "
+ + currentQuota +
+ ")");
+ }
+ mWebStorageQuotaUpdater = quotaUpdater;
+ String DIALOG_PACKAGE = "com.android.browser";
+ String DIALOG_CLASS = DIALOG_PACKAGE + ".PermissionDialog";
+ Intent intent = new Intent();
+ intent.setClassName(DIALOG_PACKAGE, DIALOG_CLASS);
+ intent.putExtra(PermissionDialog.PARAM_ORIGIN, url);
+ intent.putExtra(PermissionDialog.PARAM_QUOTA, currentQuota);
+ startActivityForResult(intent, WEBSTORAGE_QUOTA_DIALOG);
+ }
+
+ /* Adds a JavaScript error message to the system log.
+ * @param message The error message to report.
+ * @param lineNumber The line number of the error.
+ * @param sourceID The name of the source file that caused the error.
+ */
+ @Override
+ public void addMessageToConsole(String message, int lineNumber, String sourceID) {
+ ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+ errorConsole.addErrorMessage(message, sourceID, lineNumber);
+ if (mShouldShowErrorConsole &&
+ errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+ }
+ Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
+ }
};
/**
@@ -3700,19 +4012,19 @@
String cookies = CookieManager.getInstance().getCookie(url);
ContentValues values = new ContentValues();
- values.put(Downloads.URI, uri.toString());
- values.put(Downloads.COOKIE_DATA, cookies);
- values.put(Downloads.USER_AGENT, userAgent);
- values.put(Downloads.NOTIFICATION_PACKAGE,
+ values.put(Downloads.COLUMN_URI, uri.toString());
+ values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
+ values.put(Downloads.COLUMN_USER_AGENT, userAgent);
+ values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
getPackageName());
- values.put(Downloads.NOTIFICATION_CLASS,
+ values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
BrowserDownloadPage.class.getCanonicalName());
- values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- values.put(Downloads.MIMETYPE, mimetype);
- values.put(Downloads.FILENAME_HINT, filename);
- values.put(Downloads.DESCRIPTION, uri.getHost());
+ values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
+ values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
+ values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
if (contentLength > 0) {
- values.put(Downloads.TOTAL_BYTES, contentLength);
+ values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
}
if (mimetype == null) {
// We must have long pressed on a link or image to download it. We
@@ -3778,7 +4090,11 @@
// If the tab overview is animating or being shown, do not update the
// lock icon.
if (mAnimationCount == 0 && mTabOverview == null) {
- getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
+ if (CUSTOM_BROWSER_BAR) {
+ mTitleBar.setLock(d);
+ } else {
+ getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
+ }
}
}
@@ -4271,7 +4587,17 @@
String data = intent.getAction();
Bundle extras = intent.getExtras();
if (extras != null && extras.getBoolean("new_window", false)) {
- openTab(data);
+ final TabControl.Tab newTab = openTab(data);
+ if (mSettings.openInBackground() &&
+ newTab != null && mTabOverview != null) {
+ mTabControl.populatePickerData(newTab);
+ mTabControl.setCurrentTab(newTab);
+ mTabOverview.add(newTab);
+ mTabOverview.setCurrentIndex(
+ mTabControl.getCurrentIndex());
+ sendAnimateFromOverview(newTab, false,
+ EMPTY_URL_DATA, TAB_OVERVIEW_DELAY, null);
+ }
} else {
final TabControl.Tab currentTab =
mTabControl.getCurrentTab();
@@ -4279,8 +4605,8 @@
// middle of an animation, animate away from it to the
// current tab.
if (mTabOverview != null && mAnimationCount == 0) {
- sendAnimateFromOverview(currentTab, false, new UrlData(data),
- TAB_OVERVIEW_DELAY, null);
+ sendAnimateFromOverview(currentTab, false,
+ new UrlData(data), TAB_OVERVIEW_DELAY, null);
} else {
dismissSubWindow(currentTab);
if (data != null && data.length() != 0) {
@@ -4290,6 +4616,14 @@
}
}
break;
+ case WEBSTORAGE_QUOTA_DIALOG:
+ long currentQuota = 0;
+ if (resultCode == RESULT_OK && intent != null) {
+ currentQuota = intent.getLongExtra(
+ PermissionDialog.PARAM_QUOTA, currentQuota);
+ }
+ mWebStorageQuotaUpdater.updateQuota(currentQuota);
+ break;
default:
break;
}
@@ -4330,8 +4664,8 @@
// was clicked on.
if (mTabControl.getTabCount() == 0) {
current = mTabControl.createNewTab();
- sendAnimateFromOverview(current, true,
- new UrlData(mSettings.getHomePage()), TAB_OVERVIEW_DELAY, null);
+ sendAnimateFromOverview(current, true, new UrlData(
+ mSettings.getHomePage()), TAB_OVERVIEW_DELAY, null);
} else {
final int index = position > 0 ? (position - 1) : 0;
current = mTabControl.getTab(index);
@@ -4367,8 +4701,8 @@
if (index == ImageGrid.NEW_TAB) {
openTabAndShow(mSettings.getHomePage(), null, false, null);
} else {
- sendAnimateFromOverview(mTabControl.getTab(index),
- false, EMPTY_URL_DATA, 0, null);
+ sendAnimateFromOverview(mTabControl.getTab(index), false,
+ EMPTY_URL_DATA, 0, null);
}
}
}
@@ -4390,13 +4724,19 @@
AnimatingView(Context ctxt, TabControl.Tab t) {
super(ctxt);
mTab = t;
- // Use the top window in the animation since the tab overview will
- // display the top window in each cell.
- final WebView w = t.getTopWindow();
- mPicture = w.capturePicture();
- mScale = w.getScale() / w.getWidth();
- mScrollX = w.getScrollX();
- mScrollY = w.getScrollY();
+ if (t != null && t.getTopWindow() != null) {
+ // Use the top window in the animation since the tab overview
+ // will display the top window in each cell.
+ final WebView w = t.getTopWindow();
+ mPicture = w.capturePicture();
+ mScale = w.getScale() / w.getWidth();
+ mScrollX = w.getScrollX();
+ mScrollY = w.getScrollY();
+ } else {
+ mPicture = null;
+ mScale = 1.0f;
+ mScrollX = mScrollY = 0;
+ }
}
@Override
@@ -4470,16 +4810,20 @@
mAnimationCount++;
// Always change the title bar to the window overview title while
// animating.
- getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, null);
- getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null);
- getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
- Window.PROGRESS_VISIBILITY_OFF);
- setTitle(R.string.tab_picker_title);
+ if (CUSTOM_BROWSER_BAR) {
+ mTitleBar.setToTabPicker();
+ } else {
+ getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, null);
+ getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null);
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+ Window.PROGRESS_VISIBILITY_OFF);
+ setTitle(R.string.tab_picker_title);
+ }
// Make the menu empty until the animation completes.
mMenuState = EMPTY_MENU;
}
- private void bookmarksOrHistoryPicker(boolean startWithHistory) {
+ /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
WebView current = mTabControl.getCurrentWebView();
if (current == null) {
return;
@@ -4555,7 +4899,7 @@
return 0;
}
- static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
+ protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
"(?i)" + // switch on case insensitive matching
"(" + // begin group for schema
"(?:http|https|file):\\/\\/" +
@@ -4620,6 +4964,34 @@
return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
}
+ /* package */ void setShouldShowErrorConsole(boolean flag) {
+ if (flag == mShouldShowErrorConsole) {
+ // Nothing to do.
+ return;
+ }
+
+ mShouldShowErrorConsole = flag;
+
+ ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+
+ if (flag) {
+ // Setting the show state of the console will cause it's the layout to be inflated.
+ if (errorConsole.numberOfErrors() > 0) {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+ } else {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+ }
+
+ // Now we can add it to the main view.
+ mErrorConsoleContainer.addView(errorConsole,
+ new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ } else {
+ mErrorConsoleContainer.removeView(errorConsole);
+ }
+
+ }
+
private final static int LOCK_ICON_UNSECURE = 0;
private final static int LOCK_ICON_SECURE = 1;
private final static int LOCK_ICON_MIXED = 2;
@@ -4632,11 +5004,15 @@
private ContentResolver mResolver;
private FrameLayout mContentView;
private ImageGrid mTabOverview;
+ private View mCustomView;
+ private FrameLayout mCustomViewContainer;
+ private WebChromeClient.CustomViewCallback mCustomViewCallback;
// FIXME, temp address onPrepareMenu performance problem. When we move everything out of
// view, we should rewrite this.
private int mCurrentMenuState = 0;
private int mMenuState = R.id.MAIN_MENU;
+ private int mOldMenuState = EMPTY_MENU;
private static final int EMPTY_MENU = -1;
private Menu mMenu;
@@ -4727,6 +5103,11 @@
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT);
+ /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
+ new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT,
+ Gravity.CENTER);
// Google search
final static String QuickSearch_G = "http://www.google.com/m?q=%s";
// Wikipedia search
@@ -4760,6 +5141,11 @@
private Toast mStopToast;
+ private TitleBar mTitleBar;
+
+ private LinearLayout mErrorConsoleContainer = null;
+ private boolean mShouldShowErrorConsole = false;
+
// Used during animations to prevent other animations from being triggered.
// A count is used since the animation to and from the Window overview can
// overlap. A count of 0 means no animation where a count of > 0 means
@@ -4777,10 +5163,13 @@
private IntentFilter mNetworkStateChangedFilter;
private BroadcastReceiver mNetworkStateIntentReceiver;
+ private BroadcastReceiver mPackageInstallationReceiver;
+
// activity requestCode
- final static int COMBO_PAGE = 1;
- final static int DOWNLOAD_PAGE = 2;
- final static int PREFERENCES_PAGE = 3;
+ final static int COMBO_PAGE = 1;
+ final static int DOWNLOAD_PAGE = 2;
+ final static int PREFERENCES_PAGE = 3;
+ final static int WEBSTORAGE_QUOTA_DIALOG = 4;
// the frenquency of checking whether system memory is low
final static int CHECK_MEMORY_INTERVAL = 30000; // 30 seconds
@@ -4830,7 +5219,7 @@
String mEncoding;
@Override
boolean isEmpty() {
- return mInlined == null || mInlined.length() == 0 || super.isEmpty();
+ return mInlined == null || mInlined.length() == 0 || super.isEmpty();
}
@Override
diff --git a/src/com/android/browser/BrowserBackupAgent.java b/src/com/android/browser/BrowserBackupAgent.java
new file mode 100644
index 0000000..9e10370
--- /dev/null
+++ b/src/com/android/browser/BrowserBackupAgent.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2009 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 java.io.IOException;
+
+import android.app.BackupAgent;
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.database.Cursor;
+import android.os.ParcelFileDescriptor;
+import android.provider.Browser;
+import android.provider.Browser.BookmarkColumns;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+import java.util.zip.CRC32;
+
+/**
+ * Settings backup agent for the Android browser. Currently the only thing
+ * stored is the set of bookmarks. It's okay if I/O exceptions are thrown
+ * out of the agent; the calling code handles it and the backup operation
+ * simply fails.
+ */
+public class BrowserBackupAgent extends BackupAgent {
+ static final String BOOKMARK_KEY = "_bookmarks_";
+
+ /**
+ * In order to determine whether the bookmark set has changed since the
+ * last time we did a backup, we store the following bits of info in the
+ * state file after a backup:
+ *
+ * 1. the size of the flattened bookmark file
+ * 2. the CRC32 of that file
+ *
+ * After we flatten the bookmarks file here in onBackup, we compare its
+ * metrics with the values from the saved state. If they match, it means
+ * the bookmarks didn't really change and we don't need to send the data.
+ * (If they don't match, of course, then they've changed and we do indeed
+ * send the new flattened file to be backed up.)
+ */
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ long savedFileSize = -1;
+ long savedCrc = -1;
+
+ // Extract the previous bookmark file size & CRC from the saved state
+ DataInputStream in = new DataInputStream(
+ new FileInputStream(oldState.getFileDescriptor()));
+ try {
+ savedFileSize = in.readLong();
+ savedCrc = in.readLong();
+ } catch (EOFException e) {
+ // It means we had no previous state; that's fine
+ }
+
+ // TODO: BUILD THE FLATTENED BOOKMARK FILE FROM THE DB (into tmpfile)
+ File tmpfile = getFilesDir().createTempFile("bkp", null);
+ CRC32 crc = new CRC32();
+ try {
+ Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
+ new String[] { BookmarkColumns.URL, BookmarkColumns.VISITS,
+ BookmarkColumns.DATE, BookmarkColumns.CREATED,
+ BookmarkColumns.TITLE },
+ BookmarkColumns.BOOKMARK + " == 1 ", null, null);
+ int count = cursor.getCount();
+ FileOutputStream out = new FileOutputStream(tmpfile);
+ for (int i = 0; i < count; i++) {
+ StringBuilder sb = new StringBuilder();
+ // URL
+ sb.append("'");
+ sb.append(cursor.getString(0));
+ sb.append("','");
+ // VISITS
+ sb.append(cursor.getInt(1));
+ sb.append("','");
+ // DATE
+ sb.append(cursor.getLong(2));
+ sb.append("','");
+ // CREATED
+ sb.append(cursor.getLong(3));
+ sb.append("','");
+ // TITLE
+ sb.append(cursor.getString(4));
+ sb.append("'");
+ out.write(sb.toString().getBytes());
+
+ cursor.moveToNext();
+ }
+ out.close();
+ /*
+ android.util.Log.d("s", "backing up data" +
+ getContentResolver().openFileDescriptor(Browser.BOOKMARKS_URI, "r").toString());
+ */
+ // NOTE: feed the flattened data through the crc engine on the fly
+ // to save re-reading it later just to checksum it
+
+ // Once the file is built, compare its metrics with the saved ones
+ if ((crc.getValue() != savedCrc) || (tmpfile.length() != savedFileSize)) {
+ // Different checksum or different size, so we need to back it up
+ copyFileToBackup(BOOKMARK_KEY, tmpfile, data);
+ }
+
+ // Last, record the metrics of the bookmark file that we just stored
+ writeBackupState(tmpfile.length(), crc.getValue(), newState);
+ } finally {
+ // Make sure to tidy up when we're done
+ tmpfile.delete();
+ }
+ }
+
+ /**
+ * Restore from backup -- reads in the flattened bookmark file as supplied from
+ * the backup service, parses that out, and rebuilds the bookmarks table in the
+ * browser database from it.
+ */
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode,
+ ParcelFileDescriptor newState) throws IOException {
+ long crc = -1;
+ File tmpfile = getFilesDir().createTempFile("rst", null);
+ try {
+ while (data.readNextHeader()) {
+ if (BOOKMARK_KEY.equals(data.getKey())) {
+ // Read the flattened bookmark data into a temp file
+ crc = copyBackupToFile(data, tmpfile, data.getDataSize());
+
+ // TODO: READ THE FLAT BOOKMARKS FILE 'tmpfile' AND REBUILD THE DB TABLE
+ }
+
+ // Last, write the state we just restored from so we can discern
+ // changes whenever we get invoked for backup in the future
+ writeBackupState(tmpfile.length(), crc, newState);
+ }
+ } finally {
+ // Whatever happens, delete the temp file
+ tmpfile.delete();
+ }
+ }
+
+ /*
+ * Utility functions
+ */
+
+ // Write the file to backup as a single record under the given key
+ private void copyFileToBackup(String key, File file, BackupDataOutput data)
+ throws IOException {
+ final int CHUNK = 8192;
+ byte[] buf = new byte[CHUNK];
+
+ int toCopy = (int) file.length();
+ data.writeEntityHeader(key, toCopy);
+
+ FileInputStream in = new FileInputStream(file);
+ int nRead;
+ while (toCopy > 0) {
+ nRead = in.read(buf, 0, CHUNK);
+ data.writeEntityData(buf, nRead);
+ toCopy -= nRead;
+ }
+ in.close();
+ }
+
+ // Read the given file from backup to a file, calculating a CRC32 along the way
+ private long copyBackupToFile(BackupDataInput data, File file, int toRead)
+ throws IOException {
+ final int CHUNK = 8192;
+ byte[] buf = new byte[CHUNK];
+ CRC32 crc = new CRC32();
+
+ while (toRead > 0) {
+ int numRead = data.readEntityData(buf, 0, CHUNK);
+ crc.update(buf, 0, numRead);
+ toRead -= numRead;
+ }
+
+ return crc.getValue();
+ }
+
+ // Write the given metrics to the new state file
+ private void writeBackupState(long fileSize, long crc, ParcelFileDescriptor stateFile)
+ throws IOException {
+ DataOutputStream out = new DataOutputStream(
+ new FileOutputStream(stateFile.getFileDescriptor()));
+ out.writeLong(fileSize);
+ out.writeLong(crc);
+ }
+}
diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java
index 27782e0..75be45b 100644
--- a/src/com/android/browser/BrowserBookmarksAdapter.java
+++ b/src/com/android/browser/BrowserBookmarksAdapter.java
@@ -30,29 +30,27 @@
import android.provider.Browser;
import android.provider.Browser.BookmarkColumns;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebIconDatabase;
import android.webkit.WebIconDatabase.IconListener;
import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
import java.io.ByteArrayOutputStream;
class BrowserBookmarksAdapter extends BaseAdapter {
- private final String LOGTAG = "Bookmarks";
-
private String mCurrentPage;
+ private String mCurrentTitle;
private Cursor mCursor;
private int mCount;
- private String mLastWhereClause;
- private String[] mLastSelectionArgs;
- private String mLastOrderBy;
private BrowserBookmarksPage mBookmarksPage;
private ContentResolver mContentResolver;
- private ChangeObserver mChangeObserver;
- private DataSetObserver mDataSetObserver;
private boolean mDataValid;
+ private boolean mGridMode;
// When true, this adapter is used to pick a bookmark to create a shortcut
private boolean mCreateShortcut;
@@ -70,36 +68,37 @@
/**
* Create a new BrowserBookmarksAdapter.
- * @param b BrowserBookmarksPage that instantiated this.
- * Necessary so it will adjust its focus
- * appropriately after a search.
- */
- public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage) {
- this(b, curPage, false);
- }
-
- /**
- * Create a new BrowserBookmarksAdapter.
* @param b BrowserBookmarksPage that instantiated this.
* Necessary so it will adjust its focus
* appropriately after a search.
*/
public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage,
- boolean createShortcut) {
+ String curTitle, boolean createShortcut) {
mDataValid = false;
mCreateShortcut = createShortcut;
mExtraOffset = createShortcut ? 0 : 1;
mBookmarksPage = b;
- mCurrentPage = b.getResources().getString(R.string.current_page) +
- curPage;
+ mCurrentPage = b.getResources().getString(R.string.current_page)
+ + curPage;
+ mCurrentTitle = curTitle;
mContentResolver = b.getContentResolver();
- mLastOrderBy = Browser.BookmarkColumns.CREATED + " DESC";
- mChangeObserver = new ChangeObserver();
- mDataSetObserver = new MyDataSetObserver();
+ mGridMode = false;
+
// FIXME: Should have a default sort order that the user selects.
- search(null);
+ String whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0";
+ String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
+ mCursor = b.managedQuery(Browser.BOOKMARKS_URI,
+ Browser.HISTORY_PROJECTION, whereClause, null, orderBy);
+ mCursor.registerContentObserver(new ChangeObserver());
+ mCursor.registerDataSetObserver(new MyDataSetObserver());
+
+ mDataValid = true;
+ notifyDataSetChanged();
+
+ mCount = mCursor.getCount() + mExtraOffset;
+
// FIXME: This requires another query of the database after the
- // initial search(null). Can we optimize this?
+ // managedQuery. Can we optimize this?
Browser.requestAllIcons(mContentResolver,
Browser.BookmarkColumns.FAVICON + " is NULL AND " +
Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver);
@@ -181,18 +180,7 @@
}
mCursor.moveToPosition(position- mExtraOffset);
String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- WebIconDatabase.getInstance().releaseIconForPageUrl(url);
- Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, mCursor
- .getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
- int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
- if (0 == numVisits) {
- mContentResolver.delete(uri, null, null);
- } else {
- // It is no longer a bookmark, but it is still a visited site.
- ContentValues values = new ContentValues();
- values.put(Browser.BookmarkColumns.BOOKMARK, 0);
- mContentResolver.update(uri, values, null, null);
- }
+ Bookmarks.removeFromBookmarks(null, mContentResolver, url);
refreshList();
}
@@ -253,52 +241,9 @@
* Refresh list to recognize a change in the database.
*/
public void refreshList() {
- // FIXME: consider using requery().
- // Need to do more work to get it to function though.
- searchInternal(mLastWhereClause, mLastSelectionArgs, mLastOrderBy);
- }
-
- /**
- * Search the database for bookmarks that match the input string.
- * @param like String to use to search the database. Strings with spaces
- * are treated as having multiple search terms using the
- * OR operator. Search both the title and url.
- */
- public void search(String like) {
- String whereClause = Browser.BookmarkColumns.BOOKMARK + " == 1";
- String[] selectionArgs = null;
- if (like != null) {
- String[] likes = like.split(" ");
- int count = 0;
- boolean firstTerm = true;
- StringBuilder andClause = new StringBuilder(256);
- for (int j = 0; j < likes.length; j++) {
- if (likes[j].length() > 0) {
- if (firstTerm) {
- firstTerm = false;
- } else {
- andClause.append(" OR ");
- }
- andClause.append(Browser.BookmarkColumns.TITLE
- + " LIKE ? OR " + Browser.BookmarkColumns.URL
- + " LIKE ? ");
- count += 2;
- }
- }
- if (count > 0) {
- selectionArgs = new String[count];
- count = 0;
- for (int j = 0; j < likes.length; j++) {
- if (likes[j].length() > 0) {
- like = "%" + likes[j] + "%";
- selectionArgs[count++] = like;
- selectionArgs[count++] = like;
- }
- }
- whereClause += " AND (" + andClause + ")";
- }
- }
- searchInternal(whereClause, selectionArgs, mLastOrderBy);
+ mCursor.requery();
+ mCount = mCursor.getCount() + mExtraOffset;
+ notifyDataSetChanged();
}
/**
@@ -348,46 +293,6 @@
}
/**
- * This sorts alphabetically, with non-capitalized titles before
- * capitalized.
- */
- public void sortAlphabetical() {
- searchInternal(mLastWhereClause, mLastSelectionArgs,
- Browser.BookmarkColumns.TITLE + " COLLATE UNICODE ASC");
- }
-
- /**
- * Internal function used in search, sort, and refreshList.
- */
- private void searchInternal(String whereClause, String[] selectionArgs,
- String orderBy) {
- if (mCursor != null) {
- mCursor.unregisterContentObserver(mChangeObserver);
- mCursor.unregisterDataSetObserver(mDataSetObserver);
- mBookmarksPage.stopManagingCursor(mCursor);
- mCursor.deactivate();
- }
-
- mLastWhereClause = whereClause;
- mLastSelectionArgs = selectionArgs;
- mLastOrderBy = orderBy;
- mCursor = mContentResolver.query(
- Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- whereClause,
- selectionArgs,
- orderBy);
- mCursor.registerContentObserver(mChangeObserver);
- mCursor.registerDataSetObserver(mDataSetObserver);
- mBookmarksPage.startManagingCursor(mCursor);
-
- mDataValid = true;
- notifyDataSetChanged();
-
- mCount = mCursor.getCount() + mExtraOffset;
- }
-
- /**
* How many items should be displayed in the list.
* @return Count of items.
*/
@@ -425,6 +330,20 @@
return position;
}
+ /* package */ void switchViewMode(boolean toGrid) {
+ mGridMode = toGrid;
+ }
+
+ /* package */ void populateBookmarkItem(BookmarkItem b, int position) {
+ mCursor.moveToPosition(position - mExtraOffset);
+ b.setUrl(mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX));
+ b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
+ byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ Bitmap bitmap = (null == data) ? null :
+ BitmapFactory.decodeByteArray(data, 0, data.length);
+ b.setFavicon(bitmap);
+ }
+
/**
* Get a View that displays the data at the specified position
* in the list.
@@ -440,6 +359,53 @@
throw new AssertionError(
"BrowserBookmarksAdapter tried to get a view out of range");
}
+ if (mGridMode) {
+ if (convertView == null || convertView instanceof AddNewBookmark
+ || convertView instanceof BookmarkItem) {
+ LayoutInflater factory = LayoutInflater.from(mBookmarksPage);
+ convertView
+ = factory.inflate(R.layout.bookmark_thumbnail, null);
+ }
+ View holder = convertView.findViewById(R.id.holder);
+ ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb);
+ ImageView fav = (ImageView) convertView.findViewById(R.id.fav);
+ TextView tv = (TextView) convertView.findViewById(R.id.label);
+
+ if (0 == position && !mCreateShortcut) {
+ // This is to create a bookmark for the current page.
+ holder.setVisibility(View.VISIBLE);
+ fav.setVisibility(View.GONE);
+ tv.setText(mCurrentTitle);
+ // FIXME: Want to show the screenshot of the current page
+ thumb.setImageResource(R.drawable.blank);
+ return convertView;
+ }
+ holder.setVisibility(View.GONE);
+ mCursor.moveToPosition(position - mExtraOffset);
+ tv.setText(mCursor.getString(
+ Browser.HISTORY_PROJECTION_TITLE_INDEX));
+ byte[] data = mCursor.getBlob(
+ Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX);
+ if (data == null) {
+ // Backup is to just show white
+ thumb.setImageResource(R.drawable.blank);
+ } else {
+ thumb.setImageBitmap(
+ BitmapFactory.decodeByteArray(data, 0, data.length));
+ }
+ // Now show the favicon
+ data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ if (data == null) {
+ fav.setVisibility(View.GONE);
+ } else {
+ fav.setVisibility(View.VISIBLE);
+ fav.setImageBitmap(
+ BitmapFactory.decodeByteArray(data, 0, data.length));
+ }
+
+ return convertView;
+
+ }
if (position == 0 && !mCreateShortcut) {
AddNewBookmark b;
if (convertView instanceof AddNewBookmark) {
@@ -450,7 +416,7 @@
b.setUrl(mCurrentPage);
return b;
}
- if (convertView == null || convertView instanceof AddNewBookmark) {
+ if (convertView == null || !(convertView instanceof BookmarkItem)) {
convertView = new BookmarkItem(mBookmarksPage);
}
bind((BookmarkItem)convertView, position);
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index dd34c14..428aa92 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -36,6 +36,7 @@
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -43,7 +44,9 @@
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
+import android.widget.GridView;
import android.widget.ListView;
+import android.widget.Toast;
/**
* View showing the user's bookmarks in the browser.
@@ -51,6 +54,9 @@
public class BrowserBookmarksPage extends Activity implements
View.OnCreateContextMenuListener {
+ private boolean mGridMode;
+ private GridView mGridPage;
+ private View mVerticalList;
private BrowserBookmarksAdapter mBookmarksAdapter;
private static final int BOOKMARKS_SAVE = 1;
private boolean mMaxTabsOpen;
@@ -107,7 +113,13 @@
break;
case R.id.copy_url_context_menu_id:
copy(getUrl(i.position));
-
+ break;
+ case R.id.homepage_context_menu_id:
+ BrowserSettings.getInstance().setHomePage(this,
+ getUrl(i.position));
+ Toast.makeText(this, R.string.homepage_set,
+ Toast.LENGTH_LONG).show();
+ break;
default:
return super.onContextItemSelected(item);
}
@@ -131,25 +143,29 @@
((ViewGroup) mAddHeader.getParent()).
removeView(mAddHeader);
}
- ((AddNewBookmark) i.targetView).copyTo(mAddHeader);
+ mAddHeader.setUrl(getIntent().getStringExtra("url"));
menu.setHeaderView(mAddHeader);
return;
}
menu.setGroupVisible(R.id.ADD_MENU, false);
- BookmarkItem b = (BookmarkItem) i.targetView;
+ if (mMaxTabsOpen) {
+ menu.findItem(R.id.new_window_context_menu_id).setVisible(
+ false);
+ }
if (mContextHeader == null) {
mContextHeader = new BookmarkItem(BrowserBookmarksPage.this);
} else if (mContextHeader.getParent() != null) {
((ViewGroup) mContextHeader.getParent()).
removeView(mContextHeader);
}
- b.copyTo(mContextHeader);
- menu.setHeaderView(mContextHeader);
-
- if (mMaxTabsOpen) {
- menu.findItem(R.id.new_window_context_menu_id).setVisible(
- false);
+ if (mGridMode) {
+ mBookmarksAdapter.populateBookmarkItem(mContextHeader,
+ i.position);
+ } else {
+ BookmarkItem b = (BookmarkItem) i.targetView;
+ b.copyTo(mContextHeader);
}
+ menu.setHeaderView(mContextHeader);
}
/**
@@ -159,25 +175,61 @@
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- setContentView(R.layout.browser_bookmarks_page);
- setTitle(R.string.browser_bookmarks_page_bookmarks_text);
-
if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
mCreateShortcut = true;
}
-
- mBookmarksAdapter = new BrowserBookmarksAdapter(this,
- getIntent().getStringExtra("url"), mCreateShortcut);
mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
- ListView listView = (ListView) findViewById(R.id.list);
- listView.setAdapter(mBookmarksAdapter);
- listView.setDrawSelectorOnTop(false);
- listView.setVerticalScrollBarEnabled(true);
- listView.setOnItemClickListener(mListener);
+ setTitle(R.string.browser_bookmarks_page_bookmarks_text);
+ mBookmarksAdapter = new BrowserBookmarksAdapter(this,
+ getIntent().getStringExtra("url"),
+ getIntent().getStringExtra("title"), mCreateShortcut);
+ switchViewMode(true);
+ }
- if (!mCreateShortcut) {
- listView.setOnCreateContextMenuListener(this);
+ /**
+ * Set the ContentView to be either the grid of thumbnails or the vertical
+ * list. Pass true to set it to the grid.
+ */
+ private void switchViewMode(boolean gridMode) {
+ mGridMode = gridMode;
+ mBookmarksAdapter.switchViewMode(gridMode);
+ if (mGridMode) {
+ if (mGridPage == null) {
+ mGridPage = new GridView(this);
+ mGridPage.setAdapter(mBookmarksAdapter);
+ mGridPage.setOnItemClickListener(mListener);
+ mGridPage.setNumColumns(GridView.AUTO_FIT);
+ // Keep this in sync with bookmark_thumb and
+ // BrowserActivity.updateScreenshot
+ mGridPage.setColumnWidth(100);
+ mGridPage.setFocusable(true);
+ mGridPage.setFocusableInTouchMode(true);
+ mGridPage.setSelector(android.R.drawable.gallery_thumb);
+ mGridPage.setVerticalSpacing(10);
+ if (!mCreateShortcut) {
+ mGridPage.setOnCreateContextMenuListener(this);
+ }
+ }
+ setContentView(mGridPage);
+ } else {
+ if (null == mVerticalList) {
+ LayoutInflater factory = LayoutInflater.from(this);
+ mVerticalList = factory.inflate(R.layout.browser_bookmarks_page,
+ null);
+
+ ListView listView
+ = (ListView) mVerticalList.findViewById(R.id.list);
+ listView.setAdapter(mBookmarksAdapter);
+ listView.setDrawSelectorOnTop(false);
+ listView.setVerticalScrollBarEnabled(true);
+ listView.setOnItemClickListener(mListener);
+
+ if (!mCreateShortcut) {
+ listView.setOnCreateContextMenuListener(this);
+ }
+ }
+ setContentView(mVerticalList);
}
}
@@ -196,7 +248,7 @@
// It is possible that the view has been canceled when we get to
// this point as back has a higher priority
if (mCanceled) {
- android.util.Log.e("browser", "item clicked when dismising");
+ android.util.Log.e(LOGTAG, "item clicked when dismissing");
return;
}
if (!mCreateShortcut) {
@@ -294,12 +346,16 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.new_context_menu_id:
- saveCurrentPage();
- break;
-
- default:
- return super.onOptionsItemSelected(item);
+ case R.id.new_context_menu_id:
+ saveCurrentPage();
+ break;
+
+ case R.id.switch_mode_menu_id:
+ switchViewMode(!mGridMode);
+ break;
+
+ default:
+ return super.onOptionsItemSelected(item);
}
return true;
}
@@ -370,7 +426,7 @@
/**
* Refresh the shown list after the database has changed.
*/
- public void refreshList() {
+ private void refreshList() {
mBookmarksAdapter.refreshList();
}
diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java
index 38b83fe..16cb982 100644
--- a/src/com/android/browser/BrowserDownloadAdapter.java
+++ b/src/com/android/browser/BrowserDownloadAdapter.java
@@ -60,14 +60,14 @@
public BrowserDownloadAdapter(Context context, int layout, Cursor c) {
super(context, layout, c);
mFilenameColumnId = c.getColumnIndexOrThrow(Downloads._DATA);
- mTitleColumnId = c.getColumnIndexOrThrow(Downloads.TITLE);
- mDescColumnId = c.getColumnIndexOrThrow(Downloads.DESCRIPTION);
- mStatusColumnId = c.getColumnIndexOrThrow(Downloads.STATUS);
- mTotalBytesColumnId = c.getColumnIndexOrThrow(Downloads.TOTAL_BYTES);
+ mTitleColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_TITLE);
+ mDescColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_DESCRIPTION);
+ mStatusColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
+ mTotalBytesColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES);
mCurrentBytesColumnId =
- c.getColumnIndexOrThrow(Downloads.CURRENT_BYTES);
- mMimetypeColumnId = c.getColumnIndexOrThrow(Downloads.MIMETYPE);
- mDateColumnId = c.getColumnIndexOrThrow(Downloads.LAST_MODIFICATION);
+ c.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES);
+ mMimetypeColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE);
+ mDateColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION);
}
@Override
@@ -106,7 +106,7 @@
// We have a filename, so we can build a title from that
title = new File(fullFilename).getName();
ContentValues values = new ContentValues();
- values.put(Downloads.TITLE, title);
+ values.put(Downloads.COLUMN_TITLE, title);
// assume "_id" is the first column for the cursor
context.getContentResolver().update(
ContentUris.withAppendedId(Downloads.CONTENT_URI,
diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java
index 4397337..22e0e65 100644
--- a/src/com/android/browser/BrowserDownloadPage.java
+++ b/src/com/android/browser/BrowserDownloadPage.java
@@ -66,34 +66,30 @@
setTitle(getText(R.string.download_title));
mListView = (ListView) findViewById(R.id.list);
- LayoutInflater factory = LayoutInflater.from(this);
- View v = factory.inflate(R.layout.no_downloads, null);
- addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
- LayoutParams.FILL_PARENT));
- mListView.setEmptyView(v);
+ mListView.setEmptyView(findViewById(R.id.empty));
mDownloadCursor = managedQuery(Downloads.CONTENT_URI,
- new String [] {"_id", Downloads.TITLE, Downloads.STATUS,
- Downloads.TOTAL_BYTES, Downloads.CURRENT_BYTES,
- Downloads._DATA, Downloads.DESCRIPTION,
- Downloads.MIMETYPE, Downloads.LAST_MODIFICATION,
- Downloads.VISIBILITY},
+ new String [] {"_id", Downloads.COLUMN_TITLE, Downloads.COLUMN_STATUS,
+ Downloads.COLUMN_TOTAL_BYTES, Downloads.COLUMN_CURRENT_BYTES,
+ Downloads._DATA, Downloads.COLUMN_DESCRIPTION,
+ Downloads.COLUMN_MIME_TYPE, Downloads.COLUMN_LAST_MODIFICATION,
+ Downloads.COLUMN_VISIBILITY},
null, null);
// only attach everything to the listbox if we can access
// the download database. Otherwise, just show it empty
if (mDownloadCursor != null) {
mStatusColumnId =
- mDownloadCursor.getColumnIndexOrThrow(Downloads.STATUS);
+ mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
mIdColumnId =
mDownloadCursor.getColumnIndexOrThrow(Downloads._ID);
mTitleColumnId =
- mDownloadCursor.getColumnIndexOrThrow(Downloads.TITLE);
+ mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_TITLE);
// Create a list "controller" for the data
mDownloadAdapter = new BrowserDownloadAdapter(this,
R.layout.browser_download_item, mDownloadCursor);
-
+
mListView.setAdapter(mDownloadAdapter);
mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
mListView.setOnCreateContextMenuListener(this);
@@ -403,7 +399,7 @@
mDownloadCursor.getColumnIndexOrThrow(Downloads._DATA);
String filename = mDownloadCursor.getString(filenameColumnId);
int mimetypeColumnId =
- mDownloadCursor.getColumnIndexOrThrow(Downloads.MIMETYPE);
+ mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE);
String mimetype = mDownloadCursor.getString(mimetypeColumnId);
Uri path = Uri.parse(filename);
// If there is no scheme, then it must be a file
@@ -453,13 +449,13 @@
private void hideCompletedDownload() {
int status = mDownloadCursor.getInt(mStatusColumnId);
- int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.VISIBILITY);
+ int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY);
int visibility = mDownloadCursor.getInt(visibilityColumn);
if (Downloads.isStatusCompleted(status) &&
visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
ContentValues values = new ContentValues();
- values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE);
+ values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE);
getContentResolver().update(
ContentUris.withAppendedId(Downloads.CONTENT_URI,
mDownloadCursor.getLong(mIdColumnId)), values, null, null);
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java
index 42ca848..d5e7049 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -41,6 +41,7 @@
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.ViewStub;
import android.webkit.DateSorter;
import android.webkit.WebIconDatabase.IconListener;
import android.widget.AdapterView;
@@ -48,6 +49,7 @@
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.TextView;
+import android.widget.Toast;
import java.util.List;
import java.util.Vector;
@@ -110,8 +112,7 @@
setListAdapter(mAdapter);
final ExpandableListView list = getExpandableListView();
list.setOnCreateContextMenuListener(this);
- LayoutInflater factory = LayoutInflater.from(this);
- View v = factory.inflate(R.layout.empty_history, null);
+ View v = new ViewStub(this, R.layout.empty_history);
addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
list.setEmptyView(v);
@@ -222,6 +223,11 @@
Browser.deleteFromHistory(getContentResolver(), url);
mAdapter.refreshData();
return true;
+ case R.id.homepage_context_menu_id:
+ BrowserSettings.getInstance().setHomePage(this, url);
+ Toast.makeText(this, R.string.homepage_set,
+ Toast.LENGTH_LONG).show();
+ return true;
default:
break;
}
@@ -267,18 +273,25 @@
// Array for each of our bins. Each entry represents how many items are
// in that bin.
- int mItemMap[];
+ private int mItemMap[];
// This is our GroupCount. We will have at most DateSorter.DAY_COUNT
// bins, less if the user has no items in one or more bins.
- int mNumberOfBins;
- Vector<DataSetObserver> mObservers;
- Cursor mCursor;
+ private int mNumberOfBins;
+ private Vector<DataSetObserver> mObservers;
+ private Cursor mCursor;
HistoryAdapter() {
mObservers = new Vector<DataSetObserver>();
- String whereClause = Browser.BookmarkColumns.VISITS + " > 0 ";
- String orderBy = Browser.BookmarkColumns.DATE + " DESC";
+ final String whereClause = Browser.BookmarkColumns.VISITS + " > 0"
+ // In AddBookmarkPage, where we save new bookmarks, we add
+ // three visits to newly created bookmarks, so that
+ // bookmarks that have not been visited will show up in the
+ // most visited, and higher in the goto search box.
+ // However, this puts the site in the history, unless we
+ // ignore sites with a DATE of 0, which the next line does.
+ + " AND " + Browser.BookmarkColumns.DATE + " > 0";
+ final String orderBy = Browser.BookmarkColumns.DATE + " DESC";
mCursor = managedQuery(
Browser.BOOKMARKS_URI,
@@ -290,6 +303,9 @@
}
void refreshData() {
+ if (mCursor.isClosed()) {
+ return;
+ }
mCursor.requery();
buildMap();
for (DataSetObserver o : mObservers) {
diff --git a/src/com/android/browser/BrowserHomepagePreference.java b/src/com/android/browser/BrowserHomepagePreference.java
index d4708c3..7324f24 100644
--- a/src/com/android/browser/BrowserHomepagePreference.java
+++ b/src/com/android/browser/BrowserHomepagePreference.java
@@ -50,8 +50,8 @@
if (dialog != null) {
String url = s.toString();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(
- url.length() == 0 || url.equals("about:blank") ||
- Regex.WEB_URL_PATTERN.matcher(url).matches());
+ url.length() == 0 ||
+ BrowserActivity.ACCEPTED_URI_SCHEMA.matcher(url).matches());
}
}
diff --git a/src/com/android/browser/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java
index 3b747d1..2524eb8 100644
--- a/src/com/android/browser/BrowserPreferencesPage.java
+++ b/src/com/android/browser/BrowserPreferencesPage.java
@@ -17,12 +17,19 @@
package com.android.browser;
import java.util.List;
+import java.util.Vector;
+import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.preference.EditTextPreference;
+import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.webkit.Plugin;
+import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.Plugin;
@@ -30,6 +37,8 @@
implements Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener {
+ String TAG = "BrowserPreferencesPage";
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -66,6 +75,28 @@
e = findPreference(BrowserSettings.PREF_GEARS_SETTINGS);
e.setOnPreferenceClickListener(this);
+
+ PreferenceScreen manageDatabases = (PreferenceScreen)
+ findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
+ Intent intent = new Intent(this, WebsiteSettingsActivity.class);
+ manageDatabases.setIntent(intent);
+ }
+
+ /*
+ * We need to set the manageDatabases PreferenceScreen state
+ * in the onResume(), as the number of origins with databases
+ * could have changed after calling the WebsiteSettingsActivity.
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+ PreferenceScreen manageDatabases = (PreferenceScreen)
+ findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
+ manageDatabases.setEnabled(false);
+ Vector origins = WebStorage.getInstance().getOrigins();
+ if ((origins != null) && (origins.size() > 0)) {
+ manageDatabases.setEnabled(true);
+ }
}
@Override
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index a3ccf04..29c65e8 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -96,6 +96,8 @@
private static final int MAX_SUGGESTION_SHORT_ENTRIES = 3;
private static final int MAX_SUGGESTION_LONG_ENTRIES = 6;
+ private static final String MAX_SUGGESTION_LONG_ENTRIES_STRING =
+ Integer.valueOf(MAX_SUGGESTION_LONG_ENTRIES).toString();
// make sure that these match the index of TABLE_NAMES
private static final int URI_MATCH_BOOKMARKS = 0;
@@ -143,7 +145,8 @@
// 15 -> 17 Set it up for the SearchManager
// 17 -> 18 Added favicon in bookmarks table for Home shortcuts
// 18 -> 19 Remove labels table
- private static final int DATABASE_VERSION = 19;
+ // 19 -> 20 Added thumbnail
+ private static final int DATABASE_VERSION = 20;
// Regular expression which matches http://, followed by some stuff, followed by
// optionally a trailing slash, all matched as separate groups.
@@ -215,7 +218,8 @@
"created LONG," +
"description TEXT," +
"bookmark INTEGER," +
- "favicon BLOB DEFAULT NULL" +
+ "favicon BLOB DEFAULT NULL," +
+ "thumbnail BLOB DEFAULT NULL" +
");");
final CharSequence[] bookmarks = mContext.getResources()
@@ -242,9 +246,12 @@
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
- + newVersion + ", which will destroy all old data");
+ + newVersion);
if (oldVersion == 18) {
db.execSQL("DROP TABLE IF EXISTS labels");
+ }
+ if (oldVersion <= 19) {
+ db.execSQL("ALTER TABLE bookmarks ADD COLUMN thumbnail BLOB DEFAULT NULL;");
} else {
db.execSQL("DROP TABLE IF EXISTS bookmarks");
db.execSQL("DROP TABLE IF EXISTS searches");
@@ -471,22 +478,22 @@
case SUGGEST_COLUMN_ICON_1_ID:
if (mHistoryCount > mPos) {
if (mHistoryCursor.getInt(3) == 1) {
- return new Integer(
+ return Integer.valueOf(
R.drawable.ic_search_category_bookmark)
.toString();
} else {
- return new Integer(
+ return Integer.valueOf(
R.drawable.ic_search_category_history)
.toString();
}
} else {
- return new Integer(
+ return Integer.valueOf(
R.drawable.ic_search_category_suggest)
.toString();
}
case SUGGEST_COLUMN_ICON_2_ID:
- return new String("0");
+ return "0";
case SUGGEST_COLUMN_QUERY_ID:
if (mHistoryCount > mPos) {
@@ -637,7 +644,8 @@
myArgs = null;
} else {
String like = selectionArgs[0] + "%";
- if (selectionArgs[0].startsWith("http")) {
+ if (selectionArgs[0].startsWith("http")
+ || selectionArgs[0].startsWith("file")) {
myArgs = new String[1];
myArgs[0] = like;
suggestSelection = selection;
@@ -655,8 +663,7 @@
Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
- ORDER_BY,
- (new Integer(MAX_SUGGESTION_LONG_ENTRIES)).toString());
+ ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING);
if (match == URI_MATCH_BOOKMARKS_SUGGEST
|| Regex.WEB_URL_PATTERN.matcher(selectionArgs[0]).matches()) {
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index a5e23c9..479c0a3 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -20,13 +20,17 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
import android.webkit.CookieManager;
import android.webkit.WebView;
import android.webkit.WebViewDatabase;
import android.webkit.WebIconDatabase;
import android.webkit.WebSettings;
+import android.webkit.WebStorage;
import android.preference.PreferenceManager;
import android.provider.Browser;
@@ -48,7 +52,7 @@
*/
class BrowserSettings extends Observable {
- // Public variables for settings
+ // Private variables for settings
// NOTE: these defaults need to be kept in sync with the XML
// until the performance of PreferenceManager.setDefaultValues()
// is improved.
@@ -65,7 +69,18 @@
private String homeUrl = "";
private boolean loginInitialized = false;
private boolean autoFitPage = true;
+ private boolean landscapeOnly = false;
private boolean showDebugSettings = false;
+ private String databasePath; // default value set in loadFromDb()
+ private boolean databaseEnabled = true;
+ private long webStorageDefaultQuota = 5 * 1024 * 1024;
+ // The Browser always enables Application Caches.
+ private boolean appCacheEnabled = true;
+ private String appCachePath; // default value set in loadFromDb().
+ private boolean domStorageEnabled = true;
+ private String jsFlags = "";
+
+ private final static String TAG = "BrowserSettings";
// Development settings
public WebSettings.LayoutAlgorithm layoutAlgorithm =
@@ -75,6 +90,11 @@
private boolean tracing = false;
private boolean lightTouch = false;
private boolean navDump = false;
+
+ // By default the error console is shown once the user navigates to about:debug.
+ // The setting can be then toggled from the settings menu.
+ private boolean showConsole = true;
+
// Browser only settings
private boolean doFlick = false;
@@ -97,22 +117,25 @@
"privacy_clear_form_data";
public final static String PREF_CLEAR_PASSWORDS =
"privacy_clear_passwords";
+ public final static String PREF_DEFAULT_QUOTA =
+ "webstorage_default_quota";
public final static String PREF_EXTRAS_RESET_DEFAULTS =
"reset_default_preferences";
public final static String PREF_DEBUG_SETTINGS = "debug_menu";
public final static String PREF_GEARS_SETTINGS = "gears_settings";
+ public final static String PREF_WEBSITE_SETTINGS = "website_settings";
public final static String PREF_TEXT_SIZE = "text_size";
public final static String PREF_DEFAULT_ZOOM = "default_zoom";
public final static String PREF_DEFAULT_TEXT_ENCODING =
"default_text_encoding";
private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (Macintosh; " +
- "U; Intel Mac OS X 10_5_5; en-us) AppleWebKit/525.18 (KHTML, " +
- "like Gecko) Version/3.1.2 Safari/525.20.1";
+ "U; Intel Mac OS X 10_5_7; en-us) AppleWebKit/530.17 (KHTML, " +
+ "like Gecko) Version/4.0 Safari/530.17";
private static final String IPHONE_USERAGENT = "Mozilla/5.0 (iPhone; U; " +
- "CPU iPhone OS 2_2 like Mac OS X; en-us) AppleWebKit/525.18.1 " +
- "(KHTML, like Gecko) Version/3.1.1 Mobile/5G77 Safari/525.20";
+ "CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 " +
+ "(KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16";
// Value to truncate strings when adding them to a TextView within
// a ListView
@@ -157,7 +180,6 @@
s.setLoadsImagesAutomatically(b.loadsImagesAutomatically);
s.setJavaScriptEnabled(b.javaScriptEnabled);
s.setPluginsEnabled(b.pluginsEnabled);
- s.setPluginsPath(b.pluginsPath);
s.setJavaScriptCanOpenWindowsAutomatically(
b.javaScriptCanOpenWindowsAutomatically);
s.setDefaultTextEncodingName(b.defaultTextEncodingName);
@@ -178,6 +200,19 @@
s.setSupportMultipleWindows(true);
// Turn off file access
s.setAllowFileAccess(false);
+
+ s.setDatabasePath(b.databasePath);
+ s.setDatabaseEnabled(b.databaseEnabled);
+ s.setDomStorageEnabled(b.domStorageEnabled);
+ s.setWebStorageDefaultQuota(b.webStorageDefaultQuota);
+
+ // Turn on Application Caches.
+ s.setAppCachePath(b.appCachePath);
+ s.setAppCacheEnabled(b.appCacheEnabled);
+
+ // Enable/Disable the error console.
+ b.mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+ b.showDebugSettings && b.showConsole);
}
}
@@ -197,6 +232,10 @@
// Set the default value for the plugins path to the application's
// local directory.
pluginsPath = ctx.getDir("plugins", 0).getPath();
+ // Set the default value for the Application Caches path.
+ appCachePath = ctx.getDir("appcache", 0).getPath();
+ // Set the default value for the Database path.
+ databasePath = ctx.getDir("databases", 0).getPath();
homeUrl = getFactoryResetHomeUrl(ctx);
@@ -219,6 +258,15 @@
pluginsEnabled = p.getBoolean("enable_plugins",
pluginsEnabled);
pluginsPath = p.getString("plugins_path", pluginsPath);
+ databasePath = p.getString("database_path", databasePath);
+ databaseEnabled = p.getBoolean("enable_database", databaseEnabled);
+ webStorageDefaultQuota = Long.parseLong(p.getString(PREF_DEFAULT_QUOTA,
+ String.valueOf(webStorageDefaultQuota)));
+ appCacheEnabled = p.getBoolean("enable_appcache",
+ appCacheEnabled);
+ domStorageEnabled = p.getBoolean("enable_domstorage",
+ domStorageEnabled);
+ appCachePath = p.getString("appcache_path", appCachePath);
javaScriptCanOpenWindowsAutomatically = !p.getBoolean(
"block_popup_windows",
!javaScriptCanOpenWindowsAutomatically);
@@ -238,6 +286,14 @@
zoomDensity = WebSettings.ZoomDensity.valueOf(
p.getString(PREF_DEFAULT_ZOOM, zoomDensity.name()));
autoFitPage = p.getBoolean("autofit_pages", autoFitPage);
+ boolean landscapeOnlyTemp =
+ p.getBoolean("landscape_only", landscapeOnly);
+ if (landscapeOnlyTemp != landscapeOnly) {
+ landscapeOnly = landscapeOnlyTemp;
+ mTabControl.getBrowserActivity().setRequestedOrientation(
+ landscapeOnly ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
useWideViewPort = true; // use wide view port for either setting
if (autoFitPage) {
layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS;
@@ -274,6 +330,19 @@
doFlick = p.getBoolean("enable_flick", doFlick);
userAgent = Integer.parseInt(p.getString("user_agent", "0"));
}
+ // JS flags is loaded from DB even if showDebugSettings is false,
+ // so that it can be set once and be effective all the time.
+ jsFlags = p.getString("js_engine_flags", "");
+
+ // Read the setting for showing/hiding the JS Console always so that should the
+ // user enable debug settings, we already know if we should show the console.
+ // The user will never see the console unless they navigate to about:debug,
+ // regardless of the setting we read here. This setting is only used after debug
+ // is enabled.
+ showConsole = p.getBoolean("javascript_console", showConsole);
+ mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+ showDebugSettings && showConsole);
+
update();
}
@@ -285,6 +354,10 @@
return homeUrl;
}
+ public String getJsFlags() {
+ return jsFlags;
+ }
+
public void setHomePage(Context context, String url) {
Editor ed = PreferenceManager.
getDefaultSharedPreferences(context).edit();
@@ -436,14 +509,24 @@
db.clearHttpAuthUsernamePassword();
}
- /*package*/ void resetDefaultPreferences(Context context) {
+ /*package*/ void clearDatabases(Context context) {
+ WebStorage.getInstance().deleteAllDatabases();
+ // Remove all listed databases from the preferences
+ PreferenceActivity activity = (PreferenceActivity) context;
+ PreferenceScreen screen = (PreferenceScreen)
+ activity.findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
+ screen.removeAll();
+ screen.setEnabled(false);
+ }
+
+ /*package*/ void resetDefaultPreferences(Context ctx) {
SharedPreferences p =
- PreferenceManager.getDefaultSharedPreferences(context);
+ PreferenceManager.getDefaultSharedPreferences(ctx);
p.edit().clear().commit();
- PreferenceManager.setDefaultValues(context, R.xml.browser_preferences,
+ PreferenceManager.setDefaultValues(ctx, R.xml.browser_preferences,
true);
// reset homeUrl
- setHomePage(context, getFactoryResetHomeUrl(context));
+ setHomePage(ctx, getFactoryResetHomeUrl(ctx));
}
private String getFactoryResetHomeUrl(Context context) {
diff --git a/src/com/android/browser/BrowserYesNoPreference.java b/src/com/android/browser/BrowserYesNoPreference.java
index 65cde71..ae93882 100644
--- a/src/com/android/browser/BrowserYesNoPreference.java
+++ b/src/com/android/browser/BrowserYesNoPreference.java
@@ -38,6 +38,7 @@
Context context = getContext();
if (BrowserSettings.PREF_CLEAR_CACHE.equals(getKey())) {
BrowserSettings.getInstance().clearCache(context);
+ BrowserSettings.getInstance().clearDatabases(context);
} else if (BrowserSettings.PREF_CLEAR_COOKIES.equals(getKey())) {
BrowserSettings.getInstance().clearCookies(context);
} else if (BrowserSettings.PREF_CLEAR_HISTORY.equals(getKey())) {
diff --git a/src/com/android/browser/ErrorConsoleView.java b/src/com/android/browser/ErrorConsoleView.java
new file mode 100644
index 0000000..56f663b
--- /dev/null
+++ b/src/com/android/browser/ErrorConsoleView.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2009 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.database.DataSetObserver;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.webkit.WebView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TwoLineListItem;
+
+import java.util.Vector;
+
+/* package */ class ErrorConsoleView extends LinearLayout {
+
+ /**
+ * Define some constants to describe the visibility of the error console.
+ */
+ public static final int SHOW_MINIMIZED = 0;
+ public static final int SHOW_MAXIMIZED = 1;
+ public static final int SHOW_NONE = 2;
+
+ private TextView mConsoleHeader;
+ private ErrorConsoleListView mErrorList;
+ private LinearLayout mEvalJsViewGroup;
+ private EditText mEvalEditText;
+ private Button mEvalButton;
+ private WebView mWebView;
+ private int mCurrentShowState = SHOW_NONE;
+
+ private boolean mSetupComplete = false;
+
+ // Before we've been asked to display the console, cache any messages that should
+ // be added to the console. Then when we do display the console, add them to the view
+ // then.
+ private Vector<ErrorConsoleMessage> mErrorMessageCache;
+
+ public ErrorConsoleView(Context context) {
+ super(context);
+ }
+
+ public ErrorConsoleView(Context context, AttributeSet attributes) {
+ super(context, attributes);
+ }
+
+ private void commonSetupIfNeeded() {
+ if (mSetupComplete) {
+ return;
+ }
+
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.error_console, this);
+
+ // Get references to each ui element.
+ mConsoleHeader = (TextView) findViewById(R.id.error_console_header_id);
+ mErrorList = (ErrorConsoleListView) findViewById(R.id.error_console_list_id);
+ mEvalJsViewGroup = (LinearLayout) findViewById(R.id.error_console_eval_view_group_id);
+ mEvalEditText = (EditText) findViewById(R.id.error_console_eval_text_id);
+ mEvalButton = (Button) findViewById(R.id.error_console_eval_button_id);
+
+ mEvalButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ // Send the javascript to be evaluated to webkit as a javascript: url
+ // TODO: Can we expose access to webkit's JS interpreter here and evaluate it that
+ // way? Note that this is called on the UI thread so we will need to post a message
+ // to the WebCore thread to implement this.
+ if (mWebView != null) {
+ mWebView.loadUrl("javascript:" + mEvalEditText.getText());
+ }
+
+ mEvalEditText.setText("");
+ }
+ });
+
+ // Make clicking on the console title bar min/maximse it.
+ mConsoleHeader.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mCurrentShowState == SHOW_MINIMIZED) {
+ showConsole(SHOW_MAXIMIZED);
+ } else {
+ showConsole(SHOW_MINIMIZED);
+ }
+ }
+ });
+
+ // Add any cached messages to the list now that we've assembled the view.
+ if (mErrorMessageCache != null) {
+ for (ErrorConsoleMessage msg : mErrorMessageCache) {
+ mErrorList.addErrorMessage(msg.getMessage(), msg.getSourceID(), msg.getLineNumber());
+ }
+ mErrorMessageCache.clear();
+ }
+
+ mSetupComplete = true;
+ }
+
+ /**
+ * Adds a message to the set of messages the console uses.
+ */
+ public void addErrorMessage(String msg, String sourceId, int lineNumber) {
+ if (mSetupComplete) {
+ mErrorList.addErrorMessage(msg, sourceId, lineNumber);
+ } else {
+ if (mErrorMessageCache == null) {
+ mErrorMessageCache = new Vector<ErrorConsoleMessage>();
+ }
+ mErrorMessageCache.add(new ErrorConsoleMessage(msg, sourceId, lineNumber));
+ }
+ }
+
+ /**
+ * Removes all error messages from the console.
+ */
+ public void clearErrorMessages() {
+ if (mSetupComplete) {
+ mErrorList.clearErrorMessages();
+ } else if (mErrorMessageCache != null) {
+ mErrorMessageCache.clear();
+ }
+ }
+
+ /**
+ * Returns the current number of errors displayed in the console.
+ */
+ public int numberOfErrors() {
+ if (mSetupComplete) {
+ return mErrorList.getCount();
+ } else {
+ return (mErrorMessageCache == null) ? 0 : mErrorMessageCache.size();
+ }
+ }
+
+ /**
+ * Sets the webview that this console is associated with. Currently this is used so
+ * we can call into webkit to evaluate JS expressions in the console.
+ */
+ public void setWebView(WebView webview) {
+ mWebView = webview;
+ }
+
+ /**
+ * Sets the visibility state of the console.
+ */
+ public void showConsole(int show_state) {
+ commonSetupIfNeeded();
+ switch (show_state) {
+ case SHOW_MINIMIZED:
+ mConsoleHeader.setVisibility(View.VISIBLE);
+ mConsoleHeader.setText(R.string.error_console_header_text_minimized);
+ mErrorList.setVisibility(View.GONE);
+ mEvalJsViewGroup.setVisibility(View.GONE);
+ break;
+
+ case SHOW_MAXIMIZED:
+ mConsoleHeader.setVisibility(View.VISIBLE);
+ mConsoleHeader.setText(R.string.error_console_header_text_maximized);
+ mErrorList.setVisibility(View.VISIBLE);
+ mEvalJsViewGroup.setVisibility(View.VISIBLE);
+ break;
+
+ case SHOW_NONE:
+ mConsoleHeader.setVisibility(View.GONE);
+ mErrorList.setVisibility(View.GONE);
+ mEvalJsViewGroup.setVisibility(View.GONE);
+ break;
+ }
+ mCurrentShowState = show_state;
+ }
+
+ /**
+ * Returns the current visibility state of the console.
+ */
+ public int getShowState() {
+ if (mSetupComplete) {
+ return mCurrentShowState;
+ } else {
+ return SHOW_NONE;
+ }
+ }
+
+ /**
+ * This class extends ListView to implement the View that will actually display the set of
+ * errors encountered on the current page.
+ */
+ private static class ErrorConsoleListView extends ListView {
+ // An adapter for this View that contains a list of error messages.
+ private ErrorConsoleMessageList mConsoleMessages;
+
+ public ErrorConsoleListView(Context context, AttributeSet attributes) {
+ super(context, attributes);
+ mConsoleMessages = new ErrorConsoleMessageList(context);
+ setAdapter(mConsoleMessages);
+ }
+
+ public void addErrorMessage(String msg, String sourceId, int lineNumber) {
+ mConsoleMessages.add(msg, sourceId, lineNumber);
+ setSelection(mConsoleMessages.getCount());
+ }
+
+ public void clearErrorMessages() {
+ mConsoleMessages.clear();
+ }
+
+ /**
+ * This class is an adapter for ErrorConsoleListView that contains the error console
+ * message data.
+ */
+ private class ErrorConsoleMessageList extends android.widget.BaseAdapter
+ implements android.widget.ListAdapter {
+
+ private Vector<ErrorConsoleMessage> mMessages;
+ private LayoutInflater mInflater;
+
+ public ErrorConsoleMessageList(Context context) {
+ mMessages = new Vector<ErrorConsoleMessage>();
+ mInflater = (LayoutInflater)context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ /**
+ * Add a new message to the list and update the View.
+ */
+ public void add(String msg, String sourceID, int lineNumber) {
+ mMessages.add(new ErrorConsoleMessage(msg, sourceID, lineNumber));
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Remove all messages from the list and update the view.
+ */
+ public void clear() {
+ mMessages.clear();
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return false;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public Object getItem(int position) {
+ return mMessages.get(position);
+ }
+
+ public int getCount() {
+ return mMessages.size();
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ /**
+ * Constructs a TwoLineListItem for the error at position.
+ */
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ ErrorConsoleMessage error = mMessages.get(position);
+
+ if (error == null) {
+ return null;
+ }
+
+ if (convertView == null) {
+ view = mInflater.inflate(android.R.layout.two_line_list_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ TextView headline = (TextView) view.findViewById(android.R.id.text1);
+ TextView subText = (TextView) view.findViewById(android.R.id.text2);
+ headline.setText(error.getSourceID() + ":" + error.getLineNumber());
+ subText.setText(error.getMessage());
+ return view;
+ }
+
+ }
+ }
+
+ /**
+ * This class holds the data for a single error message in the console.
+ */
+ private static class ErrorConsoleMessage {
+ private String mMessage;
+ private String mSourceID;
+ private int mLineNumber;
+
+ public ErrorConsoleMessage(String msg, String sourceID, int lineNumber) {
+ mMessage = msg;
+ mSourceID = sourceID;
+ mLineNumber = lineNumber;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+
+ public String getSourceID() {
+ return mSourceID;
+ }
+
+ public int getLineNumber() {
+ return mLineNumber;
+ }
+ }
+}
diff --git a/src/com/android/browser/FetchUrlMimeType.java b/src/com/android/browser/FetchUrlMimeType.java
index 8578643..c585dbb 100644
--- a/src/com/android/browser/FetchUrlMimeType.java
+++ b/src/com/android/browser/FetchUrlMimeType.java
@@ -58,7 +58,7 @@
mValues = values[0];
// Check to make sure we have a URI to download
- String uri = mValues.getAsString(Downloads.URI);
+ String uri = mValues.getAsString(Downloads.COLUMN_URI);
if (uri == null || uri.length() == 0) {
return null;
}
@@ -66,15 +66,15 @@
// User agent is likely to be null, though the AndroidHttpClient
// seems ok with that.
AndroidHttpClient client = AndroidHttpClient.newInstance(
- mValues.getAsString(Downloads.USER_AGENT));
+ mValues.getAsString(Downloads.COLUMN_USER_AGENT));
HttpHead request = new HttpHead(uri);
- String cookie = mValues.getAsString(Downloads.COOKIE_DATA);
+ String cookie = mValues.getAsString(Downloads.COLUMN_COOKIE_DATA);
if (cookie != null && cookie.length() > 0) {
request.addHeader("Cookie", cookie);
}
- String referer = mValues.getAsString(Downloads.REFERER);
+ String referer = mValues.getAsString(Downloads.COLUMN_REFERER);
if (referer != null && referer.length() > 0) {
request.addHeader("Referer", referer);
}
@@ -111,19 +111,19 @@
@Override
public void onPostExecute(String mimeType) {
if (mimeType != null) {
- String url = mValues.getAsString(Downloads.URI);
+ String url = mValues.getAsString(Downloads.COLUMN_URI);
if (mimeType.equalsIgnoreCase("text/plain") ||
mimeType.equalsIgnoreCase("application/octet-stream")) {
String newMimeType =
MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(url));
if (newMimeType != null) {
- mValues.put(Downloads.MIMETYPE, newMimeType);
+ mValues.put(Downloads.COLUMN_MIME_TYPE, newMimeType);
}
}
String filename = URLUtil.guessFileName(url,
null, mimeType);
- mValues.put(Downloads.FILENAME_HINT, filename);
+ mValues.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
}
// Start the download
diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java
index 6e9574c..2049bd0 100644
--- a/src/com/android/browser/FindDialog.java
+++ b/src/com/android/browser/FindDialog.java
@@ -42,7 +42,6 @@
private BrowserActivity mBrowserActivity;
// Views with which the user can interact.
- private View mOk;
private EditText mEditText;
private View mNextButton;
private View mPrevButton;
@@ -129,7 +128,6 @@
button = findViewById(R.id.done);
button.setOnClickListener(mFindCancelListener);
- mOk = button;
mMatches = (TextView) findViewById(R.id.matches);
mMatchesView = findViewById(R.id.matches_view);
@@ -143,23 +141,14 @@
mBrowserActivity.closeFind();
mWebView.clearMatches();
}
-
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- int code = event.getKeyCode();
- boolean up = event.getAction() == KeyEvent.ACTION_UP;
- switch (code) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER:
- if (!mEditText.hasFocus()) {
- break;
- }
- if (up) {
- findNext();
- }
- return true;
- default:
- break;
+ if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
+ && event.getAction() == KeyEvent.ACTION_UP
+ && mEditText.hasFocus()) {
+ findNext();
+ return true;
}
return super.dispatchKeyEvent(event);
}
diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java
index 55e43f0..b37a3bd 100644
--- a/src/com/android/browser/HistoryItem.java
+++ b/src/com/android/browser/HistoryItem.java
@@ -17,23 +17,13 @@
package com.android.browser;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
import android.content.Context;
-import android.database.Cursor;
import android.graphics.Bitmap;
-import android.net.Uri;
import android.provider.Browser;
-import android.util.Log;
import android.view.View;
-import android.webkit.WebIconDatabase;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
-import android.widget.Toast;
-
-import java.util.Date;
/**
* Layout representing a history item in the classic history viewer.
@@ -54,56 +44,13 @@
mListener = new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
- ContentResolver cr = mContext.getContentResolver();
- Cursor cursor = cr.query(
- Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- "url = ?",
- new String[] { mUrl },
- null);
- boolean first = cursor.moveToFirst();
- // Should be in the database no matter what
- if (!first) {
- throw new AssertionError("URL is not in the database!");
- }
if (isChecked) {
- // Add to bookmarks
- // FIXME: Share code with AddBookmarkPage.java
- ContentValues map = new ContentValues();
- map.put(Browser.BookmarkColumns.CREATED,
- new Date().getTime());
- map.put(Browser.BookmarkColumns.TITLE, getName());
- map.put(Browser.BookmarkColumns.BOOKMARK, 1);
- try {
- cr.update(Browser.BOOKMARKS_URI, map,
- "_id = " + cursor.getInt(0), null);
- } catch (IllegalStateException e) {
- Log.e("HistoryItem", "no database!");
- }
- WebIconDatabase.getInstance().retainIconForPageUrl(mUrl);
- // catch IllegalStateException?
- Toast.makeText(mContext, R.string.added_to_bookmarks,
- Toast.LENGTH_LONG).show();
+ Bookmarks.addBookmark(mContext,
+ mContext.getContentResolver(), mUrl, getName());
} else {
- // Remove from bookmarks
- // FIXME: This code should be shared with
- // BrowserBookmarksAdapter.java
- WebIconDatabase.getInstance().releaseIconForPageUrl(mUrl);
- Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
- cursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
- // It is no longer a bookmark, but it is still a visited
- // site.
- ContentValues values = new ContentValues();
- values.put(Browser.BookmarkColumns.BOOKMARK, 0);
- try {
- cr.update(uri, values, null, null);
- } catch (IllegalStateException e) {
- Log.e("HistoryItem", "no database!");
- }
- Toast.makeText(mContext, R.string.removed_from_bookmarks,
- Toast.LENGTH_LONG).show();
+ Bookmarks.removeFromBookmarks(mContext,
+ mContext.getContentResolver(), mUrl);
}
- cursor.deactivate();
}
};
}
diff --git a/src/com/android/browser/MostVisitedActivity.java b/src/com/android/browser/MostVisitedActivity.java
index 704ee27..90052d3 100644
--- a/src/com/android/browser/MostVisitedActivity.java
+++ b/src/com/android/browser/MostVisitedActivity.java
@@ -35,6 +35,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewStub;
import java.util.Vector;
@@ -50,8 +51,7 @@
.addListener(new IconReceiver());
setListAdapter(mAdapter);
ListView list = getListView();
- LayoutInflater factory = LayoutInflater.from(this);
- View v = factory.inflate(R.layout.empty_history, null);
+ View v = new ViewStub(this, R.layout.empty_history);
addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
list.setEmptyView(v);
@@ -84,9 +84,9 @@
private Vector<DataSetObserver> mObservers;
private Cursor mCursor;
// These correspond with projection below.
- private final int mUrlIndex = 0;
- private final int mTitleIndex = 1;
- private final int mBookmarkIndex = 2;
+ private static final int mUrlIndex = 0;
+ private static final int mTitleIndex = 1;
+ private static final int mBookmarkIndex = 2;
MyAdapter() {
mObservers = new Vector<DataSetObserver>();
diff --git a/src/com/android/browser/PermissionDialog.java b/src/com/android/browser/PermissionDialog.java
new file mode 100644
index 0000000..b71261a
--- /dev/null
+++ b/src/com/android/browser/PermissionDialog.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2009 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.app.Dialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Permission dialog for HTML5
+ * @hide
+ */
+public class PermissionDialog extends Activity {
+
+ private static final String TAG = "PermissionDialog";
+ public static final String PARAM_ORIGIN = "origin";
+ public static final String PARAM_QUOTA = "quota";
+
+ private String mWebStorageOrigin;
+ private long mWebStorageQuota = 0;
+ private int mNotification = 0;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ getParameters();
+ setupDialog();
+ }
+
+ private void getParameters() {
+ Intent intent = getIntent();
+ mWebStorageOrigin = intent.getStringExtra(PARAM_ORIGIN);
+ mWebStorageQuota = intent.getLongExtra(PARAM_QUOTA, 0);
+ }
+
+ private void setupDialog() {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.permission_dialog);
+
+ setIcon(R.id.icon, android.R.drawable.ic_popup_disk_full);
+ setText(R.id.dialog_title, R.string.query_storage_quota_prompt);
+ setText(R.id.dialog_message, R.string.query_storage_quota_message);
+ setCharSequence(R.id.origin, mWebStorageOrigin);
+
+ setupButton(R.id.button_allow, R.string.permission_button_allow,
+ new View.OnClickListener() {
+ public void onClick(View v) { allow(); }
+ });
+ setupButton(R.id.button_alwaysdeny, R.string.permission_button_alwaysdeny,
+ new View.OnClickListener() {
+ public void onClick(View v) { alwaysdeny(); }
+ });
+ setupButton(R.id.button_deny, R.string.permission_button_deny,
+ new View.OnClickListener() {
+ public void onClick(View v) { deny(); }
+ });
+ }
+
+ private void setText(int viewID, int stringID) {
+ setCharSequence(viewID, getString(stringID));
+ }
+
+ private void setCharSequence(int viewID, CharSequence string) {
+ View view = findViewById(viewID);
+ if (view == null) {
+ return;
+ }
+ view.setVisibility(View.VISIBLE);
+ TextView textView = (TextView) view;
+ textView.setText(string);
+ }
+
+ private void setIcon(int viewID, int imageID) {
+ View view = findViewById(viewID);
+ if (view == null) {
+ return;
+ }
+ view.setVisibility(View.VISIBLE);
+ ImageView icon = (ImageView) view;
+ icon.setImageResource(imageID);
+ }
+
+ private void setupButton(int viewID, int stringID,
+ View.OnClickListener listener) {
+ View view = findViewById(viewID);
+ if (view == null) {
+ return;
+ }
+ setText(viewID, stringID);
+ view.setOnClickListener(listener);
+ }
+
+ private void useNextQuota() {
+ CharSequence[] values = getResources().getTextArray(
+ R.array.webstorage_quota_entries_values);
+ for (int i=0; i<values.length; i++) {
+ long value = Long.parseLong(values[i].toString());
+ value *= (1024 * 1024); // the string array is expressed in MB
+ if (value > mWebStorageQuota) {
+ mWebStorageQuota = value;
+ break;
+ }
+ }
+ }
+
+ private void allow() {
+ // If somehow there is no "next quota" in the ladder,
+ // we'll add 1MB anyway.
+ mWebStorageQuota += 1024*1024;
+ useNextQuota();
+ mNotification = R.string.webstorage_notification;
+ closeDialog();
+ }
+
+ private void alwaysdeny() {
+ // Setting the quota to 0 will prevent any new data to be
+ // added, but the existing data will not be deleted.
+ mWebStorageQuota = 0;
+ mNotification = R.string.webstorage_notification;
+ closeDialog();
+ }
+
+ private void deny() {
+ closeDialog();
+ }
+
+ private void closeDialog() {
+ Intent intent = new Intent();
+ intent.putExtra(PARAM_QUOTA, mWebStorageQuota);
+ setResult(RESULT_OK, intent);
+ showToast();
+ finish();
+ }
+
+ private void showToast() {
+ if (mNotification != 0) {
+ Toast toast = Toast.makeText(this, mNotification, Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.BOTTOM, 0, 0);
+ toast.show();
+ }
+ }
+
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK)
+ && (event.getAction() == KeyEvent.ACTION_DOWN)) {
+ closeDialog();
+ return true; // event consumed
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+}
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 575be8d..c7c3e3f 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -195,6 +195,8 @@
// url has not changed.
private String mOriginalUrl;
+ private ErrorConsoleView mErrorConsole;
+
// Construct a new tab
private Tab(WebView w, boolean closeOnExit, String appId, String url) {
mMainView = w;
@@ -388,6 +390,28 @@
}
/**
+ * Return the current tab's error console. Creates the console if createIfNEcessary
+ * is true and we haven't already created the console.
+ * @param createIfNecessary Flag to indicate if the console should be created if it has
+ * not been already.
+ * @return The current tab's error console, or null if one has not been created and
+ * createIfNecessary is false.
+ */
+ ErrorConsoleView getCurrentErrorConsole(boolean createIfNecessary) {
+ Tab t = getTab(mCurrentTab);
+ if (t == null) {
+ return null;
+ }
+
+ if (createIfNecessary && t.mErrorConsole == null) {
+ t.mErrorConsole = new ErrorConsoleView(mActivity);
+ t.mErrorConsole.setWebView(t.mMainView);
+ }
+
+ return t.mErrorConsole;
+ }
+
+ /**
* Return the current tab's top-level WebView. This can return a subwindow
* if one exists.
* @return The top-level WebView of the current tab.
@@ -446,6 +470,9 @@
* @return index of Tab or -1 if not found
*/
int getTabIndex(Tab tab) {
+ if (tab == null) {
+ return -1;
+ }
return mTabs.indexOf(tab);
}
@@ -681,11 +708,11 @@
return;
}
- // free the WebView cache
- Log.w(LOGTAG, "Free WebView cache");
+ // free the WebView's unused memory (this includes the cache)
+ Log.w(LOGTAG, "Free WebView's unused memory and cache");
WebView view = getCurrentWebView();
if (view != null) {
- view.clearCache(false);
+ view.freeMemory();
}
// force a gc
System.gc();
@@ -801,6 +828,45 @@
return null;
}
+ // This method checks if a non-app tab (one created within the browser)
+ // matches the given url.
+ private boolean tabMatchesUrl(Tab t, String url) {
+ if (t.mAppId != null) {
+ return false;
+ } else if (t.mMainView == null) {
+ return false;
+ } else if (url.equals(t.mMainView.getUrl()) ||
+ url.equals(t.mMainView.getOriginalUrl())) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the tab that has no app id associated with it and the url of the
+ * tab matches the given url.
+ * @param url The url to search for.
+ */
+ Tab findUnusedTabWithUrl(String url) {
+ if (url == null) {
+ return null;
+ }
+ // Check the current tab first.
+ Tab t = getCurrentTab();
+ if (t != null && tabMatchesUrl(t, url)) {
+ return t;
+ }
+ // Now check all the rest.
+ final int size = getTabCount();
+ for (int i = 0; i < size; i++) {
+ t = getTab(i);
+ if (tabMatchesUrl(t, url)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
/**
* Recreate the main WebView of the given tab. Returns true if the WebView
* was deleted.
@@ -865,6 +931,48 @@
return setCurrentTab(newTab, false);
}
+ /*package*/ void pauseCurrentTab() {
+ Tab t = getCurrentTab();
+ if (t != null) {
+ t.mMainView.onPause();
+ if (t.mSubView != null) {
+ t.mSubView.onPause();
+ }
+ }
+ }
+
+ /*package*/ void resumeCurrentTab() {
+ Tab t = getCurrentTab();
+ if (t != null) {
+ t.mMainView.onResume();
+ if (t.mSubView != null) {
+ t.mSubView.onResume();
+ }
+ }
+ }
+
+ private void putViewInForeground(WebView v, WebViewClient vc,
+ WebChromeClient cc) {
+ v.setWebViewClient(vc);
+ v.setWebChromeClient(cc);
+ v.setOnCreateContextMenuListener(mActivity);
+ v.setDownloadListener(mActivity);
+ v.onResume();
+ }
+
+ private void putViewInBackground(WebView v) {
+ // Set an empty callback so that default actions are not triggered.
+ v.setWebViewClient(mEmptyClient);
+ v.setWebChromeClient(mBackgroundChromeClient);
+ v.setOnCreateContextMenuListener(null);
+ // Leave the DownloadManager attached so that downloads can start in
+ // a non-active window. This can happen when going to a site that does
+ // a redirect after a period of time. The user could have switched to
+ // another tab while waiting for the download to start.
+ v.setDownloadListener(mActivity);
+ v.onPause();
+ }
+
/**
* If force is true, this method skips the check for newTab == current.
*/
@@ -890,7 +998,6 @@
mTabQueue.add(newTab);
WebView mainView;
- WebView subView;
// Display the new current tab
mCurrentTab = mTabs.indexOf(newTab);
@@ -900,17 +1007,12 @@
// Same work as in createNewTab() except don't do new Tab()
newTab.mMainView = mainView = createNewWebView();
}
- mainView.setWebViewClient(mActivity.getWebViewClient());
- mainView.setWebChromeClient(mActivity.getWebChromeClient());
- mainView.setOnCreateContextMenuListener(mActivity);
- mainView.setDownloadListener(mActivity);
+ putViewInForeground(mainView, mActivity.getWebViewClient(),
+ mActivity.getWebChromeClient());
// Add the subwindow if it exists
if (newTab.mSubViewContainer != null) {
- subView = newTab.mSubView;
- subView.setWebViewClient(newTab.mSubViewClient);
- subView.setWebChromeClient(newTab.mSubViewChromeClient);
- subView.setOnCreateContextMenuListener(mActivity);
- subView.setDownloadListener(mActivity);
+ putViewInForeground(newTab.mSubView, newTab.mSubViewClient,
+ newTab.mSubViewChromeClient);
}
if (needRestore) {
// Have to finish setCurrentTab work before calling restoreState
@@ -925,23 +1027,9 @@
* Put the tab in the background using all the empty/background clients.
*/
private void putTabInBackground(Tab t) {
- WebView mainView = t.mMainView;
- // Set an empty callback so that default actions are not triggered.
- mainView.setWebViewClient(mEmptyClient);
- mainView.setWebChromeClient(mBackgroundChromeClient);
- mainView.setOnCreateContextMenuListener(null);
- // Leave the DownloadManager attached so that downloads can start in
- // a non-active window. This can happen when going to a site that does
- // a redirect after a period of time. The user could have switched to
- // another tab while waiting for the download to start.
- mainView.setDownloadListener(mActivity);
- WebView subView = t.mSubView;
- if (subView != null) {
- // Set an empty callback so that default actions are not triggered.
- subView.setWebViewClient(mEmptyClient);
- subView.setWebChromeClient(mBackgroundChromeClient);
- subView.setOnCreateContextMenuListener(null);
- subView.setDownloadListener(mActivity);
+ putViewInBackground(t.mMainView);
+ if (t.mSubView != null) {
+ putViewInBackground(t.mSubView);
}
}
@@ -1041,6 +1129,11 @@
data.mScale = w.getScale();
data.mScrollX = w.getScrollX();
data.mScrollY = w.getScrollY();
+
+ // Remember the old picture if possible.
+ if (t.mPickerData != null) {
+ data.mPicture = t.mPickerData.mPicture;
+ }
t.mPickerData = data;
}
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
new file mode 100644
index 0000000..f534a03
--- /dev/null
+++ b/src/com/android/browser/TitleBar.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2009 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.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.webkit.WebView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+public class TitleBar extends LinearLayout {
+ private TextView mTitle;
+ private TextView mUrl;
+ private ImageView mLftButton;
+ private Drawable mBookmarkDrawable;
+ private View mRtButton;
+ private View mDivider;
+ private ProgressBar mCircularProgress;
+ private ProgressBar mHorizontalProgress;
+ private ImageView mFavicon;
+ private ImageView mLockIcon;
+ private boolean mInLoad;
+ private boolean mTitleSet;
+
+ public TitleBar(Context context) {
+ this(context, null);
+ }
+
+ public TitleBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.title_bar, this);
+
+ mTitle = (TextView) findViewById(R.id.title);
+ mUrl = (TextView) findViewById(R.id.url);
+
+ mLftButton = (ImageView) findViewById(R.id.lft_button);
+ mRtButton = findViewById(R.id.rt_button);
+
+ mCircularProgress = (ProgressBar) findViewById(R.id.progress_circular);
+ mHorizontalProgress = (ProgressBar) findViewById(
+ R.id.progress_horizontal);
+ mFavicon = (ImageView) findViewById(R.id.favicon);
+ mLockIcon = (ImageView) findViewById(R.id.lock_icon);
+ mDivider = findViewById(R.id.divider);
+ }
+
+ /* package */ void setBrowserActivity(final BrowserActivity activity) {
+ mLftButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (mInLoad) {
+ WebView webView = activity.getTopWindow();
+ if (webView != null) {
+ webView.stopLoading();
+ }
+ } else {
+ activity.bookmarksOrHistoryPicker(false);
+ }
+ }
+ });
+ mRtButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ WebView webView = activity.getTopWindow();
+ if (webView != null) {
+ webView.zoomScrollOut();
+ }
+ }
+ });
+ setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ activity.onSearchRequested();
+ }
+ });
+ }
+
+ /* package */ void setFavicon(Drawable d) {
+ mFavicon.setImageDrawable(d);
+ }
+
+ /* package */ void setLock(Drawable d) {
+ if (d == null) {
+ mLockIcon.setVisibility(View.GONE);
+ } else {
+ mLockIcon.setImageDrawable(d);
+ mLockIcon.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /* package */ void setProgress(int newProgress) {
+ if (newProgress == mCircularProgress.getMax()) {
+ mCircularProgress.setVisibility(View.GONE);
+ mHorizontalProgress.setVisibility(View.GONE);
+ mDivider.setVisibility(View.VISIBLE);
+ mRtButton.setVisibility(View.VISIBLE);
+ mLftButton.setImageDrawable(mBookmarkDrawable);
+ mInLoad = false;
+ if (!mTitleSet) {
+ mTitle.setText(mUrl.getText());
+ mUrl.setText(null);
+ mTitleSet = true;
+ }
+ } else {
+ mCircularProgress.setProgress(newProgress);
+ mHorizontalProgress.setProgress(newProgress);
+ mCircularProgress.setVisibility(View.VISIBLE);
+ mHorizontalProgress.setVisibility(View.VISIBLE);
+ mDivider.setVisibility(View.GONE);
+ mRtButton.setVisibility(View.GONE);
+ if (mBookmarkDrawable == null) {
+ // The drawable was assigned in the xml file, so it already
+ // exists. Keep a pointer to it when we switch to the resource
+ // so we can easily switch back.
+ mBookmarkDrawable = mLftButton.getDrawable();
+ }
+ mLftButton.setImageResource(
+ com.android.internal.R.drawable.ic_menu_stop);
+ mInLoad = true;
+ }
+ }
+
+ /* package */ void setTitleAndUrl(CharSequence title, CharSequence url) {
+ if (url != null) {
+ url = BrowserActivity.buildTitleUrl(url.toString());
+ }
+ if (null == title) {
+ if (mInLoad) {
+ mTitleSet = false;
+ mTitle.setText(R.string.title_bar_loading);
+ } else {
+ // If the page has no title, put the url in the title space
+ // and leave the url blank.
+ mTitle.setText(url);
+ mUrl.setText(null);
+ mTitleSet = true;
+ return;
+ }
+ } else {
+ mTitle.setText(title);
+ mTitleSet = true;
+ }
+ mUrl.setText(url);
+ }
+
+ /* package */ void setToTabPicker() {
+ mTitle.setText(R.string.tab_picker_title);
+ setFavicon(null);
+ setLock(null);
+ mCircularProgress.setVisibility(View.GONE);
+ mHorizontalProgress.setVisibility(View.GONE);
+ }
+}
diff --git a/src/com/android/browser/WebsiteSettingsActivity.java b/src/com/android/browser/WebsiteSettingsActivity.java
new file mode 100644
index 0000000..7fea766
--- /dev/null
+++ b/src/com/android/browser/WebsiteSettingsActivity.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2009 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.AlertDialog;
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Browser;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebIconDatabase;
+import android.webkit.WebStorage;
+import android.widget.ArrayAdapter;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ * Manage the settings for an origin.
+ * We use it to keep track of the HTML5 settings, i.e. database (webstorage).
+ */
+public class WebsiteSettingsActivity extends ListActivity {
+
+ private String LOGTAG = "WebsiteSettingsActivity";
+ private static String sMBStored = null;
+ private SiteAdapter mAdapter = null;
+
+ class Site {
+ private String mOrigin;
+ private String mTitle;
+ private Bitmap mIcon;
+
+ public Site(String origin, String title, Bitmap icon) {
+ mOrigin = origin;
+ mTitle = title;
+ mIcon = icon;
+ }
+
+ public String getOrigin() {
+ return mOrigin;
+ }
+
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public void setIcon(Bitmap icon) {
+ mIcon = icon;
+ }
+
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+ }
+
+ class SiteAdapter extends ArrayAdapter<Site>
+ implements AdapterView.OnItemClickListener {
+ private int mResource;
+ private LayoutInflater mInflater;
+ private Bitmap mDefaultIcon;
+ private Site mCurrentSite;
+ private final static int STORED_DATA = 0;
+
+ public SiteAdapter(Context context, int rsc) {
+ super(context, rsc);
+ mResource = rsc;
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDefaultIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_launcher_shortcut_browser_bookmark);
+ populateOrigins();
+ }
+
+ public void populateOrigins() {
+ clear();
+
+ // Get the list of origins we want to display
+ HashMap<String, Site> uris = new HashMap<String, Site>();
+ Vector origins = WebStorage.getInstance().getOrigins();
+ if (origins != null) {
+ for (int i = 0; i < origins.size(); i++) {
+ String origin = (String) origins.get(i);
+ Site site = new Site(origin, origin, null);
+ uris.put(Uri.parse(origin).getHost(), site);
+ }
+ }
+
+ // Check the bookmark db -- if one of our origin matches,
+ // we set its title and favicon
+ Cursor c = getContext().getContentResolver().query(Browser.BOOKMARKS_URI,
+ new String[] { Browser.BookmarkColumns.URL, Browser.BookmarkColumns.TITLE,
+ Browser.BookmarkColumns.FAVICON }, "bookmark = 1", null, null);
+
+ if ((c != null) && c.moveToFirst()) {
+ int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
+ int titleIndex = c.getColumnIndex(Browser.BookmarkColumns.TITLE);
+ int faviconIndex = c.getColumnIndex(Browser.BookmarkColumns.FAVICON);
+ do {
+ String url = c.getString(urlIndex);
+ String host = Uri.parse(url).getHost();
+ if (uris.containsKey(host)) {
+ String title = c.getString(titleIndex);
+ Site site = uris.get(host);
+ site.setTitle(title);
+ byte[] data = c.getBlob(faviconIndex);
+ if (data != null) {
+ Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
+ if (bmp != null) {
+ site.setIcon(bmp);
+ }
+ }
+ }
+ } while (c.moveToNext());
+ }
+
+ // We can now simply populate our array with Site instances
+ Set keys = uris.keySet();
+ Iterator iter = keys.iterator();
+ while (iter.hasNext()) {
+ String origin = (String) iter.next();
+ Site site = uris.get(origin);
+ add(site);
+ }
+
+ if (getCount() == 0) {
+ finish(); // we close the screen
+ }
+ }
+
+ public int getCount() {
+ if (mCurrentSite == null) {
+ return super.getCount();
+ }
+ return 1; // db view
+ }
+
+ public String sizeValueToString(long value) {
+ float mb = (float) value / (1024.0F * 1024.0F);
+ int val = (int) (mb * 10);
+ float ret = (float) (val / 10.0F);
+ if (ret <= 0) {
+ return "0";
+ }
+ return String.valueOf(ret);
+ }
+
+ /*
+ * If we receive the back event and are displaying
+ * site's settings, we want to go back to the main
+ * list view. If not, we just do nothing (see
+ * dispatchKeyEvent() below).
+ */
+ public boolean backKeyPressed() {
+ if (mCurrentSite != null) {
+ mCurrentSite = null;
+ populateOrigins();
+ notifyDataSetChanged();
+ return true;
+ }
+ return false;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ TextView title;
+ TextView subtitle;
+ ImageView icon;
+
+ if (convertView == null) {
+ view = mInflater.inflate(mResource, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ title = (TextView) view.findViewById(R.id.title);
+ subtitle = (TextView) view.findViewById(R.id.subtitle);
+ icon = (ImageView) view.findViewById(R.id.icon);
+
+ if (mCurrentSite == null) {
+ Site site = getItem(position);
+ title.setText(site.getTitle());
+ subtitle.setText(site.getOrigin());
+ icon.setVisibility(View.VISIBLE);
+ Bitmap bmp = site.getIcon();
+ if (bmp == null) {
+ bmp = mDefaultIcon;
+ }
+ icon.setImageBitmap(bmp);
+ // We set the site as the view's tag,
+ // so that we can get it in onItemClick()
+ view.setTag(site);
+ } else {
+ icon.setVisibility(View.GONE);
+ if (position == STORED_DATA) {
+ String origin = mCurrentSite.getOrigin();
+ long usageValue = WebStorage.getInstance().getUsageForOrigin(origin);
+ String usage = sizeValueToString(usageValue) + " " + sMBStored;
+
+ title.setText(R.string.webstorage_clear_data_title);
+ subtitle.setText(usage);
+ }
+ }
+
+ return view;
+ }
+
+ public void onItemClick(AdapterView<?> parent,
+ View view,
+ int position,
+ long id) {
+ if (mCurrentSite != null) {
+ if (position == STORED_DATA) {
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.webstorage_clear_data_dialog_title)
+ .setMessage(R.string.webstorage_clear_data_dialog_message)
+ .setPositiveButton(R.string.webstorage_clear_data_dialog_ok_button,
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dlg, int which) {
+ WebStorage.getInstance().deleteOrigin(mCurrentSite.getOrigin());
+ mCurrentSite = null;
+ populateOrigins();
+ notifyDataSetChanged();
+ }})
+ .setNegativeButton(R.string.webstorage_clear_data_dialog_cancel_button, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+ } else {
+ mCurrentSite = (Site) view.getTag();
+ notifyDataSetChanged();
+ }
+ }
+ }
+
+ /**
+ * Intercepts the back key to immediately notify
+ * NativeDialog that we are done.
+ */
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK)
+ && (event.getAction() == KeyEvent.ACTION_DOWN)) {
+ if ((mAdapter != null) && (mAdapter.backKeyPressed())){
+ return true; // event consumed
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ if (sMBStored == null) {
+ sMBStored = getString(R.string.webstorage_origin_summary_mb_stored);
+ }
+ mAdapter = new SiteAdapter(this, R.layout.application);
+ setListAdapter(mAdapter);
+ getListView().setOnItemClickListener(mAdapter);
+ }
+}