diff options
97 files changed, 3320 insertions, 569 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7e98019..ac2b317 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -84,6 +84,16 @@ <data android:mimeType="application/xhtml+xml"/> <data android:mimeType="application/vnd.wap.xhtml+xml"/> </intent-filter> + <!-- For viewing saved web archives. --> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:scheme="file" /> + <data android:mimeType="application/x-webarchive-xml"/> + </intent-filter> <!-- We are also the main entry point of the browser. --> <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -184,6 +194,10 @@ </intent-filter> </activity> + <activity android:name="SaveToHomescreenDialog" android:label="Save to homescreen" android:theme="@android:style/Theme.Dialog" + android:configChanges="orientation|keyboardHidden" android:windowSoftInputMode="stateHidden"> + </activity> + <!--receiver android:name=".widget.BookmarkWidgetProvider" android:label="@string/bookmarks"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> diff --git a/res/anim/find_dialog_enter.xml b/res/anim/dialog_enter.xml index 5e597a4..6fbcb9e 100644 --- a/res/anim/find_dialog_enter.xml +++ b/res/anim/dialog_enter.xml @@ -16,6 +16,6 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/decelerate_interpolator"> - <translate android:fromYDelta="25%" android:toYDelta="0" android:duration="75"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" /> + <translate android:fromYDelta="-25%" android:toYDelta="0" android:duration="75"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" /> </set> diff --git a/res/anim/find_dialog_exit.xml b/res/anim/dialog_exit.xml index 854abd0..9845849 100644 --- a/res/anim/find_dialog_exit.xml +++ b/res/anim/dialog_exit.xml @@ -16,7 +16,7 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"> - <translate android:fromYDelta="0" android:toYDelta="50%" android:duration="50"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="50" /> + <translate android:fromYDelta="0" android:toYDelta="-50%" android:duration="50"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="50" /> </set> diff --git a/res/drawable-hdpi/button_selected.png b/res/drawable-hdpi/button_selected.png Binary files differnew file mode 100644 index 0000000..752deda --- /dev/null +++ b/res/drawable-hdpi/button_selected.png diff --git a/res/drawable-hdpi/default_video_poster.png b/res/drawable-hdpi/default_video_poster.png Binary files differnew file mode 100644 index 0000000..9bbaf9c --- /dev/null +++ b/res/drawable-hdpi/default_video_poster.png diff --git a/res/drawable-hdpi/ic_arrow_left.png b/res/drawable-hdpi/ic_arrow_left.png Binary files differnew file mode 100644 index 0000000..bcbe3f6 --- /dev/null +++ b/res/drawable-hdpi/ic_arrow_left.png diff --git a/res/drawable-hdpi/ic_arrow_right.png b/res/drawable-hdpi/ic_arrow_right.png Binary files differnew file mode 100644 index 0000000..6095533 --- /dev/null +++ b/res/drawable-hdpi/ic_arrow_right.png diff --git a/res/drawable-hdpi/ic_btn_copy.png b/res/drawable-hdpi/ic_btn_copy.png Binary files differnew file mode 100644 index 0000000..04fda7f --- /dev/null +++ b/res/drawable-hdpi/ic_btn_copy.png diff --git a/res/drawable-hdpi/ic_btn_find.png b/res/drawable-hdpi/ic_btn_find.png Binary files differnew file mode 100755 index 0000000..20e1fbc --- /dev/null +++ b/res/drawable-hdpi/ic_btn_find.png diff --git a/res/drawable-hdpi/ic_btn_select_all.png b/res/drawable-hdpi/ic_btn_select_all.png Binary files differnew file mode 100644 index 0000000..839915b --- /dev/null +++ b/res/drawable-hdpi/ic_btn_select_all.png diff --git a/res/drawable-hdpi/ic_btn_share.png b/res/drawable-hdpi/ic_btn_share.png Binary files differnew file mode 100644 index 0000000..44db9b1 --- /dev/null +++ b/res/drawable-hdpi/ic_btn_share.png diff --git a/res/drawable-hdpi/ic_menu.png b/res/drawable-hdpi/ic_menu.png Binary files differnew file mode 100644 index 0000000..00f784f --- /dev/null +++ b/res/drawable-hdpi/ic_menu.png diff --git a/res/drawable-hdpi/ic_menu_downloads.png b/res/drawable-hdpi/ic_menu_downloads.png Binary files differnew file mode 100644 index 0000000..c85f6a2 --- /dev/null +++ b/res/drawable-hdpi/ic_menu_downloads.png diff --git a/res/drawable-hdpi/ic_menu_find.png b/res/drawable-hdpi/ic_menu_find.png Binary files differnew file mode 100644 index 0000000..17ac694 --- /dev/null +++ b/res/drawable-hdpi/ic_menu_find.png diff --git a/res/drawable-hdpi/ic_menu_pageinfo.png b/res/drawable-hdpi/ic_menu_pageinfo.png Binary files differnew file mode 100644 index 0000000..293a021 --- /dev/null +++ b/res/drawable-hdpi/ic_menu_pageinfo.png diff --git a/res/drawable-hdpi/ic_menu_settings.png b/res/drawable-hdpi/ic_menu_settings.png Binary files differnew file mode 100644 index 0000000..46be101 --- /dev/null +++ b/res/drawable-hdpi/ic_menu_settings.png diff --git a/res/drawable-hdpi/ic_menu_share.png b/res/drawable-hdpi/ic_menu_share.png Binary files differnew file mode 100644 index 0000000..4b69736 --- /dev/null +++ b/res/drawable-hdpi/ic_menu_share.png diff --git a/res/drawable-hdpi/ic_pages.png b/res/drawable-hdpi/ic_pages.png Binary files differnew file mode 100644 index 0000000..3fc2e46 --- /dev/null +++ b/res/drawable-hdpi/ic_pages.png diff --git a/res/drawable-hdpi/ic_reload.png b/res/drawable-hdpi/ic_reload.png Binary files differnew file mode 100644 index 0000000..ebad6da --- /dev/null +++ b/res/drawable-hdpi/ic_reload.png diff --git a/res/drawable-hdpi/ic_star.png b/res/drawable-hdpi/ic_star.png Binary files differnew file mode 100644 index 0000000..666c670 --- /dev/null +++ b/res/drawable-hdpi/ic_star.png diff --git a/res/drawable-hdpi/ic_stop.png b/res/drawable-hdpi/ic_stop.png Binary files differnew file mode 100644 index 0000000..0f1337f --- /dev/null +++ b/res/drawable-hdpi/ic_stop.png diff --git a/res/drawable-hdpi/pattern_carbon_fiber_dark.png b/res/drawable-hdpi/pattern_carbon_fiber_dark.png Binary files differnew file mode 100644 index 0000000..cc8e0c5 --- /dev/null +++ b/res/drawable-hdpi/pattern_carbon_fiber_dark.png diff --git a/res/drawable-hdpi/progress_stop.png b/res/drawable-hdpi/progress_stop.png Binary files differnew file mode 100644 index 0000000..aacece2 --- /dev/null +++ b/res/drawable-hdpi/progress_stop.png diff --git a/res/drawable-hdpi/tab_selected_bg.9.png b/res/drawable-hdpi/tab_selected_bg.9.png Binary files differnew file mode 100644 index 0000000..4e0f740 --- /dev/null +++ b/res/drawable-hdpi/tab_selected_bg.9.png diff --git a/res/drawable-hdpi/tab_unselected_bg.9.png b/res/drawable-hdpi/tab_unselected_bg.9.png Binary files differnew file mode 100644 index 0000000..9ab7c56 --- /dev/null +++ b/res/drawable-hdpi/tab_unselected_bg.9.png diff --git a/res/drawable-mdpi/button_selected.png b/res/drawable-mdpi/button_selected.png Binary files differnew file mode 100644 index 0000000..4fd1aa3 --- /dev/null +++ b/res/drawable-mdpi/button_selected.png diff --git a/res/drawable/default_video_poster.png b/res/drawable-mdpi/default_video_poster.png Binary files differindex f457f23..f457f23 100755 --- a/res/drawable/default_video_poster.png +++ b/res/drawable-mdpi/default_video_poster.png diff --git a/res/drawable-mdpi/ic_arrow_left.png b/res/drawable-mdpi/ic_arrow_left.png Binary files differnew file mode 100644 index 0000000..58fd2ca --- /dev/null +++ b/res/drawable-mdpi/ic_arrow_left.png diff --git a/res/drawable-mdpi/ic_arrow_right.png b/res/drawable-mdpi/ic_arrow_right.png Binary files differnew file mode 100644 index 0000000..aaf3fde --- /dev/null +++ b/res/drawable-mdpi/ic_arrow_right.png diff --git a/res/drawable-mdpi/ic_btn_copy.png b/res/drawable-mdpi/ic_btn_copy.png Binary files differnew file mode 100644 index 0000000..04fda7f --- /dev/null +++ b/res/drawable-mdpi/ic_btn_copy.png diff --git a/res/drawable-mdpi/ic_btn_find.png b/res/drawable-mdpi/ic_btn_find.png Binary files differnew file mode 100755 index 0000000..20e1fbc --- /dev/null +++ b/res/drawable-mdpi/ic_btn_find.png diff --git a/res/drawable-mdpi/ic_btn_select_all.png b/res/drawable-mdpi/ic_btn_select_all.png Binary files differnew file mode 100644 index 0000000..839915b --- /dev/null +++ b/res/drawable-mdpi/ic_btn_select_all.png diff --git a/res/drawable-mdpi/ic_btn_share.png b/res/drawable-mdpi/ic_btn_share.png Binary files differnew file mode 100644 index 0000000..44db9b1 --- /dev/null +++ b/res/drawable-mdpi/ic_btn_share.png diff --git a/res/drawable-mdpi/ic_menu.png b/res/drawable-mdpi/ic_menu.png Binary files differnew file mode 100644 index 0000000..520b2c3 --- /dev/null +++ b/res/drawable-mdpi/ic_menu.png diff --git a/res/drawable-mdpi/ic_menu_downloads.png b/res/drawable-mdpi/ic_menu_downloads.png Binary files differnew file mode 100644 index 0000000..fff5022 --- /dev/null +++ b/res/drawable-mdpi/ic_menu_downloads.png diff --git a/res/drawable-mdpi/ic_menu_find.png b/res/drawable-mdpi/ic_menu_find.png Binary files differnew file mode 100644 index 0000000..4d96348 --- /dev/null +++ b/res/drawable-mdpi/ic_menu_find.png diff --git a/res/drawable-mdpi/ic_menu_pageinfo.png b/res/drawable-mdpi/ic_menu_pageinfo.png Binary files differnew file mode 100644 index 0000000..c04f0e3 --- /dev/null +++ b/res/drawable-mdpi/ic_menu_pageinfo.png diff --git a/res/drawable-mdpi/ic_menu_settings.png b/res/drawable-mdpi/ic_menu_settings.png Binary files differnew file mode 100644 index 0000000..7a642d6 --- /dev/null +++ b/res/drawable-mdpi/ic_menu_settings.png diff --git a/res/drawable-mdpi/ic_menu_share.png b/res/drawable-mdpi/ic_menu_share.png Binary files differnew file mode 100644 index 0000000..ea2b672 --- /dev/null +++ b/res/drawable-mdpi/ic_menu_share.png diff --git a/res/drawable-mdpi/ic_pages.png b/res/drawable-mdpi/ic_pages.png Binary files differnew file mode 100644 index 0000000..fba4651 --- /dev/null +++ b/res/drawable-mdpi/ic_pages.png diff --git a/res/drawable-mdpi/ic_reload.png b/res/drawable-mdpi/ic_reload.png Binary files differnew file mode 100644 index 0000000..ec0c238 --- /dev/null +++ b/res/drawable-mdpi/ic_reload.png diff --git a/res/drawable-mdpi/ic_star.png b/res/drawable-mdpi/ic_star.png Binary files differnew file mode 100644 index 0000000..20a40de --- /dev/null +++ b/res/drawable-mdpi/ic_star.png diff --git a/res/drawable-mdpi/ic_stop.png b/res/drawable-mdpi/ic_stop.png Binary files differnew file mode 100644 index 0000000..7ee56e9 --- /dev/null +++ b/res/drawable-mdpi/ic_stop.png diff --git a/res/drawable/pattern_carbon_fiber_dark.png b/res/drawable-mdpi/pattern_carbon_fiber_dark.png Binary files differindex 07af4b6..07af4b6 100644 --- a/res/drawable/pattern_carbon_fiber_dark.png +++ b/res/drawable-mdpi/pattern_carbon_fiber_dark.png diff --git a/res/drawable-mdpi/progress_stop.png b/res/drawable-mdpi/progress_stop.png Binary files differnew file mode 100644 index 0000000..a85f987 --- /dev/null +++ b/res/drawable-mdpi/progress_stop.png diff --git a/res/drawable-mdpi/tab_selected_bg.9.png b/res/drawable-mdpi/tab_selected_bg.9.png Binary files differnew file mode 100644 index 0000000..5e6b1ed --- /dev/null +++ b/res/drawable-mdpi/tab_selected_bg.9.png diff --git a/res/drawable-mdpi/tab_unselected_bg.9.png b/res/drawable-mdpi/tab_unselected_bg.9.png Binary files differnew file mode 100644 index 0000000..c19443a --- /dev/null +++ b/res/drawable-mdpi/tab_unselected_bg.9.png diff --git a/res/values/themes.xml b/res/drawable/browserbarbutton.xml index bb922dd..35a6f48 100644 --- a/res/values/themes.xml +++ b/res/drawable/browserbarbutton.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project +<!-- Copyright (C) 2010 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,15 +14,8 @@ limitations under the License. --> -<resources> - <style name="FindDialogTheme"> - <item name="android:windowFrame">@null</item> - <item name="android:windowIsFloating">true</item> - <item name="android:windowIsTranslucent">true</item> - <item name="android:windowNoTitle">true</item> - <item name="android:background">@null</item> - <item name="android:windowBackground">@null</item> - <item name="android:windowAnimationStyle">@style/FindDialog</item> - <item name="android:backgroundDimEnabled">false</item> - </style> -</resources> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" + android:drawable="@drawable/button_selected" /> + <item android:state_pressed="false" android:drawable="@drawable/clear" /> +</selector> diff --git a/res/drawable/clear.xml b/res/drawable/clear.xml new file mode 100644 index 0000000..5973f5c --- /dev/null +++ b/res/drawable/clear.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- Used by browserbarbutton to show a clear background for the non pressed + state --> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#00000000"/> + <padding android:left="9dp" android:top="9dp" + android:right="9dp" android:bottom="9dp" /> +</shape> diff --git a/res/drawable/progress.xml b/res/drawable/progress.xml new file mode 100644 index 0000000..dd7c375 --- /dev/null +++ b/res/drawable/progress.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:id="@android:id/background"> + <shape> + <solid android:color="#ffffffff"/> + </shape> + </item> + <item android:id="@android:id/progress"> + <clip> + <shape> + <solid android:color="#ff94b73f"/> + </shape> + </clip> + </item> + +</layer-list> diff --git a/res/drawable/tab_unselected.xml b/res/drawable/tab_unselected.xml new file mode 100644 index 0000000..1f22ae1 --- /dev/null +++ b/res/drawable/tab_unselected.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#00000000"/> + <stroke android:width="3dp" android:color="#ff404040"/> + <padding android:left="9dp" android:top="9dp" + android:right="9dp" android:bottom="9dp" /> +</shape> diff --git a/res/drawable/textfield_nostroke.xml b/res/drawable/textfield_nostroke.xml new file mode 100644 index 0000000..2945056 --- /dev/null +++ b/res/drawable/textfield_nostroke.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#ffd0d0d0"/> + <stroke android:width="1dp" android:color="#ff94b73f"/> + <padding android:left="9dp" android:top="9dp" + android:right="9dp" android:bottom="9dp" /> +</shape> diff --git a/res/drawable/textfield_stroke.xml b/res/drawable/textfield_stroke.xml new file mode 100644 index 0000000..4d4c74e --- /dev/null +++ b/res/drawable/textfield_stroke.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#ffffffff"/> + <stroke android:width="3dp" android:color="#ff94b73f"/> + <padding android:left="9dp" android:top="9dp" + android:right="9dp" android:bottom="9dp" /> +</shape> diff --git a/res/layout-land/title_bar_tabbed.xml b/res/layout-land/title_bar_tabbed.xml new file mode 100644 index 0000000..853dbeb --- /dev/null +++ b/res/layout-land/title_bar_tabbed.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- + Copyright 2010, The Android Open Source Project Licensed under the + Apache License, Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. See the License for + the specific language governing permissions and limitations under the + License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tabbedtitleland" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingLeft="6dip" + android:paddingRight="6dip" + android:background="#ffdddddd"> + <ImageButton + android:id="@+id/back" + android:src="@drawable/ic_arrow_left" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginRight="6dip" + android:background="@drawable/browserbarbutton" /> + <ImageButton + android:id="@+id/forward" + android:src="@drawable/ic_arrow_right" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginRight="6dip" + android:background="@drawable/browserbarbutton" /> + <ImageButton + android:id="@+id/star" + android:src="@drawable/ic_star" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginRight="6dip" + android:background="@drawable/browserbarbutton" /> + <com.android.browser.TabScrollView + android:id="@+id/tabs" + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_height="wrap_content" + android:orientation="horizontal" /> + <com.android.browser.UrlInputView + android:id="@+id/editurl" + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_height="wrap_content" + android:layout_marginLeft="3dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="@color/black" + android:gravity="center_vertical" + android:singleLine="true" + android:ellipsize="end" + android:lines="1" + android:scrollHorizontally="true" + android:visibility="gone" + android:background="@drawable/textfield_stroke" + android:inputType="textUri" + android:imeOptions="actionGo" /> + <ImageButton + android:id="@+id/newtab" + android:src="@drawable/ic_menu_new_window" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginRight="6dip" + android:background="@drawable/browserbarbutton" /> + <ImageButton + android:id="@+id/menu" + android:src="@drawable/ic_menu" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginRight="6dip" + android:background="@drawable/browserbarbutton" /> + <ImageButton + android:id="@+id/all_btn" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:scaleType="center" + android:background="@drawable/browserbarbutton" + android:src="@drawable/ic_pages" /> +</LinearLayout> diff --git a/res/layout/bookmark_thumbnail.xml b/res/layout/bookmark_thumbnail.xml index 1f017d0..363e632 100644 --- a/res/layout/bookmark_thumbnail.xml +++ b/res/layout/bookmark_thumbnail.xml @@ -35,7 +35,7 @@ <LinearLayout android:id="@+id/holder" android:layout_height="match_parent" android:layout_width="match_parent" - android:orientation="horizontal" + android:orientation="vertical" android:background="#99000000" android:gravity="center" android:layout_alignBottom="@+id/thumb" diff --git a/res/layout/browser_add_bookmark_const_url.xml b/res/layout/browser_add_bookmark_const_url.xml new file mode 100644 index 0000000..c6603f4 --- /dev/null +++ b/res/layout/browser_add_bookmark_const_url.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + > + + <ImageView android:id="@+id/titleDivider" + android:layout_width="match_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 xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical" + android:paddingTop="5dip" + android:paddingBottom="13dip" + android:paddingLeft="20dip" + android:paddingRight="20dip" > + + <TextView + android:id="@+id/titleText" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/name" + android:gravity="left" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <EditText + android:id="@+id/title" + android:layout_height="wrap_content" + android:layout_width="250dip" + android:gravity="fill_horizontal" + android:inputType="textCapSentences" + android:selectAllOnFocus="true" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#c6c3c6" + android:minHeight="54dip" + android:orientation="horizontal" + android:paddingTop="4dip" + android:paddingLeft="2dip" + android:paddingRight="2dip" > + <Button android:id="@+id/OK" + android:text="@string/save" + android:layout_width="0dip" + android:layout_gravity="left" + android:layout_weight="1" + android:maxLines="2" + android:layout_height="wrap_content" /> + <Button android:id="@+id/cancel" + android:text="@string/do_not_save" + android:layout_width="0dip" + android:layout_gravity="right" + android:layout_weight="1" + android:maxLines="2" + android:layout_height="wrap_content" /> + </LinearLayout> + +</LinearLayout> + diff --git a/res/layout/browser_select.xml b/res/layout/browser_select.xml new file mode 100644 index 0000000..b30be8d --- /dev/null +++ b/res/layout/browser_select.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/selectControls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="5dip" + android:paddingLeft="4dip" + android:paddingRight="4dip" + android:paddingBottom="1dip" + android:background="@android:drawable/bottom_bar"> + <ImageButton + android:src="@drawable/ic_btn_copy" + android:id="@+id/copy" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> + + <ImageButton + android:src="@drawable/ic_btn_share" + android:id="@+id/share" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> + + <ImageButton + android:src="@drawable/ic_btn_select_all" + android:id="@+id/select_all" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> + + <ImageButton + android:src="@drawable/ic_btn_find" + android:id="@+id/find" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> + + <LinearLayout + android:layout_height="fill_parent" + android:layout_width="fill_parent" + android:layout_weight="1" + /> + + <ImageButton + android:src="@drawable/ic_btn_close_panel" + android:id="@+id/done" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> +</LinearLayout> + diff --git a/res/layout/browser_subwindow.xml b/res/layout/browser_subwindow.xml index 76d72d5..adf3284 100644 --- a/res/layout/browser_subwindow.xml +++ b/res/layout/browser_subwindow.xml @@ -23,6 +23,7 @@ android:layout_height="match_parent" android:padding="10dip" > <LinearLayout + android:id="@+id/inner_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" diff --git a/res/layout/custom_screen.xml b/res/layout/custom_screen.xml index 90dc324..525f30c 100644 --- a/res/layout/custom_screen.xml +++ b/res/layout/custom_screen.xml @@ -22,6 +22,7 @@ android:layout_height="match_parent" /> <LinearLayout android:orientation="vertical" + android:id="@+id/vertical_layout" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/res/layout/simple_dropdown_item_2line.xml b/res/layout/simple_dropdown_item_2line.xml new file mode 100644 index 0000000..8b955ec --- /dev/null +++ b/res/layout/simple_dropdown_item_2line.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml +** +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" + android:gravity="center_vertical" + android:baselineAligned="false"> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/icon1" + android:scaleType="center" + android:paddingLeft="2dip" + android:paddingRight="2dip" /> + <TwoLineListItem + android:paddingTop="2dip" + android:paddingBottom="2dip" + android:layout_width="0dip" + android:layout_weight="1" + android:layout_height="wrap_content" + android:mode="twoLine"> + <TextView + android:id="@android:id/text1" + style="?android:attr/dropDownItemStyle" + android:textAppearance="?android:attr/textAppearanceLargeInverse" + android:singleLine="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <TextView + android:id="@android:id/text2" + style="?android:attr/dropDownItemStyle" + android:textAppearance="?android:attr/textAppearanceSmallInverse" + android:textColor="#323232" + android:singleLine="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@android:id/text1" + android:layout_alignLeft="@android:id/text1" /> + </TwoLineListItem> +</LinearLayout> diff --git a/res/layout/tab_title.xml b/res/layout/tab_title.xml new file mode 100644 index 0000000..28b553d --- /dev/null +++ b/res/layout/tab_title.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- + Copyright 2010, The Android Open Source Project Licensed under the + Apache License, Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. See the License for + the specific language governing permissions and limitations under the + License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal"> + <ImageView + android:id="@+id/favicon" + android:layout_width="20dip" + android:layout_height="20dip" + android:layout_marginLeft="3dip" /> + <ImageView + android:id="@+id/lock" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="6dip" + android:visibility="gone" /> + <TextView + android:id="@+id/title" + android:layout_height="wrap_content" + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_marginLeft="3dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="@color/black" + android:gravity="center_vertical" + android:singleLine="true" + android:ellipsize="end" /> + <com.android.browser.CircularProgressView + android:id="@+id/stop" + android:layout_width="36dip" + android:layout_height="36dip" + android:background="@null" + android:src="@drawable/progress_stop" /> + <ImageView + android:id="@+id/close" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="6dip" + android:src="@drawable/btn_close_window" /> +</LinearLayout> diff --git a/res/layout/title_bar.xml b/res/layout/title_bar.xml index 9f0cb51..d6a77e0 100644 --- a/res/layout/title_bar.xml +++ b/res/layout/title_bar.xml @@ -90,7 +90,7 @@ android:layout_marginRight="-5dip" android:scaleType="center" android:background="@drawable/btn_bookmark" - android:src="@drawable/ic_btn_bookmarks" + android:src="@drawable/ic_list_bookmark" /> </LinearLayout> </LinearLayout> diff --git a/res/layout/title_bar_tabbed.xml b/res/layout/title_bar_tabbed.xml new file mode 100644 index 0000000..fc786e0 --- /dev/null +++ b/res/layout/title_bar_tabbed.xml @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- + Copyright 2010, The Android Open Source Project Licensed under the + Apache License, Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. See the License for + the specific language governing permissions and limitations under the + License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tabbedtitleport" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:background="#ffdddddd"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <com.android.browser.TabScrollView + android:id="@+id/tabs" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1.0" + android:orientation="horizontal" /> + <ImageButton + android:id="@+id/newtab" + android:src="@drawable/ic_menu_new_window" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="@drawable/browserbarbutton" /> + </LinearLayout> + <LinearLayout + android:id="@+id/urlbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingLeft="6dip" + android:paddingRight="6dip"> + <ImageButton + android:id="@+id/back" + android:src="@drawable/ic_arrow_left" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginRight="6dip" + android:background="@drawable/browserbarbutton" /> + <ImageButton + android:id="@+id/forward" + android:src="@drawable/ic_arrow_right" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginRight="6dip" + android:background="@drawable/browserbarbutton" /> + <ImageButton + android:id="@+id/star" + android:src="@drawable/ic_star" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginRight="6dip" + android:background="@drawable/browserbarbutton" /> + <com.android.browser.UrlInputView + android:id="@+id/editurl" + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_height="wrap_content" + android:layout_marginLeft="3dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="@color/black" + android:gravity="center_vertical" + android:singleLine="true" + android:ellipsize="end" + android:lines="1" + android:scrollHorizontally="true" + android:visibility="gone" + android:background="@drawable/textfield_nostroke" + android:inputType="textUri" + android:imeOptions="actionGo" /> + <ImageButton + android:id="@+id/menu" + android:src="@drawable/ic_menu" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginRight="6dip" + android:background="@drawable/browserbarbutton" /> + <ImageButton + android:id="@+id/all_btn" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:scaleType="center" + android:background="@drawable/browserbarbutton" + android:src="@drawable/ic_pages" /> + </LinearLayout> +</LinearLayout> diff --git a/res/menu-xlarge/browser.xml b/res/menu-xlarge/browser.xml new file mode 100644 index 0000000..0fc20be --- /dev/null +++ b/res/menu-xlarge/browser.xml @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <group android:id="@+id/MAIN_MENU"> + <item android:id="@+id/new_tab_menu_id" + android:title="@string/new_tab" + android:icon="@drawable/ic_menu_new_window" + android:alphabeticShortcut="n" /> + <item android:id="@+id/active_tabs_menu_id" + android:title="@string/active_tabs" + android:icon="@drawable/ic_menu_windows" + android:alphabeticShortcut="t" /> + <item android:id="@+id/find_menu_id" + android:title="@string/find_dot" + android:icon="@drawable/ic_menu_find" + android:alphabeticShortcut="f" /> + <item android:id="@+id/share_page_menu_id" + android:title="@string/share_page" + android:icon="@drawable/ic_menu_share" + android:alphabeticShortcut="s" /> + <item android:id="@+id/page_info_menu_id" + android:title="@string/page_info" + android:icon="@drawable/ic_menu_pageinfo" + android:alphabeticShortcut="g" /> + <item android:id="@+id/view_downloads_menu_id" + android:title="@string/menu_view_download" + android:icon="@drawable/ic_menu_downloads" + android:alphabeticShortcut="d" /> + <item android:id="@+id/preferences_menu_id" + android:title="@string/menu_preferences" + android:icon="@drawable/ic_menu_settings" + android:alphabeticShortcut="p" /> + <item android:id="@+id/save_webarchive_menu_id" + android:title="@string/menu_save_webarchive" /> + <!-- followings are debug only --> + <item android:id="@+id/dump_nav_menu_id" + android:title="@string/dump_nav" + android:visible="false" /> + <item android:id="@+id/dump_counters_menu_id" + android:title="@string/dump_counters" + android:visible="false" /> + </group> + <group android:id="@+id/MAIN_SHORTCUT_MENU" android:visible="false"> + <item android:id="@+id/homepage_menu_id" + android:alphabeticShortcut=" " /> + <item android:id="@+id/classic_history_menu_id" + android:alphabeticShortcut="h" /> + <item android:id="@+id/zoom_in_menu_id" + android:alphabeticShortcut="i" /> + <item android:id="@+id/zoom_out_menu_id" + android:alphabeticShortcut="o" /> + <item android:id="@+id/window_one_menu_id" + android:alphabeticShortcut="1" /> + <item android:id="@+id/window_two_menu_id" + android:alphabeticShortcut="2" /> + <item android:id="@+id/window_three_menu_id" + android:alphabeticShortcut="3" /> + <item android:id="@+id/window_four_menu_id" + android:alphabeticShortcut="4" /> + <item android:id="@+id/window_five_menu_id" + android:alphabeticShortcut="5" /> + <item android:id="@+id/window_six_menu_id" + android:alphabeticShortcut="6" /> + <item android:id="@+id/window_seven_menu_id" + android:alphabeticShortcut="7" /> + <item android:id="@+id/window_eight_menu_id" + android:alphabeticShortcut="8" /> + <item android:id="@+id/back_menu_id" + android:alphabeticShortcut="j" /> + <item android:id="@+id/forward_menu_id" + android:alphabeticShortcut="k" /> + <item android:id="@+id/bookmarks_menu_id" + android:alphabeticShortcut="b" /> + <item android:id="@+id/add_bookmark_menu_id" + android:alphabeticShortcut="a" /> + <item android:id="@+id/stop_reload_menu_id" + android:alphabeticShortcut="r" /> + <item android:id="@+id/goto_menu_id" + android:alphabeticShortcut="l" /> + <item android:id="@+id/close_menu_id" + android:alphabeticShortcut="w" /> + </group> +</menu> diff --git a/res/menu/browser.xml b/res/menu/browser.xml index 4793c21..adb1bad 100644 --- a/res/menu/browser.xml +++ b/res/menu/browser.xml @@ -41,22 +41,26 @@ android:alphabeticShortcut="a" /> <item android:id="@+id/find_menu_id" android:title="@string/find_dot" + android:icon="@drawable/ic_menu_find" android:alphabeticShortcut="f" /> - <item android:id="@+id/select_text_id" - android:title="@string/select_dot" - android:alphabeticShortcut="e" /> - <item android:id="@+id/page_info_menu_id" - android:title="@string/page_info" - android:alphabeticShortcut="g" /> <item android:id="@+id/share_page_menu_id" android:title="@string/share_page" + android:icon="@drawable/ic_menu_share" android:alphabeticShortcut="s" /> + <item android:id="@+id/page_info_menu_id" + android:title="@string/page_info" + android:icon="@drawable/ic_menu_pageinfo" + android:alphabeticShortcut="g" /> <item android:id="@+id/view_downloads_menu_id" android:title="@string/menu_view_download" + android:icon="@drawable/ic_menu_downloads" android:alphabeticShortcut="d" /> <item android:id="@+id/preferences_menu_id" android:title="@string/menu_preferences" + android:icon="@drawable/ic_menu_settings" android:alphabeticShortcut="p" /> + <item android:id="@+id/save_webarchive_menu_id" + android:title="@string/menu_save_webarchive" /> <!-- followings are debug only --> <item android:id="@+id/dump_nav_menu_id" android:title="@string/dump_nav" @@ -91,16 +95,10 @@ <item android:id="@+id/window_eight_menu_id" android:alphabeticShortcut="8" /> <item android:id="@+id/back_menu_id" - android:title="@string/back" - android:drawable="@*android:drawable/ic_menu_back" android:alphabeticShortcut="j" /> <item android:id="@+id/goto_menu_id" - android:title="@string/goto_dot" - android:alphabeticShortcut="l" - android:icon="@android:drawable/ic_menu_search"/> + android:alphabeticShortcut="l" /> <item android:id="@+id/close_menu_id" - android:icon="@drawable/ic_btn_close_panel" - android:title="@string/tab_picker_remove_tab" android:alphabeticShortcut="w" /> </group> <!-- these items are toggled in and out of @+id/stop_reload_menu_id --> diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index e9df9f4..7efb620 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -38,7 +38,7 @@ <item quantity="few" msgid="5544267486978946555">"<xliff:g id="NUMBER">%d</xliff:g> Treffer"</item> <item quantity="other" msgid="6616125067364315405">"<xliff:g id="NUMBER">%d</xliff:g> Treffer"</item> </plurals> - <string name="title_bar_loading" msgid="7438217780834640678">"Wird geladen..."</string> + <string name="title_bar_loading" msgid="7438217780834640678">"Ladevorgang läuft..."</string> <string name="page_info" msgid="4048529256302257195">"Seiteninfo"</string> <string name="page_info_view" msgid="5303490449842635158">"Seiteninfo anzeigen"</string> <string name="page_info_address" msgid="2222306609532903254">"Adresse:"</string> @@ -112,7 +112,7 @@ <string name="contextmenu_copylink" msgid="5153657160294534270">"Link-URL kopieren"</string> <string name="contextmenu_download_image" msgid="4243829645180686912">"Bild speichern"</string> <string name="contextmenu_view_image" msgid="3870625602053600905">"Bild anzeigen"</string> - <string name="contextmenu_set_wallpaper" msgid="3691902960115350686">"Als Hintergrund festlegen"</string> + <string name="contextmenu_set_wallpaper" msgid="3691902960115350686">"Als Hintergrundbild festlegen"</string> <string name="contextmenu_dial_dot" msgid="5856550683415933806">"Wählen..."</string> <string name="contextmenu_add_contact" msgid="3183511922223645716">"Kontakt hinzufügen"</string> <string name="contextmenu_send_mail" msgid="1014513374828775660">"E-Mail senden"</string> @@ -289,5 +289,5 @@ <string name="website_settings_clear_all_dialog_message" msgid="6150502090601476333">"Alle Websitedaten und Standortberechtigungen werden gelöscht."</string> <string name="website_settings_clear_all_dialog_ok_button" msgid="6401582240627669431">"Alle Daten löschen"</string> <string name="website_settings_clear_all_dialog_cancel_button" msgid="1896757051856611674">"Abbrechen"</string> - <string name="progress_dialog_setting_wallpaper" msgid="4871900779338536674">"Hintergrund wird eingestellt..."</string> + <string name="progress_dialog_setting_wallpaper" msgid="4871900779338536674">"Hintergrundbild wird eingestellt..."</string> </resources> diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 02b06b1..d69b315 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -22,7 +22,7 @@ <string name="active_tabs" msgid="3050623868203544623">"Windows"</string> <string name="tab_bookmarks" msgid="2305793036003473653">"Marcadores"</string> <string name="tab_most_visited" msgid="1077402532455000703">"Más visitados"</string> - <string name="tab_history" msgid="1979267558744613746">"Historial "</string> + <string name="tab_history" msgid="1979267558744613746">"Historial"</string> <string name="added_to_bookmarks" msgid="1020224130695956728">"Agregado a marcadores"</string> <string name="removed_from_bookmarks" msgid="6063705902028438800">"Suprimido de los marcadores"</string> <string name="sign_in_to" msgid="5939425800148759165">"Iniciar sesión en <xliff:g id="HOSTNAME">%s1</xliff:g> \"<xliff:g id="REALM">%s2</xliff:g>\""</string> @@ -226,17 +226,17 @@ <string name="popup_window_attempt" msgid="2673111696288657989">"Este sitio está intentando abrir una ventana emergente."</string> <string name="allow" msgid="1157313689171991335">"Permitir"</string> <string name="block" msgid="9172175889884707800">"Bloquear"</string> - <string name="too_many_windows_dialog_title" msgid="5175503564948906442">"Se alcanzó el límite de la ventana "</string> + <string name="too_many_windows_dialog_title" msgid="5175503564948906442">"Se alcanzó el límite de la ventana"</string> <string name="too_many_windows_dialog_message" msgid="1398571800233959583">"No se ha podido abrir una ventana nueva porque ya has abierto el máximo permitido."</string> <string name="too_many_subwindows_dialog_title" msgid="3805453941587725944">"Ventana emergente ya abierta"</string> <string name="too_many_subwindows_dialog_message" msgid="5827289829907966657">"No es posible abrir una ventana emergente nueva porque sólo puede abrirse una por vez."</string> <string name="download_title" msgid="2122874021047565594">"Historial de descarga"</string> <string name="download_unknown_filename" msgid="4013465542563652175">"<Desconocido>"</string> <string name="download_menu_open" msgid="4888327480367757513">"Abrir"</string> - <string name="download_menu_clear" msgid="6264454531553418124">"Borrar de la lista\n "</string> + <string name="download_menu_clear" msgid="6264454531553418124">"Borrar de la lista"</string> <string name="download_menu_delete" msgid="8815502136393894148">"Eliminar"</string> <string name="download_menu_cancel" msgid="2545333007601851574">"Cancelar descarga"</string> - <string name="download_menu_cancel_all" msgid="2136550823151999166">"Cancelar todas las descargas\n "</string> + <string name="download_menu_cancel_all" msgid="2136550823151999166">"Cancelar todas las descargas"</string> <string name="download_cancel_dlg_title" msgid="8909108500262799748">"Cancelar descargas"</string> <string name="download_cancel_dlg_msg" msgid="6285389170052357797">"Las <xliff:g id="DOWNLOAD_COUNT">%d</xliff:g> descargas se cancelarán y se borrarán del historial de descarga."</string> <string name="download_delete_file" msgid="5330036497843073249">"El archivo se eliminará"</string> diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 6e19b2e..b976eeb 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -62,7 +62,7 @@ <string name="expires_on" msgid="8061200430557020704">"Дата окончания действия:"</string> <string name="stopping" msgid="4839698519340302982">"Остановка..."</string> <string name="stop" msgid="5687251076030630074">"Стоп"</string> - <string name="reload" msgid="8585220783228408062">"Обновить"</string> + <string name="reload" msgid="8585220783228408062">"Обновление"</string> <string name="back" msgid="8414603107175713668">"Назад"</string> <string name="forward" msgid="4288210890526641577">"Вперед"</string> <string name="save" msgid="5922311934992468496">"ОК"</string> diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index bbb698c..b4dafb1 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -90,7 +90,7 @@ <string name="switch_to_thumbnails" msgid="5493351529609043151">"Miniatyrvy"</string> <string name="switch_to_list" msgid="8900531247982121055">"Listvy"</string> <string name="current_page" msgid="7510129573681663135">"från "</string> - <string name="delete_bookmark_warning" msgid="758043186202032205">"Bokmärket <xliff:g id="BOOKMARK">%s</xliff:g> tas bort. "</string> + <string name="delete_bookmark_warning" msgid="758043186202032205">"Bokmärket <xliff:g id="BOOKMARK">%s</xliff:g> tas bort."</string> <string name="open_in_new_window" msgid="6596775546468054510">"Öppna i nytt fönster"</string> <string name="goto_dot" msgid="3895839050522602723">"Kör"</string> <string name="find_dot" msgid="6259312434696611957">"Sök på sidan"</string> diff --git a/res/values/strings.xml b/res/values/strings.xml index 07c2eb9..0dcfa9e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -50,17 +50,16 @@ <!-- Label for a confirm button. Used in multiple contexts. --> <string name="ok">OK</string> - <!-- Displayed on the Find dialog to display the number of matches - found in the current page. --> + <!-- Displayed on the Find dialog when there are no matches --> + <string name="no_matches">No matches</string> + + <!-- Displayed on the Find dialog to display the index of the highlighted + match and total number of matches found in the current page. --> <plurals name="matches_found"> - <!-- Case of no matches --> - <item quantity="zero">No matches</item> <!-- Case of one match --> <item quantity="one">1 match</item> - <!-- Case of "few" (two) matches --> - <item quantity="few"><xliff:g id="number" example="2">%d</xliff:g> matches</item> - <!-- Case of several matches --> - <item quantity="other"><xliff:g id="number" example="137">%d</xliff:g> matches</item> + <!-- Case of multiple total matches --> + <item quantity="other"><xliff:g id="index" example="2">%d</xliff:g> of <xliff:g id="total" example="137">%d</xliff:g></item> </plurals> <!-- Displayed on the title bar while the page is loading --> @@ -138,12 +137,13 @@ <string name="name">Name</string> <!-- Initial value in Location field in Bookmark dialog box --> <string name="http">http://</string> - <!-- Menu item that opens a dialog to save a bookmark, initialized with the current page --> - <string name="save_to_bookmarks">Add bookmark</string> + <!-- Menu item that opens a dialog to save a bookmark for the current page, also displayed as + the title of the dialog used for adding a bookmark --> + <string name="save_to_bookmarks">Add to Bookmarks</string> <!-- Menu item on the bookmarks page, to edit an existing bookmark --> <string name="edit_bookmark">Edit bookmark</string> <!-- Context menu item to create a shortcut to the bookmark on the desktop --> - <string name="create_shortcut_bookmark">Add shortcut to Home</string> + <string name="create_shortcut_bookmark">Add to Home</string> <!-- Context menu item to open the currently highlighted bookmark --> <string name="open_bookmark">Open</string> <!-- Menu item to remove the currently highlighted bookmark--> @@ -217,6 +217,12 @@ <string name="copy_page_url">Copy page url</string> <!-- Menu item --> <string name="share_page">Share page</string> + <!-- Menu item for saving a page as a web archive. --> + <string name="menu_save_webarchive">Save as Web Archive</string> + <!-- Toast informing the user that the page has been saved. --> + <string name="webarchive_saved">Web archive saved.</string> + <!-- Toast informing the user that saving the page has failed. --> + <string name="webarchive_failed">Failed to save web archive.</string> <!-- Context Menu item open the currently selected link in the current window.--> <string name="contextmenu_openlink">Open</string> diff --git a/res/values/styles.xml b/res/values/styles.xml index 4779aa1..2e8510a 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -30,11 +30,6 @@ <item name="android:windowContentOverlay">@null</item> </style> - <style name="FindDialog"> - <item name="android:windowEnterAnimation">@anim/find_dialog_enter</item> - <item name="android:windowExitAnimation">@anim/find_dialog_exit</item> - </style> - <style name="TitleBar"> <item name="android:windowEnterAnimation">@anim/title_bar_enter</item> <item name="android:windowExitAnimation">@anim/title_bar_exit</item> diff --git a/src/com/android/browser/ActiveTabsPage.java b/src/com/android/browser/ActiveTabsPage.java index 2de7787..52828b3 100644 --- a/src/com/android/browser/ActiveTabsPage.java +++ b/src/com/android/browser/ActiveTabsPage.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.util.AttributeSet; +import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -32,6 +33,7 @@ import android.widget.ListView; import android.widget.TextView; public class ActiveTabsPage extends LinearLayout { + private static final String LOGTAG = "TabPicker"; private final BrowserActivity mBrowserActivity; private final LayoutInflater mFactory; private final TabControl mControl; @@ -152,7 +154,19 @@ public class ActiveTabsPage extends LinearLayout { (ImageView) convertView.findViewById(R.id.favicon); View close = convertView.findViewById(R.id.close); Tab tab = mControl.getTab(position); + if (tab.getWebView() == null) { + // This means that populatePickerData will have to use the + // saved state. + Log.w(LOGTAG, "Tab " + position + " has a null WebView and " + + (tab.getSavedState() == null ? "null" : "non-null") + + " saved state "); + } tab.populatePickerData(); + if (tab.getTitle() == null || tab.getTitle().length() == 0) { + Log.w(LOGTAG, "Tab " + position + " has no title. " + + "Check above in the Logs to see whether it has a " + + "null WebView or null WebHistoryItem"); + } title.setText(tab.getTitle()); url.setText(tab.getUrl()); Bitmap icon = tab.getFavicon(); diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java index 1104d5e..10c91f8 100644 --- a/src/com/android/browser/AddBookmarkPage.java +++ b/src/com/android/browser/AddBookmarkPage.java @@ -51,6 +51,7 @@ public class AddBookmarkPage extends Activity { private String mTouchIconUrl; private Bitmap mThumbnail; private String mOriginalUrl; + private boolean mIsUrlEditable = true; // Message IDs private static final int SAVE_BOOKMARK = 100; @@ -74,13 +75,24 @@ public class AddBookmarkPage extends Activity { protected void onCreate(Bundle icicle) { super.onCreate(icicle); requestWindowFeature(Window.FEATURE_LEFT_ICON); - setContentView(R.layout.browser_add_bookmark); + + mMap = getIntent().getExtras(); + if (mMap != null) { + mIsUrlEditable = mMap.getBoolean("url_editable", true); + } + + if (mIsUrlEditable) { + setContentView(R.layout.browser_add_bookmark); + } else { + setContentView(R.layout.browser_add_bookmark_const_url); + } + setTitle(R.string.save_to_bookmarks); getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_list_bookmark); String title = null; String url = null; - mMap = getIntent().getExtras(); + if (mMap != null) { Bundle b = mMap.getBundle("bookmark"); if (b != null) { @@ -96,8 +108,11 @@ public class AddBookmarkPage extends Activity { mTitle = (EditText) findViewById(R.id.title); mTitle.setText(title); - mAddress = (EditText) findViewById(R.id.address); - mAddress.setText(url); + + if (mIsUrlEditable) { + mAddress = (EditText) findViewById(R.id.address); + mAddress.setText(url); + } View.OnClickListener accept = mSaveBookmark; mButton = (TextView) findViewById(R.id.OK); @@ -135,7 +150,7 @@ public class AddBookmarkPage extends Activity { final ContentResolver cr = getContentResolver(); Bookmarks.addBookmark(null, cr, url, title, thumbnail, true); if (touchIconUrl != null) { - new DownloadTouchIcon(cr, url).execute(mTouchIconUrl); + new DownloadTouchIcon(AddBookmarkPage.this, cr, url).execute(mTouchIconUrl); } mMessage.arg1 = 1; } catch (IllegalStateException e) { @@ -173,8 +188,14 @@ public class AddBookmarkPage extends Activity { createHandler(); String title = mTitle.getText().toString().trim(); - String unfilteredUrl = - BrowserActivity.fixUrl(mAddress.getText().toString()); + String unfilteredUrl; + if (mIsUrlEditable) { + unfilteredUrl = + BrowserActivity.fixUrl(mAddress.getText().toString()); + } else { + unfilteredUrl = mOriginalUrl; + } + boolean emptyTitle = title.length() == 0; boolean emptyUrl = unfilteredUrl.trim().length() == 0; Resources r = getResources(); @@ -183,9 +204,15 @@ public class AddBookmarkPage extends Activity { mTitle.setError(r.getText(R.string.bookmark_needs_title)); } if (emptyUrl) { - mAddress.setError(r.getText(R.string.bookmark_needs_url)); + if (mIsUrlEditable) { + mAddress.setError(r.getText(R.string.bookmark_needs_url)); + } else { + Toast.makeText(AddBookmarkPage.this, R.string.bookmark_needs_url, + Toast.LENGTH_LONG).show(); + } + return false; } - return false; + } String url = unfilteredUrl.trim(); try { @@ -200,7 +227,12 @@ public class AddBookmarkPage extends Activity { // can't save their bookmark. If it was null, we'll assume // they meant http when we parse it in the WebAddress class. if (scheme != null) { - mAddress.setError(r.getText(R.string.bookmark_cannot_save_url)); + if (mIsUrlEditable) { + mAddress.setError(r.getText(R.string.bookmark_cannot_save_url)); + } else { + Toast.makeText(AddBookmarkPage.this, R.string.bookmark_cannot_save_url, + Toast.LENGTH_LONG).show(); + } return false; } WebAddress address; @@ -216,7 +248,12 @@ public class AddBookmarkPage extends Activity { } } } catch (URISyntaxException e) { - mAddress.setError(r.getText(R.string.bookmark_url_not_valid)); + if (mIsUrlEditable) { + mAddress.setError(r.getText(R.string.bookmark_url_not_valid)); + } else { + Toast.makeText(AddBookmarkPage.this, R.string.bookmark_url_not_valid, + Toast.LENGTH_LONG).show(); + } return false; } diff --git a/src/com/android/browser/BookmarkUtils.java b/src/com/android/browser/BookmarkUtils.java new file mode 100644 index 0000000..0fdad15 --- /dev/null +++ b/src/com/android/browser/BookmarkUtils.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.net.Uri; +import android.provider.Browser; +import android.util.Log; + +class BookmarkUtils { + private final static String LOGTAG = "BookmarkUtils"; + + // XXX: There is no public string defining this intent so if Home changes the value, we + // have to update this string. + private static final String INSTALL_SHORTCUT = "com.android.launcher.action.INSTALL_SHORTCUT"; + + enum BookmarkIconType { + ICON_INSTALLABLE_WEB_APP, // Icon for an installable web app (launches WebAppRuntime). + ICON_HOME_SHORTCUT // Icon for a shortcut on the home screen (launches Browser). + }; + + /** + * Creates an icon to be associated with this bookmark. If available, the apple touch icon + * will be used, else we draw our own depending on the type of "bookmark" being created. + */ + static Bitmap createIcon(Context context, Bitmap touchIcon, Bitmap favicon, + BookmarkIconType type) { + int iconDimension = context.getResources().getDimensionPixelSize( + android.R.dimen.app_icon_size); + + Bitmap bm = Bitmap.createBitmap(iconDimension, iconDimension, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bm); + Rect iconBounds = new Rect(0, 0, bm.getWidth(), bm.getHeight()); + + // Use the apple-touch-icon if available + if (touchIcon != null) { + drawTouchIconToCanvas(touchIcon, canvas, iconBounds); + } else { + // No touch icon so create our own. + // Set the background based on the type of shortcut (either webapp or home shortcut). + Bitmap icon = getIconBackground(context, type); + + if (icon != null) { + // Now draw the correct icon background into our new bitmap. + canvas.drawBitmap(icon, null, iconBounds, null); + } + + // If we have a favicon, overlay it in a nice rounded white box on top of the + // background. + if (favicon != null) { + drawFaviconToCanvas(favicon, canvas, iconBounds, + context.getResources().getDisplayMetrics().density); + } + } + return bm; + } + + /** + * Convenience method for creating an intent that will add a shortcut to the home screen. + */ + static Intent createAddToHomeIntent(Context context, String url, String title, + Bitmap touchIcon, Bitmap favicon) { + Intent i = new Intent(INSTALL_SHORTCUT); + Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + long urlHash = url.hashCode(); + long uniqueId = (urlHash << 32) | shortcutIntent.hashCode(); + shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID, Long.toString(uniqueId)); + i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title); + i.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(context, touchIcon, favicon, + BookmarkIconType.ICON_HOME_SHORTCUT)); + + // Do not allow duplicate items + i.putExtra("duplicate", false); + return i; + } + + private static Bitmap getIconBackground(Context context, BookmarkIconType type) { + if (type == BookmarkIconType.ICON_HOME_SHORTCUT) { + // Want to create a shortcut icon on the homescreen, so the icon + // background is the red bookmark. + return BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_launcher_shortcut_browser_bookmark); + } else if (type == BookmarkIconType.ICON_INSTALLABLE_WEB_APP) { + // Use the web browser icon as the background for the icon for an installable + // web app. + return BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_launcher_browser); + } + return null; + } + + private static void drawTouchIconToCanvas(Bitmap touchIcon, Canvas canvas, Rect iconBounds) { + Rect src = new Rect(0, 0, touchIcon.getWidth(), touchIcon.getHeight()); + + // Paint used for scaling the bitmap and drawing the rounded rect. + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setFilterBitmap(true); + canvas.drawBitmap(touchIcon, src, iconBounds, paint); + + // Construct a path from a round rect. This will allow drawing with + // an inverse fill so we can punch a hole using the round rect. + Path path = new Path(); + path.setFillType(Path.FillType.INVERSE_WINDING); + RectF rect = new RectF(iconBounds); + rect.inset(1, 1); + path.addRoundRect(rect, 8f, 8f, Path.Direction.CW); + + // Reuse the paint and clear the outside of the rectangle. + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + canvas.drawPath(path, paint); + } + + private static void drawFaviconToCanvas(Bitmap favicon, Canvas canvas, Rect iconBounds, + float density) { + // Make a Paint for the white background rectangle and for + // filtering the favicon. + Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + p.setStyle(Paint.Style.FILL_AND_STROKE); + p.setColor(Color.WHITE); + + // Create a rectangle that is slightly wider than the favicon + final float iconSize = 16 * density; // 16x16 favicon + final float padding = 2 * density; // white padding around icon + final float rectSize = iconSize + 2 * padding; + final float x = iconBounds.exactCenterX() - (rectSize / 2); + // Note: Subtract 2 dip from the y position since the box is + // slightly higher than center. Use padding since it is already + // 2 * density. + final float y = iconBounds.exactCenterY() - (rectSize / 2) - padding; + RectF r = new RectF(x, y, x + rectSize, y + rectSize); + + // Draw a white rounded rectangle behind the favicon + canvas.drawRoundRect(r, 2, 2, p); + + // Draw the favicon in the same rectangle as the rounded + // rectangle but inset by the padding + // (results in a 16x16 favicon). + r.inset(padding, padding); + canvas.drawBitmap(favicon, null, r, p); + } + +}; diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java index 5e55789..8afd3b4 100644 --- a/src/com/android/browser/BrowserActivity.java +++ b/src/com/android/browser/BrowserActivity.java @@ -38,13 +38,11 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; -import android.database.DatabaseUtils; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Picture; import android.graphics.PixelFormat; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -64,14 +62,13 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.provider.Browser; import android.provider.ContactsContract; -import android.provider.ContactsContract.Intents.Insert; import android.provider.Downloads; import android.provider.MediaStore; +import android.provider.ContactsContract.Intents.Insert; import android.speech.RecognizerResultsIntent; import android.text.IClipboard; import android.text.TextUtils; import android.text.format.DateFormat; -import android.util.AttributeSet; import android.util.Log; import android.util.Patterns; import android.view.ContextMenu; @@ -87,6 +84,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuItem.OnMenuItemClickListener; +import android.view.accessibility.AccessibilityManager; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.DownloadListener; @@ -104,12 +102,6 @@ import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountManagerFuture; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; -import android.accounts.AccountManagerCallback; import com.android.common.Search; import com.android.common.speech.LoggingEvents; @@ -119,11 +111,9 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; -import java.text.ParseException; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -131,6 +121,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -171,6 +162,8 @@ public class BrowserActivity extends Activity */ private FrameLayout mBrowserFrameLayout; + private boolean mXLargeScreenSize; + @Override public void onCreate(Bundle icicle) { if (LOGV_ENABLED) { @@ -186,7 +179,11 @@ public class BrowserActivity extends Activity BitmapFactory.setDefaultConfig(Bitmap.Config.ARGB_8888); } - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + if (AccessibilityManager.getInstance(this).isEnabled()) { + setDefaultKeyMode(DEFAULT_KEYS_DISABLE); + } else { + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + } mResolver = getContentResolver(); @@ -213,14 +210,31 @@ public class BrowserActivity extends Activity mCustomViewContainer = (FrameLayout) mBrowserFrameLayout .findViewById(R.id.fullscreen_custom_content); frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS); - mTitleBar = new TitleBar(this); - // mTitleBar will be always shown in the fully loaded mode - mTitleBar.setProgress(100); - mFakeTitleBar = new TitleBar(this); + mXLargeScreenSize = (getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) + == Configuration.SCREENLAYOUT_SIZE_XLARGE; // Create the tab control and our initial tab mTabControl = new TabControl(this); + + if (mXLargeScreenSize) { + mTitleBar = new TitleBarXLarge(this, mTabControl); + LinearLayout layout = (LinearLayout) mBrowserFrameLayout. + findViewById(R.id.vertical_layout); + layout.addView(mTitleBar, 0, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + } else { + mTitleBar = new TitleBar(this); + // mTitleBar will be always be shown in the fully loaded mode on + // phone + mTitleBar.setProgress(100); + // Fake title bar is not needed in xlarge layout + mFakeTitleBar = new TitleBar(this); + } + + // Open the icon database and retain all the bookmark urls for favicons retainIconsOnStartup(); @@ -305,9 +319,7 @@ public class BrowserActivity extends Activity } if (permissionOk) { PluginManager.getInstance(BrowserActivity.this) - .refreshPlugins( - Intent.ACTION_PACKAGE_ADDED - .equals(action)); + .refreshPlugins(true); } } } @@ -358,6 +370,15 @@ public class BrowserActivity extends Activity attachTabToContentView(mTabControl.getCurrentTab()); } + // Delete old thumbnails to save space + File dir = mTabControl.getThumbnailDir(); + if (dir.exists()) { + for (String child : dir.list()) { + File f = new File(dir, child); + f.delete(); + } + } + // Read JavaScript flags if it exists. String jsFlags = mSettings.getJsFlags(); if (jsFlags.trim().length() != 0) { @@ -612,6 +633,7 @@ public class BrowserActivity extends Activity final ContentResolver cr = mResolver; final String newUrl = url; new AsyncTask<Void, Void, Void>() { + @Override protected Void doInBackground(Void... unused) { Browser.updateVisitedHistory(cr, newUrl, false); Browser.addSearchUrl(cr, newUrl); @@ -673,6 +695,7 @@ public class BrowserActivity extends Activity final ContentResolver cr = mResolver; final String newUrl = url; new AsyncTask<Void, Void, Void>() { + @Override protected Void doInBackground(Void... unused) { Browser.updateVisitedHistory(cr, newUrl, false); return null; @@ -697,17 +720,21 @@ public class BrowserActivity extends Activity } /* package */ void showVoiceTitleBar(String title) { mTitleBar.setInVoiceMode(true); - mFakeTitleBar.setInVoiceMode(true); - mTitleBar.setDisplayTitle(title); - mFakeTitleBar.setDisplayTitle(title); + + if (!mXLargeScreenSize) { + mFakeTitleBar.setInVoiceMode(true); + mFakeTitleBar.setDisplayTitle(title); + } } /* package */ void revertVoiceTitleBar() { mTitleBar.setInVoiceMode(false); - mFakeTitleBar.setInVoiceMode(false); - mTitleBar.setDisplayTitle(mUrl); - mFakeTitleBar.setDisplayTitle(mUrl); + + if (!mXLargeScreenSize) { + mFakeTitleBar.setInVoiceMode(false); + mFakeTitleBar.setDisplayTitle(mUrl); + } } /* package */ static String fixUrl(String inUrl) { // FIXME: Converting the url to lower case @@ -826,7 +853,14 @@ public class BrowserActivity extends Activity return true; } + private void rebuildTitleBar() { + if (mXLargeScreenSize) { + ((TitleBarXLarge) mTitleBar).rebuildLayout(); + } + } + private void showFakeTitleBar() { + if (mXLargeScreenSize) return; if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null && !mActivityInPause) { WebView mainView = mTabControl.getCurrentWebView(); @@ -834,6 +868,13 @@ public class BrowserActivity extends Activity if (mainView == null) { return; } + // Do not need to check for null, since the current tab will have + // at least a main WebView, or we would have returned above. + if (dialogIsUp()) { + // Do not show the fake title bar, which would cover up the + // find or select dialog. + return; + } WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); @@ -868,7 +909,7 @@ public class BrowserActivity extends Activity } private void hideFakeTitleBar() { - if (mFakeTitleBar.getParent() == null) return; + if (mXLargeScreenSize || mFakeTitleBar.getParent() == null) return; WindowManager.LayoutParams params = (WindowManager.LayoutParams) mFakeTitleBar.getLayoutParams(); WebView mainView = mTabControl.getCurrentWebView(); @@ -1027,6 +1068,7 @@ public class BrowserActivity extends Activity showHttpAuthentication(mHttpAuthHandler, null, null, title, name, password, focusId); } + rebuildTitleBar(); } @Override @@ -1113,12 +1155,14 @@ public class BrowserActivity extends Activity if (mMenu == null) { return; } + MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id); MenuItem src = mInLoad ? mMenu.findItem(R.id.stop_menu_id): - mMenu.findItem(R.id.reload_menu_id); - MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id); - dest.setIcon(src.getIcon()); - dest.setTitle(src.getTitle()); + mMenu.findItem(R.id.reload_menu_id); + if (src != null) { + dest.setIcon(src.getIcon()); + dest.setTitle(src.getTitle()); + } } @Override @@ -1145,7 +1189,6 @@ public class BrowserActivity extends Activity break; // -- Browser context menu case R.id.open_context_menu_id: - case R.id.open_newtab_context_menu_id: case R.id.bookmark_context_menu_id: case R.id.save_link_context_menu_id: case R.id.share_link_context_menu_id: @@ -1263,6 +1306,7 @@ public class BrowserActivity extends Activity */ /* package */ void removeActiveTabPage(boolean needToAttach) { mContentView.removeView(mActiveTabsPage); + mTitleBar.setVisibility(View.VISIBLE); mActiveTabsPage = null; mMenuState = R.id.MAIN_MENU; if (needToAttach) { @@ -1271,6 +1315,22 @@ public class BrowserActivity extends Activity getTopWindow().requestFocus(); } + private WebView showDialog(WebDialog dialog) { + // Need to do something special for Tablet + Tab tab = mTabControl.getCurrentTab(); + if (tab.getSubWebView() == null) { + // If the find or select is being performed on the main webview, + // remove the embedded title bar. + WebView mainView = tab.getWebView(); + if (mainView != null) { + mainView.setEmbeddedTitleBar(null); + } + } + hideFakeTitleBar(); + mMenuState = EMPTY_MENU; + return tab.showDialog(dialog); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if (!mCanChord) { @@ -1305,6 +1365,7 @@ public class BrowserActivity extends Activity case R.id.active_tabs_menu_id: mActiveTabsPage = new ActiveTabsPage(this, mTabControl); removeTabFromContentView(mTabControl.getCurrentTab()); + mTitleBar.setVisibility(View.GONE); hideFakeTitleBar(); mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS); mActiveTabsPage.requestFocus(); @@ -1312,14 +1373,7 @@ public class BrowserActivity extends Activity break; case R.id.add_bookmark_menu_id: - Intent i = new Intent(BrowserActivity.this, - AddBookmarkPage.class); - WebView w = getTopWindow(); - i.putExtra("url", w.getUrl()); - i.putExtra("title", w.getTitle()); - i.putExtra("touch_icon_url", w.getTouchIconUrl()); - i.putExtra("thumbnail", createScreenshot(w)); - startActivity(i); + bookmarkCurrentPage(); break; case R.id.stop_reload_menu_id: @@ -1364,18 +1418,26 @@ public class BrowserActivity extends Activity break; case R.id.find_menu_id: - if (null == mFindDialog) { - mFindDialog = new FindDialog(this); - } - mFindDialog.setWebView(getTopWindow()); - mFindDialog.show(); - getTopWindow().setFindIsUp(true); - mMenuState = EMPTY_MENU; + showFindDialog(); break; - case R.id.select_text_id: - getTopWindow().emulateShiftHeld(); + case R.id.save_webarchive_menu_id: + if (LOGD_ENABLED) { + Log.d(LOGTAG, "Save as Web Archive"); + } + String directory = getExternalFilesDir(null).getAbsolutePath() + File.separator; + getTopWindow().saveWebArchive(directory, true, new ValueCallback<String>() { + @Override + public void onReceiveValue(String value) { + if (value != null) { + Toast.makeText(BrowserActivity.this, R.string.webarchive_saved, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(BrowserActivity.this, R.string.webarchive_failed, Toast.LENGTH_SHORT).show(); + } + } + }); break; + case R.id.page_info_menu_id: showPageInfo(mTabControl.getCurrentTab(), false); break; @@ -1394,7 +1456,8 @@ public class BrowserActivity extends Activity currentTab.populatePickerData(); sharePage(this, currentTab.getTitle(), currentTab.getUrl(), currentTab.getFavicon(), - createScreenshot(currentTab.getWebView())); + createScreenshot(currentTab.getWebView(), getDesiredThumbnailWidth(this), + getDesiredThumbnailHeight(this))); break; case R.id.dump_nav_menu_id: @@ -1450,8 +1513,74 @@ public class BrowserActivity extends Activity return true; } - public void closeFind() { + /* package */ void bookmarkCurrentPage() { + Intent i = new Intent(BrowserActivity.this, + AddBookmarkPage.class); + WebView w = getTopWindow(); + i.putExtra("url", w.getUrl()); + i.putExtra("title", w.getTitle()); + i.putExtra("touch_icon_url", w.getTouchIconUrl()); + i.putExtra("thumbnail", createScreenshot(w, getDesiredThumbnailWidth(this), + getDesiredThumbnailHeight(this))); + i.putExtra("url_editable", false); + startActivity(i); + } + + private boolean dialogIsUp() { + return null != mFindDialog && mFindDialog.isVisible() || + null != mSelectDialog && mSelectDialog.isVisible(); + } + + private boolean closeDialog(WebDialog dialog) { + if (null == dialog || !dialog.isVisible()) return false; + Tab currentTab = mTabControl.getCurrentTab(); + currentTab.closeDialog(dialog); + dialog.dismiss(); + return true; + } + + /* + * Remove the find dialog or select dialog. + */ + public void closeDialogs() { + if (!(closeDialog(mFindDialog) || closeDialog(mSelectDialog))) return; + if (!mXLargeScreenSize) { + // If the Find was being performed in the main WebView, replace the + // embedded title bar. + Tab currentTab = mTabControl.getCurrentTab(); + if (currentTab.getSubWebView() == null) { + WebView mainView = currentTab.getWebView(); + if (mainView != null) { + mainView.setEmbeddedTitleBar(mTitleBar); + } + } + } mMenuState = R.id.MAIN_MENU; + if (mInLoad) { + // The title bar was hidden, because otherwise it would cover up the + // find or select dialog. Now that the dialog has been removed, + // show the fake title bar once again. + showFakeTitleBar(); + } + } + + public void showFindDialog() { + if (null == mFindDialog) { + mFindDialog = new FindDialog(this); + } + showDialog(mFindDialog).setFindIsUp(true); + } + + public void setFindDialogText(String text) { + mFindDialog.setText(text); + } + + public void showSelectDialog() { + if (null == mSelectDialog) { + mSelectDialog = new SelectDialog(this); + } + showDialog(mSelectDialog).setUpSelect(); + mSelectDialog.hideSoftInput(); } @Override @@ -1492,11 +1621,11 @@ public class BrowserActivity extends Activity final MenuItem home = menu.findItem(R.id.homepage_menu_id); home.setEnabled(!isHome); - menu.findItem(R.id.forward_menu_id) - .setEnabled(canGoForward); + final MenuItem forward = menu.findItem(R.id.forward_menu_id); + forward.setEnabled(canGoForward); - menu.findItem(R.id.new_tab_menu_id).setEnabled( - mTabControl.canCreateNewTab()); + final MenuItem newtab = menu.findItem(R.id.new_tab_menu_id); + newtab.setEnabled(mTabControl.canCreateNewTab()); // decide whether to show the share link option PackageManager pm = getPackageManager(); @@ -1524,7 +1653,7 @@ public class BrowserActivity extends Activity @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - if (v instanceof TitleBar) { + if (v instanceof TitleBarBase) { return; } WebView webview = (WebView) v; @@ -1551,7 +1680,7 @@ public class BrowserActivity extends Activity inflater.inflate(R.menu.browsercontext, menu); // Show the correct menu group - String extra = result.getExtra(); + final String extra = result.getExtra(); menu.setGroupVisible(R.id.PHONE_MENU, type == WebView.HitTestResult.PHONE_TYPE); menu.setGroupVisible(R.id.EMAIL_MENU, @@ -1608,8 +1737,23 @@ public class BrowserActivity extends Activity titleView.setText(extra); menu.setHeaderView(titleView); // decide whether to show the open link in new tab option - menu.findItem(R.id.open_newtab_context_menu_id).setVisible( - mTabControl.canCreateNewTab()); + boolean showNewTab = mTabControl.canCreateNewTab(); + MenuItem newTabItem + = menu.findItem(R.id.open_newtab_context_menu_id); + newTabItem.setVisible(showNewTab); + if (showNewTab) { + newTabItem.setOnMenuItemClickListener( + new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final Tab parent = mTabControl.getCurrentTab(); + final Tab newTab = openTab(extra); + if (newTab != parent) { + parent.addChildTab(newTab); + } + return true; + } + }); + } menu.findItem(R.id.bookmark_context_menu_id).setVisible( Bookmarks.urlHasAcceptableScheme(extra)); PackageManager pm = getPackageManager(); @@ -1660,8 +1804,10 @@ public class BrowserActivity extends Activity ViewGroup.LayoutParams.WRAP_CONTENT)); } - WebView view = t.getWebView(); - view.setEmbeddedTitleBar(mTitleBar); + if (!mXLargeScreenSize){ + WebView view = t.getWebView(); + view.setEmbeddedTitleBar(mTitleBar); + } if (t.isInVoiceSearchMode()) { showVoiceTitleBar(t.getVoiceDisplayTitle()); } else { @@ -1687,9 +1833,11 @@ public class BrowserActivity extends Activity mErrorConsoleContainer.removeView(errorConsole); } - WebView view = t.getWebView(); - if (view != null) { - view.setEmbeddedTitleBar(null); + if (!mXLargeScreenSize) { + WebView view = t.getWebView(); + if (view != null) { + view.setEmbeddedTitleBar(null); + } } } @@ -1815,6 +1963,7 @@ public class BrowserActivity extends Activity return true; } + @Override public void run() { Drawable oldWallpaper = BrowserActivity.this.getWallpaper(); try { @@ -1919,7 +2068,9 @@ public class BrowserActivity extends Activity // If we are in voice search mode, the title has already been set. if (mTabControl.getCurrentTab().isInVoiceSearchMode()) return; mTitleBar.setDisplayTitle(url); - mFakeTitleBar.setDisplayTitle(url); + if (!mXLargeScreenSize) { + mFakeTitleBar.setDisplayTitle(url); + } } /** @@ -1962,7 +2113,9 @@ public class BrowserActivity extends Activity // Set the favicon in the title bar. void setFavicon(Bitmap icon) { mTitleBar.setFavicon(icon); - mFakeTitleBar.setFavicon(icon); + if (!mXLargeScreenSize) { + mFakeTitleBar.setFavicon(icon); + } } /** @@ -1978,6 +2131,7 @@ public class BrowserActivity extends Activity } mTabControl.setCurrentTab(mTabControl.getTab(currentIndex)); resetTitleIconAndProgress(); + updateLockIconToLatest(); } /* package */ void goBackOnePageOrQuit() { @@ -2169,9 +2323,12 @@ public class BrowserActivity extends Activity static final int UPDATE_BOOKMARK_THUMBNAIL = 108; + private static final int TOUCH_ICON_DOWNLOADED = 109; + // Private handler for handling javascript and saving passwords private Handler mHandler = new Handler() { + @Override public void handleMessage(Message msg) { switch (msg.what) { case FOCUS_NODE_HREF: @@ -2192,13 +2349,6 @@ public class BrowserActivity extends Activity case R.id.view_image_context_menu_id: loadUrlFromContext(getTopWindow(), url); break; - case R.id.open_newtab_context_menu_id: - final Tab parent = mTabControl.getCurrentTab(); - final Tab newTab = openTab(url); - if (newTab != parent) { - parent.addChildTab(newTab); - } - break; case R.id.bookmark_context_menu_id: Intent intent = new Intent(BrowserActivity.this, AddBookmarkPage.class); @@ -2207,41 +2357,8 @@ public class BrowserActivity extends Activity startActivity(intent); break; case R.id.share_link_context_menu_id: - // See if this site has been visited before - StringBuilder sb = new StringBuilder( - Browser.BookmarkColumns.URL + " = "); - DatabaseUtils.appendEscapedSQLString(sb, url); - Cursor c = mResolver.query(Browser.BOOKMARKS_URI, - Browser.HISTORY_PROJECTION, - sb.toString(), - null, + sharePage(BrowserActivity.this, title, url, null, null); - if (c.moveToFirst()) { - // The site has been visited before, so grab the - // info from the database. - Bitmap favicon = null; - Bitmap thumbnail = null; - String linkTitle = c.getString(Browser. - HISTORY_PROJECTION_TITLE_INDEX); - byte[] data = c.getBlob(Browser. - HISTORY_PROJECTION_FAVICON_INDEX); - if (data != null) { - favicon = BitmapFactory.decodeByteArray( - data, 0, data.length); - } - data = c.getBlob(Browser. - HISTORY_PROJECTION_THUMBNAIL_INDEX); - if (data != null) { - thumbnail = BitmapFactory.decodeByteArray( - data, 0, data.length); - } - sharePage(BrowserActivity.this, - linkTitle, url, favicon, thumbnail); - } else { - Browser.sendString(BrowserActivity.this, url, - getString( - R.string.choosertitle_sharevia)); - } break; case R.id.copy_link_context_menu_id: copy(url); @@ -2278,6 +2395,14 @@ public class BrowserActivity extends Activity updateScreenshot(view); } break; + + case TOUCH_ICON_DOWNLOADED: + Bundle b = msg.getData(); + showSaveToHomescreenDialog(b.getString("url"), + b.getString("title"), + (Bitmap) b.getParcelable("touchIcon"), + (Bitmap) b.getParcelable("favicon")); + break; } } }; @@ -2318,7 +2443,8 @@ public class BrowserActivity extends Activity // draw, but the API for that (WebViewCore.pictureReady()) is not // currently accessible here. - final Bitmap bm = createScreenshot(view); + final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(this), + getDesiredThumbnailHeight(this)); if (bm == null) { return; } @@ -2394,13 +2520,12 @@ public class BrowserActivity extends Activity return THUMBNAIL_HEIGHT; } - private Bitmap createScreenshot(WebView view) { + private Bitmap createScreenshot(WebView view, int width, int height) { Picture thumbnail = view.capturePicture(); if (thumbnail == null) { return null; } - Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this), - getDesiredThumbnailHeight(this), Bitmap.Config.RGB_565); + Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bm); // May need to tweak these values to determine what is the // best scale factor @@ -2409,8 +2534,7 @@ public class BrowserActivity extends Activity float scaleFactorX = 1.0f; float scaleFactorY = 1.0f; if (thumbnailWidth > 0) { - scaleFactorX = (float) getDesiredThumbnailWidth(this) / - (float)thumbnailWidth; + scaleFactorX = (float) width / (float)thumbnailWidth; } else { return null; } @@ -2420,8 +2544,7 @@ public class BrowserActivity extends Activity // If the device is in landscape and the page is shorter // than the height of the view, stretch the thumbnail to fill the // space. - scaleFactorY = (float) getDesiredThumbnailHeight(this) / - (float)thumbnailHeight; + scaleFactorY = (float) height / (float)thumbnailHeight; } else { // In the portrait case, this looks nice. scaleFactorY = scaleFactorX; @@ -2462,7 +2585,7 @@ public class BrowserActivity extends Activity onProgressChanged(view, INITIAL_PROGRESS); mDidStopLoad = false; if (!mIsNetworkUp) createAndShowNetworkDialog(); - + closeDialogs(); if (mSettings.isTracing()) { String host; try { @@ -2560,6 +2683,18 @@ public class BrowserActivity extends Activity } } + private void closeEmptyChildTab() { + Tab current = mTabControl.getCurrentTab(); + if (current != null + && current.getWebView().copyBackForwardList().getSize() == 0) { + Tab parent = current.getParentTab(); + if (parent != null) { + switchToTab(mTabControl.getTabIndex(parent)); + closeTab(current); + } + } + } + boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith(SCHEME_WTAI)) { // wtai://wp/mc;number @@ -2569,6 +2704,11 @@ public class BrowserActivity extends Activity Uri.parse(WebView.SCHEME_TEL + url.substring(SCHEME_WTAI_MC.length()))); startActivity(intent); + // before leaving BrowserActivity, close the empty child tab. + // If a new tab is created through JavaScript open to load this + // url, we would like to close it as we will load this url in a + // different Activity. + closeEmptyChildTab(); return true; } // wtai://wp/sd;dtmf @@ -2610,6 +2750,11 @@ public class BrowserActivity extends Activity .parse("market://search?q=pname:" + packagename)); intent.addCategory(Intent.CATEGORY_BROWSABLE); startActivity(intent); + // before leaving BrowserActivity, close the empty child tab. + // If a new tab is created through JavaScript open to load this + // url, we would like to close it as we will load this url in a + // different Activity. + closeEmptyChildTab(); return true; } else { return false; @@ -2622,6 +2767,11 @@ public class BrowserActivity extends Activity intent.setComponent(null); try { if (startActivityIfNeeded(intent, -1)) { + // before leaving BrowserActivity, close the empty child tab. + // If a new tab is created through JavaScript open to load this + // url, we would like to close it as we will load this url in a + // different Activity. + closeEmptyChildTab(); return true; } } catch (ActivityNotFoundException ex) { @@ -2642,7 +2792,14 @@ public class BrowserActivity extends Activity // ------------------------------------------------------------------------- void onProgressChanged(WebView view, int newProgress) { - mFakeTitleBar.setProgress(newProgress); + if (mXLargeScreenSize) { + mTitleBar.setProgress(newProgress); + } else { + // On the phone, the fake title bar will always cover up the + // regular title bar (or the regular one is offscreen), so only the + // fake title bar needs to change its progress + mFakeTitleBar.setProgress(newProgress); + } if (newProgress == 100) { // onProgressChanged() may continue to be called after the main @@ -2743,15 +2900,138 @@ public class BrowserActivity extends Activity * The Object used to inform the WebView of the file to upload. */ private ValueCallback<Uri> mUploadMessage; + private String mCameraFilePath; + + void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { + + final String imageMimeType = "image/*"; + final String videoMimeType = "video/*"; + final String audioMimeType = "audio/*"; + final String mediaSourceKey = "capture"; + final String mediaSourceValueCamera = "camera"; + final String mediaSourceValueFileSystem = "filesystem"; + final String mediaSourceValueCamcorder = "camcorder"; + final String mediaSourceValueMicrophone = "microphone"; + + // media source can be 'filesystem' or 'camera' or 'camcorder' or 'microphone'. + String mediaSource = ""; + + // We add the camera intent if there was no accept type (or '*/*' or 'image/*'). + boolean addCameraIntent = true; + // We add the camcorder intent if there was no accept type (or '*/*' or 'video/*'). + boolean addCamcorderIntent = true; + + if (mUploadMessage != null) { + // Already a file picker operation in progress. + return; + } - void openFileChooser(ValueCallback<Uri> uploadMsg) { - if (mUploadMessage != null) return; mUploadMessage = uploadMsg; + + // Parse the accept type. + String params[] = acceptType.split(";"); + String mimeType = params[0]; + + for (String p : params) { + String[] keyValue = p.split("="); + if (keyValue.length == 2) { + // Process key=value parameters. + if (mediaSourceKey.equals(keyValue[0])) { + mediaSource = keyValue[1]; + } + } + } + + // This intent will display the standard OPENABLE file picker. Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); - i.setType("*/*"); - BrowserActivity.this.startActivityForResult(Intent.createChooser(i, - getString(R.string.choose_upload)), FILE_SELECTED); + + // Create an intent to add to the standard file picker that will + // capture an image from the camera. We'll combine this intent with + // the standard OPENABLE picker unless the web developer specifically + // requested the camera or gallery be opened by passing a parameter + // in the accept type. + Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + File externalDataDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DCIM); + File cameraDataDir = new File(externalDataDir.getAbsolutePath() + + File.separator + "browser-photos"); + cameraDataDir.mkdirs(); + mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator + + System.currentTimeMillis() + ".jpg"; + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath))); + + Intent camcorderIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + + Intent soundRecIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION); + + if (mimeType.equals(imageMimeType)) { + i.setType(imageMimeType); + addCamcorderIntent = false; + if (mediaSource.equals(mediaSourceValueCamera)) { + // Specified 'image/*' and requested the camera, so go ahead and launch the camera + // directly. + BrowserActivity.this.startActivityForResult(cameraIntent, FILE_SELECTED); + return; + } else if (mediaSource.equals(mediaSourceValueFileSystem)) { + // Specified filesytem as the source, so don't want to consider the camera. + addCameraIntent = false; + } + } else if (mimeType.equals(videoMimeType)) { + i.setType(videoMimeType); + addCameraIntent = false; + // The camcorder saves it's own file and returns it to us in the intent, so + // we don't need to generate one here. + mCameraFilePath = null; + + if (mediaSource.equals(mediaSourceValueCamcorder)) { + // Specified 'video/*' and requested the camcorder, so go ahead and launch the + // camcorder directly. + BrowserActivity.this.startActivityForResult(camcorderIntent, FILE_SELECTED); + return; + } else if (mediaSource.equals(mediaSourceValueFileSystem)) { + // Specified filesystem as the source, so don't want to consider the camcorder. + addCamcorderIntent = false; + } + } else if (mimeType.equals(audioMimeType)) { + i.setType(audioMimeType); + addCameraIntent = false; + addCamcorderIntent = false; + if (mediaSource.equals(mediaSourceValueMicrophone)) { + // Specified 'audio/*' and requested microphone, so go ahead and launch the sound + // recorder. + BrowserActivity.this.startActivityForResult(soundRecIntent, FILE_SELECTED); + return; + } + // On a default system, there is no single option to open an audio "gallery". Both the + // sound recorder and music browser respond to the OPENABLE/audio/* intent unlike the + // image/* and video/* OPENABLE intents where the image / video gallery are the only + // respondants (and so the user is not prompted by default). + } else { + i.setType("*/*"); + } + + // Combine the chooser and the extra choices (like camera or camcorder) + Intent chooser = new Intent(Intent.ACTION_CHOOSER); + chooser.putExtra(Intent.EXTRA_INTENT, i); + + Vector<Intent> extraInitialIntents = new Vector<Intent>(0); + + if (addCameraIntent) { + extraInitialIntents.add(cameraIntent); + } + + if (addCamcorderIntent) { + extraInitialIntents.add(camcorderIntent); + } + + if (extraInitialIntents.size() > 0) { + Intent[] extraIntents = new Intent[extraInitialIntents.size()]; + chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraInitialIntents.toArray(extraIntents)); + } + + chooser.putExtra(Intent.EXTRA_TITLE, getString(R.string.choose_upload)); + BrowserActivity.this.startActivityForResult(chooser, FILE_SELECTED); } // ------------------------------------------------------------------------- @@ -2938,7 +3218,10 @@ public class BrowserActivity extends Activity * Update the lock icon to correspond to our latest state. */ private void updateLockIconToLatest() { - updateLockIconImage(mTabControl.getCurrentTab().getLockIconType()); + Tab t = mTabControl.getCurrentTab(); + if (t != null) { + updateLockIconImage(t.getLockIconType()); + } } /** @@ -2952,7 +3235,9 @@ public class BrowserActivity extends Activity d = mMixLockIcon; } mTitleBar.setLock(d); - mFakeTitleBar.setLock(d); + if (!mXLargeScreenSize) { + mFakeTitleBar.setLock(d); + } } /** @@ -3468,8 +3753,25 @@ public class BrowserActivity extends Activity if (null == mUploadMessage) break; Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); + + // As we ask the camera to save the result of the user taking + // a picture, the camera application does not return anything other + // than RESULT_OK. So we need to check whether the file we expected + // was written to disk in the in the case that we + // did not get an intent returned but did get a RESULT_OK. If it was, + // we assume that this result has came back from the camera. + if (result == null && intent == null && resultCode == RESULT_OK) { + File cameraFile = new File(mCameraFilePath); + if (cameraFile.exists()) { + result = Uri.fromFile(cameraFile); + // Broadcast to the media scanner that we have a new photo + // so it will be added into the gallery for the user. + sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); + } + } mUploadMessage.onReceiveValue(result); mUploadMessage = null; + mCameraFilePath = null; break; default: break; @@ -3490,6 +3792,47 @@ public class BrowserActivity extends Activity } + /* package*/ void promptAddOrInstallBookmark() { + final Tab current = mTabControl.getCurrentTab(); + Resources resources = getResources(); + CharSequence[] choices = { + resources.getString(R.string.save_to_bookmarks), + resources.getString(R.string.create_shortcut_bookmark) + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.add_new_bookmark); + builder.setItems(choices, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + if (item == 0) { + bookmarkCurrentPage(); + } else if (item == 1) { + current.populatePickerData(); + String touchIconUrl = mTabControl.getCurrentWebView().getTouchIconUrl(); + if (touchIconUrl != null) { + // Download the touch icon for this site then save it to the + // homescreen. + Bundle b = new Bundle(); + b.putString("url", current.getUrl()); + b.putString("title", current.getTitle()); + b.putParcelable("favicon", current.getFavicon()); + Message msg = mHandler.obtainMessage(TOUCH_ICON_DOWNLOADED); + msg.setData(b); + new DownloadTouchIcon(BrowserActivity.this, msg, + mTabControl.getCurrentWebView().getSettings() + .getUserAgentString()).execute(touchIconUrl); + } else { + // add to homescreen, can do it immediately as there is no touch + // icon. + showSaveToHomescreenDialog(current.getUrl(), current.getTitle(), + null, current.getFavicon()); + } + } + } + }); + builder.create().show(); + } + /** * Open the Go page. * @param startWithHistory If true, open starting on the history tab. @@ -3504,7 +3847,8 @@ public class BrowserActivity extends Activity CombinedBookmarkHistoryActivity.class); String title = current.getTitle(); String url = current.getUrl(); - Bitmap thumbnail = createScreenshot(current); + Bitmap thumbnail = createScreenshot(current, getDesiredThumbnailWidth(this), + getDesiredThumbnailHeight(this)); // Just in case the user opens bookmarks before a page finishes loading // so the current history item, and therefore the page, is null. @@ -3532,6 +3876,33 @@ public class BrowserActivity extends Activity startActivityForResult(intent, COMBO_PAGE); } + private void showSaveToHomescreenDialog(String url, String title, Bitmap touchIcon, + Bitmap favicon) { + Intent intent = new Intent(this, SaveToHomescreenDialog.class); + + // Just in case the user tries to save before a page finishes loading + // so the current history item, and therefore the page, is null. + if (null == url) { + url = mLastEnteredUrl; + // This can happen. + if (null == url) { + url = mSettings.getHomePage(); + } + } + + // In case the web page has not yet received its associated title. + if (title == null) { + title = url; + } + + intent.putExtra("title", title); + intent.putExtra("url", url); + intent.putExtra("favicon", favicon); + intent.putExtra("touchIcon", touchIcon); + startActivity(intent); + } + + // Called when loading from context menu or LOAD_URL message private void loadUrlFromContext(WebView view, String url) { // In case the user enters nothing. @@ -3726,6 +4097,7 @@ public class BrowserActivity extends Activity private void getInstalledPackages() { AsyncTask<Void, Void, Set<String> > task = new AsyncTask<Void, Void, Set<String> >() { + @Override protected Set<String> doInBackground(Void... unused) { Set<String> installedPackages = new HashSet<String>(); PackageManager pm = BrowserActivity.this.getPackageManager(); @@ -3742,6 +4114,7 @@ public class BrowserActivity extends Activity } // Executes on the UI thread + @Override protected void onPostExecute(Set<String> installedPackages) { addPackageNames(installedPackages); } @@ -3770,6 +4143,7 @@ public class BrowserActivity extends Activity private Menu mMenu; private FindDialog mFindDialog; + private SelectDialog mSelectDialog; // Used to prevent chording to result in firing two shortcuts immediately // one after another. Fixes bug 1211714. boolean mCanChord; @@ -3883,7 +4257,7 @@ public class BrowserActivity extends Activity private Toast mStopToast; - private TitleBar mTitleBar; + private TitleBarBase mTitleBar; private LinearLayout mErrorConsoleContainer = null; private boolean mShouldShowErrorConsole = false; diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java index 7560c78..6af14e8 100644 --- a/src/com/android/browser/BrowserBookmarksPage.java +++ b/src/com/android/browser/BrowserBookmarksPage.java @@ -23,16 +23,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -53,6 +43,7 @@ import android.widget.AdapterView; import android.widget.GridView; import android.widget.ListView; import android.widget.Toast; +import android.widget.AdapterView.OnItemClickListener; /*package*/ enum BookmarkViewMode { NONE, GRID, LIST } /** @@ -74,10 +65,6 @@ public class BrowserBookmarksPage extends Activity implements private boolean mMostVisited; private View mEmptyView; private int mIconSize; - // XXX: There is no public string defining this intent so if Home changes - // the value, we have to update this string. - private static final String INSTALL_SHORTCUT = - "com.android.launcher.action.INSTALL_SHORTCUT"; private final static String LOGTAG = "browser"; private final static String PREF_BOOKMARK_VIEW_MODE = "pref_bookmark_view_mode"; @@ -108,9 +95,7 @@ public class BrowserBookmarksPage extends Activity implements editBookmark(i.position); break; case R.id.shortcut_context_menu_id: - final Intent send = createShortcutIntent(i.position); - send.setAction(INSTALL_SHORTCUT); - sendBroadcast(send); + sendBroadcast(createShortcutIntent(i.position)); break; case R.id.delete_context_menu_id: if (mMostVisited) { @@ -275,13 +260,14 @@ public class BrowserBookmarksPage extends Activity implements new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... unused) { - BrowserBookmarksAdapter adapter = new BrowserBookmarksAdapter( - BrowserBookmarksPage.this, - url, - title, - thumbnail, - createShortcut, - mostVisited); + BrowserBookmarksAdapter adapter = + new BrowserBookmarksAdapter( + BrowserBookmarksPage.this, + url, + title, + thumbnail, + createShortcut, + mostVisited); mHandler.obtainMessage(ADAPTER_CREATED, adapter).sendToTarget(); return null; } @@ -358,7 +344,7 @@ public class BrowserBookmarksPage extends Activity implements } listView.setDrawSelectorOnTop(false); listView.setVerticalScrollBarEnabled(true); - listView.setOnItemClickListener(mListener); + listView.setOnItemClickListener(mListListener); if (mMostVisited) { listView.setEmptyView(mEmptyView); } @@ -422,7 +408,7 @@ public class BrowserBookmarksPage extends Activity implements } }; - private AdapterView.OnItemClickListener mListener = new AdapterView.OnItemClickListener() { + private OnItemClickListener mListener = new OnItemClickListener() { public void onItemClick(AdapterView parent, View v, int position, long id) { // It is possible that the view has been canceled when we get to // this point as back has a higher priority @@ -438,8 +424,29 @@ public class BrowserBookmarksPage extends Activity implements loadUrl(position); } } else { - final Intent intent = createShortcutIntent(position); - setResultToParent(RESULT_OK, intent); + setResultToParent(RESULT_OK, createShortcutIntent(position)); + finish(); + } + } + }; + + private OnItemClickListener mListListener = new OnItemClickListener() { + public void onItemClick(AdapterView parent, View v, int position, long id) { + // 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(LOGTAG, "item clicked when dismissing"); + return; + } + if (!mCreateShortcut) { + if (0 == position && !mMostVisited) { + // XXX: Work-around for a framework issue. + mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE); + } else { + loadUrl(position); + } + } else { + setResultToParent(RESULT_OK, createShortcutIntent(position)); finish(); } } @@ -449,99 +456,8 @@ public class BrowserBookmarksPage extends Activity implements String url = getUrl(position); String title = getBookmarkTitle(position); Bitmap touchIcon = getTouchIcon(position); - - final Intent i = new Intent(); - final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, - Uri.parse(url)); - long urlHash = url.hashCode(); - long uniqueId = (urlHash << 32) | shortcutIntent.hashCode(); - shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID, - Long.toString(uniqueId)); - i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title); - // Use the apple-touch-icon if available - if (touchIcon != null) { - // Make a copy so we can modify the pixels. We can't use - // createScaledBitmap or copy since they will preserve the config - // and lose the ability to add alpha. - Bitmap bm = Bitmap.createBitmap(mIconSize, mIconSize, - Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bm); - Rect src = new Rect(0, 0, touchIcon.getWidth(), - touchIcon.getHeight()); - Rect dest = new Rect(0, 0, bm.getWidth(), bm.getHeight()); - - // Paint used for scaling the bitmap and drawing the rounded rect. - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setFilterBitmap(true); - canvas.drawBitmap(touchIcon, src, dest, paint); - - // Construct a path from a round rect. This will allow drawing with - // an inverse fill so we can punch a hole using the round rect. - Path path = new Path(); - path.setFillType(Path.FillType.INVERSE_WINDING); - RectF rect = new RectF(0, 0, bm.getWidth(), bm.getHeight()); - rect.inset(1, 1); - path.addRoundRect(rect, 8f, 8f, Path.Direction.CW); - - // Reuse the paint and clear the outside of the rectangle. - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - canvas.drawPath(path, paint); - - i.putExtra(Intent.EXTRA_SHORTCUT_ICON, bm); - } else { - Bitmap favicon = getFavicon(position); - if (favicon == null) { - i.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext( - BrowserBookmarksPage.this, - R.drawable.ic_launcher_shortcut_browser_bookmark)); - } else { - Bitmap icon = BitmapFactory.decodeResource(getResources(), - R.drawable.ic_launcher_shortcut_browser_bookmark_icon); - - // Make a copy of the regular icon so we can modify the pixels. - Bitmap copy = icon.copy(Bitmap.Config.ARGB_8888, true); - Canvas canvas = new Canvas(copy); - - // Make a Paint for the white background rectangle and for - // filtering the favicon. - Paint p = new Paint(Paint.ANTI_ALIAS_FLAG - | Paint.FILTER_BITMAP_FLAG); - p.setStyle(Paint.Style.FILL_AND_STROKE); - p.setColor(Color.WHITE); - - final float density = - getResources().getDisplayMetrics().density; - // Create a rectangle that is slightly wider than the favicon - final float iconSize = 16 * density; // 16x16 favicon - final float padding = 2 * density; // white padding around icon - final float rectSize = iconSize + 2 * padding; - - final Rect iconBounds = - new Rect(0, 0, icon.getWidth(), icon.getHeight()); - final float x = iconBounds.exactCenterX() - (rectSize / 2); - // Note: Subtract 2 dip from the y position since the box is - // slightly higher than center. Use padding since it is already - // 2 * density. - final float y = iconBounds.exactCenterY() - (rectSize / 2) - - padding; - RectF r = new RectF(x, y, x + rectSize, y + rectSize); - - // Draw a white rounded rectangle behind the favicon - canvas.drawRoundRect(r, 2, 2, p); - - // Draw the favicon in the same rectangle as the rounded - // rectangle but inset by the padding - // (results in a 16x16 favicon). - r.inset(padding, padding); - canvas.drawBitmap(favicon, null, r, p); - i.putExtra(Intent.EXTRA_SHORTCUT_ICON, copy); - } - } - // Do not allow duplicate items - i.putExtra("duplicate", false); - return i; + Bitmap favicon = getFavicon(position); + return BookmarkUtils.createAddToHomeIntent(this, url, title, touchIcon, favicon); } private void saveCurrentPage() { @@ -756,4 +672,5 @@ public class BrowserBookmarksPage extends Activity implements resultCode, data); } } + } diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java index 0f8f721..f22c9fe 100644 --- a/src/com/android/browser/BrowserDownloadAdapter.java +++ b/src/com/android/browser/BrowserDownloadAdapter.java @@ -26,6 +26,7 @@ import android.database.Cursor; import android.drm.mobile1.DrmRawContent; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Handler; import android.provider.Downloads; import android.text.format.Formatter; import android.view.LayoutInflater; @@ -55,8 +56,9 @@ public class BrowserDownloadAdapter extends DateSortedExpandableListAdapter { private int mMimetypeColumnId; private int mDateColumnId; - public BrowserDownloadAdapter(Context context, Cursor c, int index) { - super(context, c, index); + public BrowserDownloadAdapter(Context context, Cursor c, int index, + Handler handler) { + super(context, c, index, handler); mTitleColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE); mDescColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESCRIPTION); mStatusColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java index 18faf8b..bbf1191 100644 --- a/src/com/android/browser/BrowserDownloadPage.java +++ b/src/com/android/browser/BrowserDownloadPage.java @@ -63,6 +63,7 @@ public class BrowserDownloadPage extends ExpandableListActivity { // Only meaningful while a ContentObserver is registered. The ContextMenu // will be reopened on this View. private View mSelectedView; + private Handler mHandler; private final static String LOGTAG = "BrowserDownloadPage"; @Override @@ -85,7 +86,7 @@ public class BrowserDownloadPage extends ExpandableListActivity { Downloads.Impl._DATA, Downloads.Impl.COLUMN_MIME_TYPE}, null, Downloads.Impl.COLUMN_LAST_MODIFICATION + " DESC"); - + mHandler = new Handler(); // only attach everything to the listbox if we can access // the download database. Otherwise, just show it empty if (mDownloadCursor != null) { @@ -99,7 +100,7 @@ public class BrowserDownloadPage extends ExpandableListActivity { // Create a list "controller" for the data mDownloadAdapter = new BrowserDownloadAdapter(this, mDownloadCursor, mDownloadCursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_LAST_MODIFICATION)); + Downloads.Impl.COLUMN_LAST_MODIFICATION), mHandler); setListAdapter(mDownloadAdapter); mListView.setOnCreateContextMenuListener(this); @@ -241,8 +242,8 @@ public class BrowserDownloadPage extends ExpandableListActivity { */ private class ChangeObserver extends ContentObserver { private final Uri mTrack; - public ChangeObserver(Uri track) { - super(new Handler()); + public ChangeObserver(Uri track, Handler handler) { + super(handler); mTrack = track; } @@ -313,7 +314,7 @@ public class BrowserDownloadPage extends ExpandableListActivity { getContentResolver().unregisterContentObserver( mContentObserver); } - mContentObserver = new ChangeObserver(track); + mContentObserver = new ChangeObserver(track, mHandler); mSelectedView = v; getContentResolver().registerContentObserver(track, false, mContentObserver); diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java index 23080f8..0281087 100644 --- a/src/com/android/browser/BrowserHistoryPage.java +++ b/src/com/android/browser/BrowserHistoryPage.java @@ -25,7 +25,10 @@ import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.os.ServiceManager; import android.provider.Browser; import android.text.IClipboard; @@ -92,47 +95,75 @@ public class BrowserHistoryPage extends ExpandableListActivity { } } + private static final int ADAPTER_CREATED = 1000; + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case ADAPTER_CREATED: + mAdapter = (HistoryAdapter) msg.obj; + setListAdapter(mAdapter); + final ExpandableListView list = getExpandableListView(); + // Add an empty view late, so it does not claim an empty + // history before the adapter is present + View v = new ViewStub(BrowserHistoryPage.this, + R.layout.empty_history); + addContentView(v, new LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT)); + list.setEmptyView(v); + list.setOnCreateContextMenuListener( + BrowserHistoryPage.this); + // Do not post the runnable if there is nothing in the list. + if (list.getExpandableListAdapter().getGroupCount() > 0) { + list.post(new Runnable() { + public void run() { + // In case the history gets cleared before this + // event happens + if (list.getExpandableListAdapter() + .getGroupCount() > 0) { + list.expandGroup(0); + } + } + }); + } + break; + } + } + }; + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); setTitle(R.string.browser_history); - 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"; + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + 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"; - Cursor cursor = managedQuery( - Browser.BOOKMARKS_URI, - Browser.HISTORY_PROJECTION, - whereClause, null, orderBy); + Cursor cursor = managedQuery( + Browser.BOOKMARKS_URI, + Browser.HISTORY_PROJECTION, + whereClause, null, orderBy); - mAdapter = new HistoryAdapter(this, cursor, - Browser.HISTORY_PROJECTION_DATE_INDEX); - setListAdapter(mAdapter); - final ExpandableListView list = getExpandableListView(); - list.setOnCreateContextMenuListener(this); - View v = new ViewStub(this, R.layout.empty_history); - addContentView(v, new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT)); - list.setEmptyView(v); - // Do not post the runnable if there is nothing in the list. - if (list.getExpandableListAdapter().getGroupCount() > 0) { - list.post(new Runnable() { - public void run() { - // In case the history gets cleared before this event - // happens. - if (list.getExpandableListAdapter().getGroupCount() > 0) { - list.expandGroup(0); - } - } - }); - } + HistoryAdapter adapter = new HistoryAdapter( + BrowserHistoryPage.this, cursor, + Browser.HISTORY_PROJECTION_DATE_INDEX, mHandler); + mHandler.obtainMessage(ADAPTER_CREATED, adapter).sendToTarget(); + return null; + } + }.execute(); mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window", false); @@ -153,6 +184,7 @@ public class BrowserHistoryPage extends ExpandableListActivity { @Override protected void onDestroy() { + mHandler.removeCallbacksAndMessages(null); super.onDestroy(); CombinedBookmarkHistoryActivity.getIconListenerSet() .removeListener(mIconReceiver); @@ -181,7 +213,7 @@ public class BrowserHistoryPage extends ExpandableListActivity { // CombinedBookmarkHistoryActivity ((CombinedBookmarkHistoryActivity) getParent()) .removeParentChildRelationShips(); - mAdapter.refreshData(); + if (mAdapter != null) mAdapter.refreshData(); return true; default: @@ -265,7 +297,7 @@ public class BrowserHistoryPage extends ExpandableListActivity { return true; case R.id.delete_context_menu_id: Browser.deleteFromHistory(getContentResolver(), url); - mAdapter.refreshData(); + if (mAdapter != null) mAdapter.refreshData(); return true; case R.id.homepage_context_menu_id: BrowserSettings.getInstance().setHomePage(this, url); @@ -297,8 +329,9 @@ public class BrowserHistoryPage extends ExpandableListActivity { } private class HistoryAdapter extends DateSortedExpandableListAdapter { - HistoryAdapter(Context context, Cursor cursor, int index) { - super(context, cursor, index); + HistoryAdapter(Context context, Cursor cursor, int index, + Handler handler) { + super(context, cursor, index, handler); } diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java index bf1f9d5..7064180 100644 --- a/src/com/android/browser/BrowserProvider.java +++ b/src/com/android/browser/BrowserProvider.java @@ -29,8 +29,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.UriMatcher; import android.content.SharedPreferences.Editor; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; +import android.content.res.Configuration; import android.database.AbstractCursor; import android.database.ContentObserver; import android.database.Cursor; @@ -47,8 +46,6 @@ import android.speech.RecognizerResultsIntent; import android.text.TextUtils; import android.util.Log; import android.util.Patterns; -import android.util.TypedValue; - import java.io.File; import java.io.FilenameFilter; @@ -91,6 +88,17 @@ public class BrowserProvider extends ContentProvider { private static final int SUGGEST_COLUMN_QUERY_ID = 8; private static final int SUGGEST_COLUMN_INTENT_EXTRA_DATA = 9; + // how many suggestions will be shown in dropdown + // 0..SHORT: filled by browser db + private static final int MAX_SUGGEST_SHORT_SMALL = 3; + // SHORT..LONG: filled by search suggestions + private static final int MAX_SUGGEST_LONG_SMALL = 6; + + // large screen size shows more + private static final int MAX_SUGGEST_SHORT_LARGE = 6; + private static final int MAX_SUGGEST_LONG_LARGE = 9; + + // shared suggestion columns private static final String[] COLUMNS = new String[] { "_id", @@ -104,10 +112,6 @@ public class BrowserProvider extends ContentProvider { SearchManager.SUGGEST_COLUMN_QUERY, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA}; - 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; @@ -167,6 +171,9 @@ public class BrowserProvider extends ContentProvider { private SearchManager mSearchManager; + private int mMaxSuggestionShortSize; + private int mMaxSuggestionLongSize; + public BrowserProvider() { } @@ -350,6 +357,20 @@ public class BrowserProvider extends ContentProvider { @Override public boolean onCreate() { final Context context = getContext(); + boolean xlargeScreenSize = (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) + == Configuration.SCREENLAYOUT_SIZE_XLARGE; + boolean isPortrait = (context.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_PORTRAIT); + + + if (xlargeScreenSize && isPortrait) { + mMaxSuggestionLongSize = MAX_SUGGEST_LONG_LARGE; + mMaxSuggestionShortSize = MAX_SUGGEST_SHORT_LARGE; + } else { + mMaxSuggestionLongSize = MAX_SUGGEST_LONG_SMALL; + mMaxSuggestionShortSize = MAX_SUGGEST_SHORT_SMALL; + } mOpenHelper = new DatabaseHelper(context); mBackupManager = new BackupManager(context); // we added "picasa web album" into default bookmarks for version 19. @@ -465,10 +486,10 @@ public class BrowserProvider extends ContentProvider { public MySuggestionCursor(Cursor hc, Cursor sc, String string) { mHistoryCursor = hc; mSuggestCursor = sc; - mHistoryCount = hc.getCount(); + mHistoryCount = hc != null ? hc.getCount() : 0; mSuggestionCount = sc != null ? sc.getCount() : 0; - if (mSuggestionCount > (MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount)) { - mSuggestionCount = MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount; + if (mSuggestionCount > (mMaxSuggestionLongSize - mHistoryCount)) { + mSuggestionCount = mMaxSuggestionLongSize - mHistoryCount; } mString = string; mIncludeWebSearch = string.length() > 0; @@ -682,6 +703,7 @@ public class BrowserProvider extends ContentProvider { } // TODO Temporary change, finalize after jq's changes go in + @Override public void deactivate() { if (mHistoryCursor != null) { mHistoryCursor.deactivate(); @@ -692,12 +714,14 @@ public class BrowserProvider extends ContentProvider { super.deactivate(); } + @Override public boolean requery() { return (mHistoryCursor != null ? mHistoryCursor.requery() : false) | (mSuggestCursor != null ? mSuggestCursor.requery() : false); } // TODO Temporary change, finalize after jq's changes go in + @Override public void close() { super.close(); if (mHistoryCursor != null) { @@ -762,12 +786,15 @@ public class BrowserProvider extends ContentProvider { public ResultsCursor(ArrayList<String> results) { mResults = results; } + @Override public int getCount() { return mResults.size(); } + @Override public String[] getColumnNames() { return RESULTS_COLUMNS; } + @Override public String getString(int column) { switch (column) { case RESULT_ACTION_ID: @@ -789,24 +816,30 @@ public class BrowserProvider extends ContentProvider { return null; } } + @Override public short getShort(int column) { throw new UnsupportedOperationException(); } + @Override public int getInt(int column) { throw new UnsupportedOperationException(); } + @Override public long getLong(int column) { if ((mPos != -1) && column == 0) { return mPos; // use row# as the _id } throw new UnsupportedOperationException(); } + @Override public float getFloat(int column) { throw new UnsupportedOperationException(); } + @Override public double getDouble(int column) { throw new UnsupportedOperationException(); } + @Override public boolean isNull(int column) { throw new UnsupportedOperationException(); } @@ -846,8 +879,7 @@ public class BrowserProvider extends ContentProvider { String suggestSelection; String [] myArgs; if (selectionArgs[0] == null || selectionArgs[0].equals("")) { - suggestSelection = null; - myArgs = null; + return new MySuggestionCursor(null, null, ""); } else { String like = selectionArgs[0] + "%"; if (selectionArgs[0].startsWith("http") @@ -869,7 +901,7 @@ public class BrowserProvider extends ContentProvider { Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS], SUGGEST_PROJECTION, suggestSelection, myArgs, null, null, - ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING); + ORDER_BY, Integer.toString(mMaxSuggestionLongSize)); if (match == URI_MATCH_BOOKMARKS_SUGGEST || Patterns.WEB_URL.matcher(selectionArgs[0]).matches()) { @@ -878,7 +910,7 @@ public class BrowserProvider extends ContentProvider { // get Google suggest if there is still space in the list if (myArgs != null && myArgs.length > 1 && mSearchableInfo != null - && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) { + && c.getCount() < (mMaxSuggestionShortSize - 1)) { Cursor sc = mSearchManager.getSuggestions(mSearchableInfo, selectionArgs[0]); return new MySuggestionCursor(c, sc, selectionArgs[0]); } @@ -1121,4 +1153,10 @@ public class BrowserProvider extends ContentProvider { } } + public static Cursor getBookmarksSuggestions(ContentResolver cr, String constraint) { + Uri uri = Uri.parse("content://browser/" + SearchManager.SUGGEST_URI_PATH_QUERY); + return cr.query(uri, SUGGEST_PROJECTION, SUGGEST_SELECTION, + new String[] { constraint }, ORDER_BY); + } + } diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java index 769dfca..94bed2d 100644 --- a/src/com/android/browser/BrowserSettings.java +++ b/src/com/android/browser/BrowserSettings.java @@ -106,8 +106,8 @@ class BrowserSettings extends Observable { private boolean showConsole = true; // Private preconfigured values - private static int minimumFontSize = 8; - private static int minimumLogicalFontSize = 8; + private static int minimumFontSize = 1; + private static int minimumLogicalFontSize = 1; private static int defaultFontSize = 16; private static int defaultFixedFontSize = 13; private static WebSettings.TextSize textSize = @@ -219,6 +219,9 @@ class BrowserSettings extends Observable { s.setNeedInitialFocus(false); // Browser supports multiple windows s.setSupportMultipleWindows(true); + // enable smooth transition for better performance during panning or + // zooming + s.setEnableSmoothTransition(true); // HTML5 API flags s.setAppCacheEnabled(b.appCacheEnabled); diff --git a/src/com/android/browser/CircularProgressView.java b/src/com/android/browser/CircularProgressView.java new file mode 100644 index 0000000..48f293a --- /dev/null +++ b/src/com/android/browser/CircularProgressView.java @@ -0,0 +1,140 @@ + +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.browser; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.widget.ImageButton; + +/** + * + */ +public class CircularProgressView extends ImageButton { + + private static final int[] ALPHAS = { + 64, 96, 128, 160, 192, 192, 160, 128, 96, 64 + }; + + // 100 ms delay between frames, 10fps + private static int ALPHA_REFRESH_DELAY = 100; + + private int mEndAngle; + private int mProgress; + private Paint mPaint; + private int mAlpha; + private boolean mAnimated; + private RectF mRect; + private int mMaxProgress; + + /** + * @param context + * @param attrs + * @param defStyle + */ + public CircularProgressView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + /** + * @param context + * @param attrs + */ + public CircularProgressView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + /** + * @param context + */ + public CircularProgressView(Context context) { + super(context); + init(context); + } + + private void init(Context ctx) { + mEndAngle = 0; + mProgress = 0; + mMaxProgress = 100; + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setColor(Color.BLACK); + mRect = new RectF(); + } + + void setMaxProgress(int max) { + mMaxProgress = max; + } + + private synchronized boolean isAnimated() { + return mAnimated; + } + + private synchronized void setAnimated(boolean animated) { + mAnimated = animated; + } + + void setProgress(int progress) { + mProgress = progress; + mEndAngle = 360 * progress / mMaxProgress; + invalidate(); + if (!isAnimated() && (progress > 0) && (progress < mMaxProgress)) { + setAnimated(true); + mAlpha = 0; + post(new Runnable() { + @Override + public void run() { + if (isAnimated()) { + mAlpha = (mAlpha + 1) % ALPHAS.length; + mPaint.setAlpha(ALPHAS[mAlpha]); + invalidate(); + postDelayed(this, ALPHA_REFRESH_DELAY); + } + } + }); + } else if ((progress <= 0) || (progress >= mMaxProgress)) { + setAnimated(false); + } + } + + @Override + public void onDraw(Canvas canvas) { + int w = getWidth(); + int h = getHeight(); + float cx = w * 0.5f; + float cy = h * 0.5f; + mRect.set(0, 0, w, h); + if ((mProgress > 0) && (mProgress < mMaxProgress)) { + Path p = new Path(); + p.moveTo(cx, cy); + p.lineTo(cx, 0); + p.arcTo(mRect, 270, mEndAngle); + p.lineTo(cx, cy); + int state = canvas.save(); + canvas.drawPath(p, mPaint); + canvas.restoreToCount(state); + } + super.onDraw(canvas); + } + +} diff --git a/src/com/android/browser/DateSortedExpandableListAdapter.java b/src/com/android/browser/DateSortedExpandableListAdapter.java index 1d04493..f8261d8 100644 --- a/src/com/android/browser/DateSortedExpandableListAdapter.java +++ b/src/com/android/browser/DateSortedExpandableListAdapter.java @@ -51,8 +51,8 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter { private Context mContext; private class ChangeObserver extends ContentObserver { - public ChangeObserver() { - super(new Handler()); + public ChangeObserver(Handler handler) { + super(handler); } @Override @@ -67,13 +67,13 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter { } public DateSortedExpandableListAdapter(Context context, Cursor cursor, - int dateIndex) { + int dateIndex, Handler handler) { mContext = context; mDateSorter = new DateSorter(context); mObservers = new Vector<DataSetObserver>(); mCursor = cursor; mIdIndex = cursor.getColumnIndexOrThrow(BaseColumns._ID); - cursor.registerContentObserver(new ChangeObserver()); + cursor.registerContentObserver(new ChangeObserver(handler)); mDateIndex = dateIndex; buildMap(); } diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java index 14404ff..765d288 100644 --- a/src/com/android/browser/DownloadTouchIcon.java +++ b/src/com/android/browser/DownloadTouchIcon.java @@ -16,6 +16,7 @@ package com.android.browser; +import android.app.Activity; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -25,10 +26,11 @@ import android.graphics.BitmapFactory; import android.net.http.AndroidHttpClient; import android.net.Proxy; import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Message; import android.provider.Browser; import android.webkit.WebView; - import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; @@ -45,10 +47,17 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> { private Cursor mCursor; private final String mOriginalUrl; private final String mUrl; - private final String mUserAgent; - private final BrowserActivity mActivity; + private final String mUserAgent; // Sites may serve a different icon to different UAs + private Message mMessage; + + private final Activity mActivity; /* package */ Tab mTab; + /** + * Use this ctor to store the touch icon in the bookmarks database for + * the originalUrl so we take account of redirects. Used when the user + * bookmarks a page from outside the bookmarks activity. + */ public DownloadTouchIcon(Tab tab, BrowserActivity activity, ContentResolver cr, WebView view) { mTab = tab; mActivity = activity; @@ -59,24 +68,49 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> { mUserAgent = view.getSettings().getUserAgentString(); } - public DownloadTouchIcon(ContentResolver cr, String url) { + /** + * Use this ctor to download the touch icon and update the bookmarks database + * entry for the given url. Used when the user creates a bookmark from + * within the bookmarks activity and there haven't been any redirects. + * TODO: Would be nice to set the user agent here so that there is no + * potential for the three different ctors here to return different icons. + */ + public DownloadTouchIcon(AddBookmarkPage activity, ContentResolver cr, String url) { mTab = null; - mActivity = null; + mActivity = activity; mContentResolver = cr; mOriginalUrl = null; mUrl = url; mUserAgent = null; } + /** + * Use this ctor to not store the touch icon in a database, rather add it to + * the passed Message's data bundle with the key "touchIcon" and then send + * the message. + */ + public DownloadTouchIcon(BrowserActivity activity, Message msg, String userAgent) { + mMessage = msg; + mActivity = activity; + mContentResolver = null; + mOriginalUrl = null; + mUrl = null; + mUserAgent = userAgent; + } + @Override public Void doInBackground(String... values) { - mCursor = BrowserBookmarksAdapter.queryBookmarksForUrl(mContentResolver, - mOriginalUrl, mUrl, true); - if (mCursor != null && mCursor.getCount() > 0) { - String url = values[0]; + if (mContentResolver != null) { + mCursor = BrowserBookmarksAdapter.queryBookmarksForUrl(mContentResolver, + mOriginalUrl, mUrl, true); + } - AndroidHttpClient client = AndroidHttpClient.newInstance( - mUserAgent); + boolean inBookmarksDatabase = mCursor != null && mCursor.getCount() > 0; + + String url = values[0]; + + if (inBookmarksDatabase || mMessage != null) { + AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent); HttpHost httpHost = Proxy.getPreferredHttpHost(mActivity, url); if (httpHost != null) { ConnRouteParams.setDefaultProxy(client.getParams(), httpHost); @@ -89,7 +123,6 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> { try { HttpResponse response = client.execute(request); - if (response.getStatusLine().getStatusCode() == 200) { HttpEntity entity = response.getEntity(); if (entity != null) { @@ -97,7 +130,12 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> { if (content != null) { Bitmap icon = BitmapFactory.decodeStream( content, null, null); - storeIcon(icon); + if (inBookmarksDatabase) { + storeIcon(icon); + } else if (mMessage != null) { + Bundle b = mMessage.getData(); + b.putParcelable("touchIcon", icon); + } } } } @@ -109,9 +147,15 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> { client.close(); } } + if (mCursor != null) { mCursor.close(); } + + if (mMessage != null) { + mMessage.sendToTarget(); + } + return null; } diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java index 45c8016..9d0ac4b 100644 --- a/src/com/android/browser/FindDialog.java +++ b/src/com/android/browser/FindDialog.java @@ -16,27 +16,25 @@ package com.android.browser; -import android.app.Dialog; import android.content.Context; -import android.os.Bundle; import android.text.Editable; +import android.text.Selection; import android.text.Spannable; import android.text.TextWatcher; import android.view.Gravity; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; +import android.view.animation.AnimationUtils; import android.view.inputmethod.InputMethodManager; import android.webkit.WebView; import android.widget.EditText; +import android.widget.LinearLayout; import android.widget.TextView; -/* package */ class FindDialog extends Dialog implements TextWatcher { - private WebView mWebView; +/* package */ class FindDialog extends WebDialog implements TextWatcher { private TextView mMatches; - private BrowserActivity mBrowserActivity; // Views with which the user can interact. private EditText mEditText; @@ -44,39 +42,30 @@ import android.widget.TextView; private View mPrevButton; private View mMatchesView; + // When the dialog is opened up with old text, enter needs to be pressed + // (or the text needs to be changed) before WebView.findAll can be called. + // Once it has been called, enter should move to the next match. + private boolean mMatchesFound; + private int mNumberOfMatches; + private View.OnClickListener mFindListener = new View.OnClickListener() { public void onClick(View v) { findNext(); } }; - private View.OnClickListener mFindCancelListener = - new View.OnClickListener() { - public void onClick(View v) { - dismiss(); - } - }; - - private View.OnClickListener mFindPreviousListener = + private View.OnClickListener mFindPreviousListener = new View.OnClickListener() { public void onClick(View v) { if (mWebView == null) { throw new AssertionError("No WebView for FindDialog::onClick"); } mWebView.findNext(false); + updateMatchesString(); hideSoftInput(); } }; - /* - * Remove the soft keyboard from the screen. - */ - private void hideSoftInput() { - InputMethodManager imm = (InputMethodManager) - mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); - } - private void disableButtons() { mPrevButton.setEnabled(false); mNextButton.setEnabled(false); @@ -84,28 +73,13 @@ import android.widget.TextView; mNextButton.setFocusable(false); } - /* package */ void setWebView(WebView webview) { - mWebView = webview; - } - /* package */ FindDialog(BrowserActivity context) { - super(context, R.style.FindDialogTheme); - mBrowserActivity = context; - setCanceledOnTouchOutside(true); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + super(context); - Window theWindow = getWindow(); - theWindow.setGravity(Gravity.BOTTOM|Gravity.FILL_HORIZONTAL); - - setContentView(R.layout.browser_find); - - theWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); + LayoutInflater factory = LayoutInflater.from(context); + factory.inflate(R.layout.browser_find, this); + addCancel(); mEditText = (EditText) findViewById(R.id.edit); View button = findViewById(R.id.next); @@ -116,29 +90,59 @@ import android.widget.TextView; button.setOnClickListener(mFindPreviousListener); mPrevButton = button; - button = findViewById(R.id.done); - button.setOnClickListener(mFindCancelListener); - mMatches = (TextView) findViewById(R.id.matches); mMatchesView = findViewById(R.id.matches_view); disableButtons(); - theWindow.setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } - + + /** + * Called by BrowserActivity.closeDialog. Start the animation to hide + * the dialog, inform the WebView that the dialog is being dismissed, + * and hide the soft keyboard. + */ public void dismiss() { super.dismiss(); - mBrowserActivity.closeFind(); mWebView.notifyFindDialogDismissed(); + hideSoftInput(); + } + + @Override + public boolean dispatchKeyEventPreIme(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (state != null) { + int action = event.getAction(); + if (KeyEvent.ACTION_DOWN == action + && event.getRepeatCount() == 0) { + state.startTracking(event, this); + return true; + } else if (KeyEvent.ACTION_UP == action + && !event.isCanceled() && state.isTracking(event)) { + mBrowserActivity.closeDialogs(); + return true; + } + } + } + return super.dispatchKeyEventPreIme(event); } @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER - && event.getAction() == KeyEvent.ACTION_UP - && mEditText.hasFocus()) { - findNext(); - return true; + int keyCode = event.getKeyCode(); + if (event.getAction() == KeyEvent.ACTION_UP) { + if (keyCode == KeyEvent.KEYCODE_ENTER + && mEditText.hasFocus()) { + if (mMatchesFound) { + findNext(); + } else { + findAll(); + // Set the selection to the end. + Spannable span = (Spannable) mEditText.getText(); + Selection.setSelection(span, span.length()); + } + return true; + } } return super.dispatchKeyEvent(event); } @@ -148,18 +152,26 @@ import android.widget.TextView; throw new AssertionError("No WebView for FindDialog::findNext"); } mWebView.findNext(true); + updateMatchesString(); hideSoftInput(); } public void show() { super.show(); + // In case the matches view is showing from a previous search + mMatchesView.setVisibility(View.INVISIBLE); + mMatchesFound = false; + // This text is only here to ensure that mMatches has a height. + mMatches.setText("0"); mEditText.requestFocus(); - mEditText.setText(""); Spannable span = (Spannable) mEditText.getText(); - span.setSpan(this, 0, span.length(), - Spannable.SPAN_INCLUSIVE_INCLUSIVE); - setMatchesFound(0); + int length = span.length(); + Selection.setSelection(span, 0, length); + span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE); disableButtons(); + InputMethodManager imm = (InputMethodManager) + mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mEditText, 0); } // TextWatcher methods @@ -173,9 +185,13 @@ import android.widget.TextView; int start, int before, int count) { + findAll(); + } + + private void findAll() { if (mWebView == null) { throw new AssertionError( - "No WebView for FindDialog::onTextChanged"); + "No WebView for FindDialog::findAll"); } CharSequence find = mEditText.getText(); if (0 == find.length()) { @@ -184,14 +200,16 @@ import android.widget.TextView; mMatchesView.setVisibility(View.INVISIBLE); } else { mMatchesView.setVisibility(View.VISIBLE); - mWebView.setFindDialogHeight( - getWindow().getDecorView().getHeight()); int found = mWebView.findAll(find.toString()); + mMatchesFound = true; setMatchesFound(found); if (found < 2) { disableButtons(); if (found == 0) { - setMatchesFound(0); + // Cannot use getQuantityString, which ignores the "zero" + // quantity. + mMatches.setText(mBrowserActivity.getResources().getString( + R.string.no_matches)); } } else { mPrevButton.setFocusable(true); @@ -203,8 +221,21 @@ import android.widget.TextView; } private void setMatchesFound(int found) { + mNumberOfMatches = found; + updateMatchesString(); + } + + public void setText(String text) { + mEditText.setText(text); + findAll(); + } + + private void updateMatchesString() { + // Note: updateMatchesString is only called by methods that have already + // checked mWebView for null. String template = mBrowserActivity.getResources(). - getQuantityString(R.plurals.matches_found, found, found); + getQuantityString(R.plurals.matches_found, mNumberOfMatches, + mWebView.findIndex() + 1, mNumberOfMatches); mMatches.setText(template); } diff --git a/src/com/android/browser/SaveToHomescreenDialog.java b/src/com/android/browser/SaveToHomescreenDialog.java new file mode 100644 index 0000000..15f0aea --- /dev/null +++ b/src/com/android/browser/SaveToHomescreenDialog.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.ParseException; +import android.net.Uri; +import android.net.WebAddress; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.provider.Browser; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Date; + +public class SaveToHomescreenDialog extends Activity { + + private EditText mTitle; + private String mUrl; + private Bitmap mFavicon; + private Bitmap mTouchIcon; + + private View.OnClickListener mOk = new View.OnClickListener() { + public void onClick(View v) { + if (save()) { + finish(); + } + } + }; + + private View.OnClickListener mCancel = new View.OnClickListener() { + public void onClick(View v) { + finish(); + } + }; + + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + requestWindowFeature(Window.FEATURE_LEFT_ICON); + setContentView(R.layout.browser_add_bookmark_const_url); + setTitle(R.string.create_shortcut_bookmark); + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, + R.drawable.ic_list_bookmark); + + String title = null; + String url = null; + Bundle map = getIntent().getExtras(); + if (map != null) { + title = map.getString("title"); + } + + mUrl = map.getString("url"); + mFavicon = (Bitmap)map.getParcelable("favicon"); + mTouchIcon = (Bitmap)map.getParcelable("touchIcon"); + + Bitmap icon = BookmarkUtils.createIcon(this, mTouchIcon, mFavicon, + BookmarkUtils.BookmarkIconType.ICON_HOME_SHORTCUT); + getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, new BitmapDrawable(icon)); + + mTitle = (EditText) findViewById(R.id.title); + mTitle.setText(title); + + Button okButton = (Button) findViewById(R.id.OK); + okButton.setOnClickListener(mOk); + + Button cancelButton = (Button) findViewById(R.id.cancel); + cancelButton.setOnClickListener(mCancel); + + if (!getWindow().getDecorView().isInTouchMode()) { + okButton.requestFocus(); + } + } + + /** + * Parse the data entered in the dialog and send an intent to create an + * icon on the homescreen. + */ + private boolean save() { + String title = mTitle.getText().toString().trim(); + String unfilteredUrl = BrowserActivity.fixUrl(mUrl); + if (title.length() == 0) { + mTitle.setError(getResources().getText(R.string.bookmark_needs_title)); + return false; + } + + String url = unfilteredUrl.trim(); + + sendBroadcast(BookmarkUtils.createAddToHomeIntent(this, url, title, + mTouchIcon, mFavicon)); + setResult(RESULT_OK); + return true; + } +} diff --git a/src/com/android/browser/SelectDialog.java b/src/com/android/browser/SelectDialog.java new file mode 100644 index 0000000..461127a --- /dev/null +++ b/src/com/android/browser/SelectDialog.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.provider.Browser; +import android.view.LayoutInflater; +import android.view.View; + +/* package */ class SelectDialog extends WebDialog { + private View mCopyButton; + private View mSelectAllButton; + private View mShareButton; + private View mFindButton; + + SelectDialog(BrowserActivity context) { + super(context); + LayoutInflater factory = LayoutInflater.from(context); + factory.inflate(R.layout.browser_select, this); + addCancel(); + + mCopyButton = findViewById(R.id.copy); + mCopyButton.setOnClickListener(mCopyListener); + mSelectAllButton = findViewById(R.id.select_all); + mSelectAllButton.setOnClickListener(mSelectAllListener); + mShareButton = findViewById(R.id.share); + mShareButton.setOnClickListener(mShareListener); + mFindButton = findViewById(R.id.find); + mFindButton.setOnClickListener(mFindListener); + } + + private View.OnClickListener mCopyListener = new View.OnClickListener() { + public void onClick(View v) { + mWebView.copySelection(); + mBrowserActivity.closeDialogs(); + } + }; + + private View.OnClickListener mSelectAllListener = new View.OnClickListener() { + public void onClick(View v) { + mWebView.selectAll(); + } + }; + + private View.OnClickListener mShareListener = new View.OnClickListener() { + public void onClick(View v) { + String selection = mWebView.getSelection(); + Browser.sendString(mBrowserActivity, selection); + mBrowserActivity.closeDialogs(); + } + }; + + private View.OnClickListener mFindListener = new View.OnClickListener() { + public void onClick(View v) { + String selection = mWebView.getSelection(); + mBrowserActivity.closeDialogs(); + mBrowserActivity.showFindDialog(); + mBrowserActivity.setFindDialogText(selection); + } + }; + + /** + * Called by BrowserActivity.closeDialog. Start the animation to hide + * the dialog, and inform the WebView that the dialog is being dismissed. + */ + @Override + public void dismiss() { + super.dismiss(); + mWebView.notifySelectDialogDismissed(); + } + +} diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java index 5795b05..1d9482b 100644 --- a/src/com/android/browser/Tab.java +++ b/src/com/android/browser/Tab.java @@ -16,21 +16,13 @@ package com.android.browser; -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Map; -import java.util.Vector; - import android.app.AlertDialog; import android.app.SearchManager; import android.content.ContentResolver; import android.content.ContentValues; import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; import android.content.Intent; +import android.content.DialogInterface.OnCancelListener; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; @@ -71,8 +63,17 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.browser.TabControl.TabChangeListener; import com.android.common.speech.LoggingEvents; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Vector; + /** * Class for maintaining Tabs with a main WebView and a subwindow. */ @@ -87,7 +88,7 @@ class Tab { // The Geolocation permissions prompt private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt; // Main WebView wrapper - private View mContainer; + private LinearLayout mContainer; // Main WebView private WebView mMainView; // Subwindow container @@ -151,7 +152,6 @@ class Tab { static final String CURRTAB = "currentTab"; static final String CURRURL = "currentUrl"; static final String CURRTITLE = "currentTitle"; - static final String CURRPICTURE = "currentPicture"; static final String CLOSEONEXIT = "closeonexit"; static final String PARENTTAB = "parentTab"; static final String APPID = "appid"; @@ -503,6 +503,9 @@ class Tab { if (mInForeground) { mActivity.onPageStarted(view, url, favicon); } + if (getTabChangeListener() != null) { + getTabChangeListener().onPageStarted(Tab.this); + } } @Override @@ -524,6 +527,9 @@ class Tab { if (mInForeground) { mActivity.onPageFinished(view, url); } + if (getTabChangeListener() != null) { + getTabChangeListener().onPageFinished(Tab.this); + } } // return true if want to hijack the url to let another app to handle it @@ -673,6 +679,7 @@ class Tab { final ContentResolver cr = mActivity.getContentResolver(); final String newUrl = url; new AsyncTask<Void, Void, Void>() { + @Override protected Void doInBackground(Void... unused) { Browser.updateVisitedHistory(cr, newUrl, true); return null; @@ -949,6 +956,9 @@ class Tab { if (mInForeground) { mActivity.onProgressChanged(view, newProgress); } + if (getTabChangeListener() != null) { + getTabChangeListener().onProgress(Tab.this, newProgress); + } } @Override @@ -958,11 +968,16 @@ class Tab { // here, if url is null, we want to reset the title mActivity.setUrlTitle(pageUrl, title); } + TabChangeListener tcl = getTabChangeListener(); + if (tcl != null) { + tcl.onUrlAndTitle(Tab.this, pageUrl,title); + } if (pageUrl == null || pageUrl.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) { return; } new AsyncTask<Void, Void, Void>() { + @Override protected Void doInBackground(Void... unused) { // See if we can find the current url in our history // database and add the new title to it. @@ -1021,6 +1036,9 @@ class Tab { if (mInForeground) { mActivity.setFavicon(icon); } + if (getTabChangeListener() != null) { + getTabChangeListener().onFavicon(Tab.this, icon); + } } @Override @@ -1041,6 +1059,16 @@ class Tab { } @Override + public void onSelectionDone(WebView view) { + if (mInForeground) mActivity.closeDialogs(); + } + + @Override + public void onSelectionStart(WebView view) { + if (mInForeground) mActivity.showSelectDialog(); + } + + @Override public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { if (mInForeground) mActivity.onShowCustomView(view, callback); @@ -1185,9 +1213,9 @@ class Tab { } @Override - public void openFileChooser(ValueCallback<Uri> uploadMsg) { + public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { if (mInForeground) { - mActivity.openFileChooser(uploadMsg); + mActivity.openFileChooser(uploadMsg, acceptType); } else { uploadMsg.onReceiveValue(null); } @@ -1199,10 +1227,12 @@ class Tab { @Override public void getVisitedHistory(final ValueCallback<String[]> callback) { AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() { + @Override public String[] doInBackground(Void... unused) { return Browser.getVisitedHistory(mActivity .getContentResolver()); } + @Override public void onPostExecute(String[] result) { callback.onReceiveValue(result); }; @@ -1220,9 +1250,18 @@ class Tab { private static class SubWindowClient extends WebViewClient { // The main WebViewClient. private final WebViewClient mClient; + private final BrowserActivity mBrowserActivity; - SubWindowClient(WebViewClient client) { + SubWindowClient(WebViewClient client, BrowserActivity activity) { mClient = client; + mBrowserActivity = activity; + } + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + // Unlike the others, do not call mClient's version, which would + // change the progress bar. However, we do want to remove the + // find or select dialog. + mBrowserActivity.closeDialogs(); } @Override public void doUpdateVisitedHistory(WebView view, String url, @@ -1312,7 +1351,7 @@ class Tab { // The tab consists of a container view, which contains the main // WebView, as well as any other UI elements associated with the tab. - mContainer = mInflateService.inflate(R.layout.tab, null); + mContainer = (LinearLayout) mInflateService.inflate(R.layout.tab, null); mDownloadListener = new DownloadListener() { public void onDownloadStart(String url, String userAgent, @@ -1423,6 +1462,7 @@ class Tab { */ boolean createSubWindow() { if (mSubView == null) { + mActivity.closeDialogs(); mSubViewContainer = mInflateService.inflate( R.layout.browser_subwindow, null); mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview); @@ -1431,7 +1471,8 @@ class Tab { mSubView.setMapTrackballToArrowKeys(false); // Enable the built-in zoom mSubView.getSettings().setBuiltInZoomControls(true); - mSubView.setWebViewClient(new SubWindowClient(mWebViewClient)); + mSubView.setWebViewClient(new SubWindowClient(mWebViewClient, + mActivity)); mSubView.setWebChromeClient(new SubWindowChromeClient( mWebChromeClient)); // Set a different DownloadListener for the mSubView, since it will @@ -1469,6 +1510,7 @@ class Tab { */ void dismissSubWindow() { if (mSubView != null) { + mActivity.closeDialogs(); BrowserSettings.getInstance().deleteObserver( mSubView.getSettings()); mSubView.destroy(); @@ -1493,6 +1535,7 @@ class Tab { void removeSubWindow(ViewGroup content) { if (mSubView != null) { content.removeView(mSubViewContainer); + mActivity.closeDialogs(); } } @@ -1551,6 +1594,7 @@ class Tab { (FrameLayout) mContainer.findViewById(R.id.webview_wrapper); wrapper.removeView(mMainView); content.removeView(mContainer); + mActivity.closeDialogs(); removeSubWindow(content); } @@ -1824,6 +1868,9 @@ class Tab { // FIXME: The only place we cared about subwindow was for // bookmarking (i.e. not when saving state). Was this deliberate? final WebBackForwardList list = mMainView.copyBackForwardList(); + if (list == null) { + Log.w(LOGTAG, "populatePickerData called and WebBackForwardList is null"); + } final WebHistoryItem item = list != null ? list.getCurrentItem() : null; populatePickerData(item); } @@ -1832,7 +1879,9 @@ class Tab { // WebView. private void populatePickerData(WebHistoryItem item) { mPickerData = new PickerData(); - if (item != null) { + if (item == null) { + Log.w(LOGTAG, "populatePickerData called with a null WebHistoryItem"); + } else { mPickerData.mUrl = item.getUrl(); mPickerData.mTitle = item.getTitle(); mPickerData.mFavicon = item.getFavicon(); @@ -1883,17 +1932,6 @@ class Tab { mSavedState = new Bundle(); final WebBackForwardList list = mMainView.saveState(mSavedState); - if (list != null) { - final File f = new File(mActivity.getTabControl().getThumbnailDir(), - mMainView.hashCode() + "_pic.save"); - if (mMainView.savePicture(mSavedState, f)) { - mSavedState.putString(CURRPICTURE, f.getPath()); - } else { - // if savePicture returned false, we can't trust the contents, - // and it may be large, so we delete it right away - f.delete(); - } - } // Store some extra info for displaying the tab in the picker. final WebHistoryItem item = list != null ? list.getCurrentItem() : null; @@ -1939,11 +1977,47 @@ class Tab { if (list == null) { return false; } - if (b.containsKey(CURRPICTURE)) { - final File f = new File(b.getString(CURRPICTURE)); - mMainView.restorePicture(b, f); - f.delete(); - } return true; } + + /* + * Opens the find and select text dialogs. Called by BrowserActivity. + */ + WebView showDialog(WebDialog dialog) { + LinearLayout container; + WebView view; + if (mSubView != null) { + view = mSubView; + container = (LinearLayout) mSubViewContainer.findViewById( + R.id.inner_container); + } else { + view = mMainView; + container = mContainer; + } + dialog.show(); + container.addView(dialog, 0, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + dialog.setWebView(view); + return view; + } + + /* + * Close the find or select dialog. Called by BrowserActivity.closeDialog. + */ + void closeDialog(WebDialog dialog) { + // The dialog may be attached to the subwindow. Ensure that the + // correct parent has it removed. + LinearLayout parent = (LinearLayout) dialog.getParent(); + if (parent != null) parent.removeView(dialog); + } + + /** + * always get the TabChangeListener form the tab control + * @return the TabControl change listener + */ + private TabChangeListener getTabChangeListener() { + return mActivity.getTabControl().getTabChangeListener(); + } + } diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java index 7cd2ccb..0fbdd7a 100644 --- a/src/com/android/browser/TabControl.java +++ b/src/com/android/browser/TabControl.java @@ -132,7 +132,7 @@ class TabControl { int getCurrentIndex() { return mCurrentTab; } - + /** * Given a Tab, find it's index * @param Tab to find @@ -167,6 +167,9 @@ class TabControl { mTabs.add(t); // Initially put the tab in the background. t.putInBackground(); + if (mTabChangeListener != null) { + mTabChangeListener.onNewTab(t); + } return t; } @@ -229,17 +232,11 @@ class TabControl { } } - // This tab may have been pushed in to the background and then closed. - // If the saved state contains a picture file, delete the file. - Bundle savedState = t.getSavedState(); - if (savedState != null) { - if (savedState.containsKey(Tab.CURRPICTURE)) { - new File(savedState.getString(Tab.CURRPICTURE)).delete(); - } - } - // Remove it from the queue of viewed tabs. mTabQueue.remove(t); + if (mTabChangeListener != null) { + mTabChangeListener.onRemoveTab(t); + } return true; } @@ -626,6 +623,47 @@ class TabControl { mainView.loadUrl(BrowserSettings.getInstance().getHomePage()); } } + if (mTabChangeListener != null) { + mTabChangeListener.onCurrentTab(newTab); + } return true; } + + interface TabChangeListener { + + public void onNewTab(Tab tab); + + public void onRemoveTab(Tab tab); + + public void onCurrentTab(Tab tab); + + public void onProgress(Tab tab, int progress); + + public void onUrlAndTitle(Tab tab, String url, String title); + + public void onFavicon(Tab tab, Bitmap favicon); + + public void onPageStarted(Tab tab); + + public void onPageFinished(Tab tab); + + } + + private TabChangeListener mTabChangeListener; + + /** + * register the TabChangeListener with the tab control + * @param listener + */ + void setOnTabChangeListener(TabChangeListener listener) { + mTabChangeListener = listener; + } + + /** + * get the current TabChangeListener (used by the tabs) + */ + TabChangeListener getTabChangeListener() { + return mTabChangeListener; + } + } diff --git a/src/com/android/browser/TabScrollView.java b/src/com/android/browser/TabScrollView.java new file mode 100644 index 0000000..6d8b91b --- /dev/null +++ b/src/com/android/browser/TabScrollView.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; + +/** + * custom view for displaying tabs in the tabbed title bar + */ +public class TabScrollView extends HorizontalScrollView { + + private Context mContext; + + private LinearLayout mContentView; + + private int mSelected; + + /** + * @param context + * @param attrs + * @param defStyle + */ + public TabScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + /** + * @param context + * @param attrs + */ + public TabScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + /** + * @param context + */ + public TabScrollView(Context context) { + super(context); + init(context); + } + + private void init(Context ctx) { + mContext = ctx; + setHorizontalScrollBarEnabled(false); + mContentView = new LinearLayout(mContext); + mContentView.setOrientation(LinearLayout.HORIZONTAL); + mContentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + addView(mContentView); + mSelected = -1; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + ensureChildVisible(getSelectedTab()); + } + + void setSelectedTab(int position) { + View v = getSelectedTab(); + if (v != null) { + v.setSelected(false); + } + mSelected = position; + v = getSelectedTab(); + if (v != null) { + v.setSelected(true); + } + requestLayout(); + } + + View getSelectedTab() { + if ((mSelected >= 0) && (mSelected < mContentView.getChildCount())) { + return mContentView.getChildAt(mSelected); + } else { + return null; + } + } + + void clearTabs() { + mContentView.removeAllViews(); + } + + void addTab(View tab) { + mContentView.addView(tab); + tab.setSelected(false); + } + + void addTab(View tab, int pos) { + mContentView.addView(tab, pos); + tab.setSelected(false); + } + + void removeTab(View tab) { + mContentView.removeView(tab); + } + + void ensureChildVisible(View child) { + if (child != null) { + int childl = child.getLeft(); + int childr = childl + child.getWidth(); + int viewl = getScrollX(); + int viewr = viewl + getWidth(); + if (childl < viewl) { + // need scrolling to left + scrollTo(childl, 0); + } else if (childr > viewr) { + // need scrolling to right + scrollTo(childr - viewr + viewl, 0); + } + } + } + + +} diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java index dc4979b..f45025d 100644 --- a/src/com/android/browser/TitleBar.java +++ b/src/com/android/browser/TitleBar.java @@ -21,14 +21,8 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Rect; import android.graphics.drawable.Animatable; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.PaintDrawable; import android.os.Handler; import android.os.Message; import android.speech.RecognizerIntent; @@ -45,7 +39,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; @@ -55,21 +48,16 @@ import com.android.common.speech.LoggingEvents; * This class represents a title bar for a particular "tab" or "window" in the * browser. */ -public class TitleBar extends LinearLayout { +public class TitleBar extends TitleBarBase { private TextView mTitle; - private Drawable mCloseDrawable; private ImageView mRtButton; private Drawable mCircularProgress; private ProgressBar mHorizontalProgress; - private ImageView mFavicon; - private ImageView mLockIcon; private ImageView mStopButton; private Drawable mBookmarkDrawable; private Drawable mVoiceDrawable; private boolean mInLoad; private BrowserActivity mBrowserActivity; - private Drawable mGenericFavicon; - private int mIconDimension; private View mTitleBg; private MyHandler mHandler; private Intent mVoiceSearchIntent; @@ -84,7 +72,7 @@ public class TitleBar extends LinearLayout { private static int LONG_PRESS = 1; public TitleBar(BrowserActivity context) { - super(context, null); + super(context); mHandler = new MyHandler(); LayoutInflater factory = LayoutInflater.from(context); factory.inflate(R.layout.title_bar, this); @@ -107,13 +95,11 @@ public class TitleBar extends LinearLayout { TypedValue.COMPLEX_UNIT_DIP, 8f, metrics); mRightMargin = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 6f, metrics); - mIconDimension = (int) TypedValue.applyDimension( + int iconDimension = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 20f, metrics); - mCircularProgress.setBounds(0, 0, mIconDimension, mIconDimension); + mCircularProgress.setBounds(0, 0, iconDimension, iconDimension); mHorizontalProgress = (ProgressBar) findViewById( R.id.progress_horizontal); - mGenericFavicon = context.getResources().getDrawable( - R.drawable.app_web_browser_sm); mVoiceSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); mVoiceSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); @@ -219,7 +205,7 @@ public class TitleBar extends LinearLayout { } else if (mInLoad) { mBrowserActivity.stopLoading(); } else { - mBrowserActivity.bookmarksOrHistoryPicker(false); + mBrowserActivity.promptAddOrInstallBookmark(); } button.setPressed(false); } else if (mTitleBg.isPressed()) { @@ -248,25 +234,6 @@ public class TitleBar extends LinearLayout { } /** - * Set a new Bitmap for the Favicon. - */ - /* package */ void setFavicon(Bitmap icon) { - 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); - mFavicon.setImageDrawable(d); - } - - /** * Change the TitleBar to or from voice mode. If there is no package to * handle voice search, the TitleBar cannot be set to voice mode. */ @@ -302,18 +269,6 @@ public class TitleBar extends LinearLayout { } /** - * Set the Drawable for the lock icon, or null to hide it. - */ - /* package */ void setLock(Drawable d) { - if (null == d) { - mLockIcon.setVisibility(View.GONE); - } else { - mLockIcon.setImageDrawable(d); - mLockIcon.setVisibility(View.VISIBLE); - } - } - - /** * Update the progress, from 0 to 100. */ /* package */ void setProgress(int newProgress) { @@ -374,11 +329,4 @@ public class TitleBar extends LinearLayout { } } } - - /* package */ void setToTabPicker() { - mTitle.setText(R.string.tab_picker_title); - setFavicon(null); - setLock(null); - mHorizontalProgress.setVisibility(View.GONE); - } } diff --git a/src/com/android/browser/TitleBarBase.java b/src/com/android/browser/TitleBarBase.java new file mode 100644 index 0000000..7016dc0 --- /dev/null +++ b/src/com/android/browser/TitleBarBase.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.PaintDrawable; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; + +/** + * Base class for a title bar used by the browser. + */ +public class TitleBarBase extends LinearLayout { + // These need to be set by the subclass. + protected ImageView mFavicon; + protected ImageView mLockIcon; + + protected Drawable mGenericFavicon; + + public TitleBarBase(Context context) { + super(context, null); + mGenericFavicon = context.getResources().getDrawable( + R.drawable.app_web_browser_sm); + } + + /* package */ void setProgress(int newProgress) {} + /* package */ void setDisplayTitle(String title) {} + + /* package */ void setLock(Drawable d) { + assert mLockIcon != null; + if (null == d) { + mLockIcon.setVisibility(View.GONE); + } else { + mLockIcon.setImageDrawable(d); + mLockIcon.setVisibility(View.VISIBLE); + } + } + + /* package */ void setFavicon(Bitmap icon) { + assert mFavicon != null; + 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); + mFavicon.setImageDrawable(d); + } + + /* package */ void setInVoiceMode(boolean inVoiceMode) {} + +} diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java new file mode 100644 index 0000000..fd6d67b --- /dev/null +++ b/src/com/android/browser/TitleBarXLarge.java @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.SearchManager; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.PaintDrawable; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.browser.TabControl.TabChangeListener; +import com.android.browser.UrlInputView.UrlInputListener; + +import java.util.HashMap; +import java.util.Map; + +/** + * tabbed title bar for xlarge screen browser + */ +public class TitleBarXLarge extends TitleBarBase + implements TabChangeListener, UrlInputListener { + + private static final int PROGRESS_MAX = 100; + + private static final int TAB_WIDTH_SELECTED = 400; + private static final int TAB_WIDTH_UNSELECTED = 150; + + private BrowserActivity mBrowserActivity; + private Drawable mStopDrawable; + private Drawable mReloadDrawable; + private Drawable mSelectedBackground; + private Drawable mUnselectedBackground; + + private View mBackButton; + private View mForwardButton; + private View mStar; + private View mMenu; + private View mAllButton; + private TabScrollView mTabs; + private View mNewButton; + private TabControl mControl; + private UrlInputView mUrlView; + + private boolean mIsInLandscape; + private Map<Tab, TabViewData> mTabMap; + + private float mDensityScale; + + public TitleBarXLarge(BrowserActivity context, TabControl tabcontrol) { + super(context); + mDensityScale = context.getResources().getDisplayMetrics().density; + mTabMap = new HashMap<Tab, TabViewData>(); + mBrowserActivity = context; + mControl = tabcontrol; + Resources resources = context.getResources(); + mSelectedBackground = resources.getDrawable(R.drawable.tab_selected_bg); + mUnselectedBackground = resources.getDrawable(R.drawable.tab_unselected_bg); + mStopDrawable = resources.getDrawable(R.drawable.progress_stop); + mReloadDrawable = resources.getDrawable(R.drawable.ic_reload); + rebuildLayout(context, true); + // register the tab change listener + mControl.setOnTabChangeListener(this); + } + + void rebuildLayout() { + rebuildLayout(mBrowserActivity, false); + } + + private void rebuildLayout(Context context, boolean rebuildData) { + removeAllViews(); + LayoutInflater factory = LayoutInflater.from(context); + factory.inflate(R.layout.title_bar_tabbed, this); + + mTabs = (TabScrollView) findViewById(R.id.tabs); + mNewButton = findViewById(R.id.newtab); + mUrlView = (UrlInputView) findViewById(R.id.editurl); + mAllButton = findViewById(R.id.all_btn); + // TODO: Change enabled states based on whether you can go + // back/forward. Probably should be done inside onPageStarted. + mBackButton = findViewById(R.id.back); + mForwardButton = findViewById(R.id.forward); + mStar = findViewById(R.id.star); + mMenu = findViewById(R.id.menu); + View.OnClickListener listener = new View.OnClickListener() { + public void onClick(View v) { + if (mBackButton == v) { + mBrowserActivity.getTopWindow().goBack(); + } else if (mForwardButton == v) { + mBrowserActivity.getTopWindow().goForward(); + } else if (mStar == v) { + mBrowserActivity.promptAddOrInstallBookmark(); + } else if (mMenu == v) { + mBrowserActivity.openOptionsMenu(); + } else if (mAllButton == v) { + // TODO: Show the new bookmarks/windows view. + mBrowserActivity.bookmarksOrHistoryPicker(false); + } else if (mNewButton == v) { + mBrowserActivity.openTabToHomePage(); + } + } + }; + mBackButton.setOnClickListener(listener); + mForwardButton.setOnClickListener(listener); + mStar.setOnClickListener(listener); + mAllButton.setOnClickListener(listener); + mMenu.setOnClickListener(listener); + mNewButton.setOnClickListener(listener); + + mIsInLandscape = mBrowserActivity.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + mUrlView.setVisibility(mIsInLandscape ? View.GONE : View.VISIBLE); + mUrlView.setUrlInputListener(this); + buildTabs(rebuildData); + // ensure title bar state + onCurrentTab(mControl.getCurrentTab()); + } + + void showUrlEditor(TabViewData tabdata) { + mUrlView.setVisibility(View.VISIBLE); + if (mIsInLandscape) { + mTabs.setVisibility(View.GONE); + mUrlView.requestFocus(); + mUrlView.forceIme(); + } + } + + void hideUrlEditor() { + Tab tab = mControl.getCurrentTab(); + if (mIsInLandscape) { + mUrlView.setVisibility(View.GONE); + mTabs.setVisibility(View.VISIBLE); + } else { + // portrait mode + mUrlView.setText(tab.getWebView().getUrl()); + } + tab.getWebView().requestFocus(); + } + + + // UrlInputListener implementation + + @Override + public void onAction(String text) { + hideUrlEditor(); + Intent i = new Intent(); + i.setAction(Intent.ACTION_SEARCH); + i.putExtra(SearchManager.QUERY, text); + mBrowserActivity.onNewIntent(i); + } + + @Override + public void onDismiss() { + hideUrlEditor(); + } + + @Override + public void createContextMenu(ContextMenu menu) { + MenuInflater inflater = mBrowserActivity.getMenuInflater(); + inflater.inflate(R.menu.title_context, menu); + mBrowserActivity.onCreateContextMenu(menu, this, null); + } + + @Override + /* package */ void setLock(Drawable d) { + // TODO: handle in tab specific callback + } + + @Override + /* package */ void setFavicon(Bitmap icon) { + // this is handled in the tab specific callback + } + + /** + * Update the progress, from 0 to 100. + */ + @Override + /* package */ void setProgress(int newProgress) { + // this is handled in tab specific callback + } + + @Override + /* package */ void setDisplayTitle(String title) { + // this is done in tab specific callback + } + + private void buildTabs(boolean needsRebuilding) { + mTabs.clearTabs(); + for (int i = 0; i < mControl.getTabCount(); i++) { + Tab tab = mControl.getTab(i); + TabViewData data = buildTab(needsRebuilding, tab); + TabView tv = buildView(data); + } + mTabs.setSelectedTab(mControl.getCurrentIndex()); + } + + private TabViewData buildTab(boolean needsRebuilding, Tab tab) { + TabViewData data = null; + if (needsRebuilding) { + data = new TabViewData(tab); + mTabMap.put(tab, data); + } else { + data = mTabMap.get(tab); + } + return data; + } + + private TabView buildView(final TabViewData data) { + TabView tv = new TabView(mBrowserActivity, data); + tv.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mTabs.getSelectedTab() == v) { + showUrlEditor(data); + } else { + int ix = mControl.getTabIndex(data.mTab); + mTabs.setSelectedTab(ix); + mBrowserActivity.switchToTab(ix); + } + } + }); + mTabs.addTab(tv); + return tv; + } + + /** + * the views used in the tab bar + */ + class TabView extends LinearLayout { + + TabViewData mTabData; + View mTabContent; + TextView mTitle; + ImageView mIconView; + ImageView mLock; + CircularProgressView mStop; + ImageView mClose; + boolean mSelected; + boolean mInLoad; + + /** + * @param context + */ + public TabView(Context context, TabViewData tab) { + super(context); + mTabData = tab; + LayoutInflater inflater = LayoutInflater.from(mContext); + mTabContent = inflater.inflate(R.layout.tab_title, this); + mTitle = (TextView) mTabContent.findViewById(R.id.title); + mIconView = (ImageView) mTabContent.findViewById(R.id.favicon); + mLock = (ImageView) mTabContent.findViewById(R.id.lock); + mStop = (CircularProgressView) mTabContent.findViewById(R.id.stop); + mStop.setMaxProgress(PROGRESS_MAX); + mClose = (ImageView) mTabContent.findViewById(R.id.close); + mClose.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + closeTab(); + } + }); + mStop.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mInLoad) { + mBrowserActivity.stopLoading(); + } else { + mBrowserActivity.getTopWindow().reload(); + } + } + }); + mSelected = false; + mInLoad = false; + // update the status + updateFromData(); + } + + private void updateFromData() { + mTabData.mTabView = this; + if (mTabData.mUrl != null) { + setDisplayTitle(mTabData.mUrl); + } + if (mTabData.mTitle != null) { + setDisplayTitle(mTabData.mTitle); + } + setProgress(mTabData.mProgress); + if (mTabData.mIcon != null) { + setFavicon(mTabData.mIcon); + } + if (mTabData.mLock != null) { + setLock(mTabData.mLock); + } + } + + @Override + public void setSelected(boolean selected) { + mSelected = selected; + mStop.setVisibility(mSelected ? View.VISIBLE : View.GONE); + mIconView.setVisibility(mSelected ? View.VISIBLE : View.GONE); + super.setSelected(selected); + setBackgroundDrawable(selected ? mSelectedBackground + : mUnselectedBackground); + setLayoutParams(new LayoutParams(selected ? + (int) (TAB_WIDTH_SELECTED * mDensityScale) + : (int) (TAB_WIDTH_UNSELECTED * mDensityScale), + LayoutParams.WRAP_CONTENT)); + } + + void setDisplayTitle(String title) { + mTitle.setText(title); + } + + void setFavicon(Drawable d) { + mIconView.setImageDrawable(d); + } + + void setLock(Drawable d) { + if (null == d) { + mLock.setVisibility(View.GONE); + } else { + mLock.setImageDrawable(d); + mLock.setVisibility(View.VISIBLE); + } + } + + void setTitleCompoundDrawables(Drawable left, Drawable top, + Drawable right, Drawable bottom) { + mTitle.setCompoundDrawables(left, top, right, bottom); + } + + void setProgress(int newProgress) { + mStop.setProgress(newProgress); + if (newProgress >= PROGRESS_MAX) { + mInLoad = false; + mStop.setImageDrawable(mReloadDrawable); + } else { + if (!mInLoad && getWindowToken() != null) { + // checking the window token lets us be sure that we + // are attached to a window before starting the animation, + // preventing a potential race condition + // (fix for bug http://b/2115736) + mInLoad = true; + mStop.setImageDrawable(mStopDrawable); + } + } + } + + private void closeTab() { + if (mTabData.mTab == mControl.getCurrentTab()) { + mBrowserActivity.closeCurrentWindow(); + } else { + mBrowserActivity.closeTab(mTabData.mTab); + } + } + + } + + /** + * class to store tab state within the title bar + */ + class TabViewData { + + Tab mTab; + TabView mTabView; + int mProgress; + Drawable mIcon; + Drawable mLock; + String mTitle; + String mUrl; + + TabViewData(Tab tab) { + mTab = tab; + } + + void setUrlAndTitle(String url, String title) { + mUrl = url; + mTitle = title; + if (mTabView != null) { + if (title != null) { + mTabView.setDisplayTitle(title); + } else if (url != null) { + mTabView.setDisplayTitle(url); + } + } + } + + void setProgress(int newProgress) { + mProgress = newProgress; + if (mTabView != null) { + mTabView.setProgress(mProgress); + } + } + + void setFavicon(Bitmap icon) { + Drawable[] array = new Drawable[3]; + array[0] = new PaintDrawable(Color.BLACK); + array[1] = new PaintDrawable(Color.WHITE); + 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); + mIcon = d; + if (mTabView != null) { + mTabView.setFavicon(mIcon); + } + } + + } + + // TabChangeListener implementation + + @Override + public void onCurrentTab(Tab tab) { + mTabs.setSelectedTab(mControl.getCurrentIndex()); + TabViewData tvd = mTabMap.get(tab); + if (tvd != null) { + if (tvd.mUrl != null) { + mUrlView.setText(tvd.mUrl); + } + setProgress(tvd.mProgress); + } + } + + @Override + public void onFavicon(Tab tab, Bitmap favicon) { + TabViewData tvd = mTabMap.get(tab); + if (tvd != null) { + tvd.setFavicon(favicon); + } + } + + @Override + public void onNewTab(Tab tab) { + TabViewData tvd = buildTab(true, tab); + buildView(tvd); + } + + @Override + public void onProgress(Tab tab, int progress) { + TabViewData tvd = mTabMap.get(tab); + if (tvd != null) { + tvd.setProgress(progress); + } + if (tab == mControl.getCurrentTab()) { + setProgress(progress); + } + } + + @Override + public void onRemoveTab(Tab tab) { + TabViewData tvd = mTabMap.get(tab); + TabView tv = tvd.mTabView; + if (tv != null) { + mTabs.removeTab(tv); + } + mTabMap.remove(tab); + } + + @Override + public void onUrlAndTitle(Tab tab, String url, String title) { + TabViewData tvd = mTabMap.get(tab); + if (tvd != null) { + tvd.setUrlAndTitle(url, title); + } + if ((url != null) && (tab == mControl.getCurrentTab())) { + mUrlView.setText(url); + } + } + + @Override + public void onPageFinished(Tab tab) { + } + + @Override + public void onPageStarted(Tab tab) { + } + +} diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java new file mode 100644 index 0000000..3841257 --- /dev/null +++ b/src/com/android/browser/UrlInputView.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.SearchManager; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AutoCompleteTextView; +import android.widget.CursorAdapter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; + +/** + * url/search input view + * handling suggestions + */ +public class UrlInputView extends AutoCompleteTextView { + + private UrlInputListener mListener; + private InputMethodManager mInputManager; + private SuggestionsAdapter mAdapter; + private Drawable mFocusDrawable; + private Drawable mNoFocusDrawable; + + + public UrlInputView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + public UrlInputView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public UrlInputView(Context context) { + super(context); + init(context); + } + + private void init(Context ctx) { + mFocusDrawable = ctx.getResources().getDrawable(R.drawable.textfield_stroke); + mNoFocusDrawable = ctx.getResources().getDrawable(R.drawable.textfield_nostroke); + mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE); + setOnEditorActionListener(new OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + finishInput(getText().toString()); + return true; + } + }); + setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + setBackgroundDrawable(hasFocus ? mFocusDrawable : mNoFocusDrawable); + } + }); + final ContentResolver cr = mContext.getContentResolver(); + mAdapter = new SuggestionsAdapter(mContext, + BrowserProvider.getBookmarksSuggestions(cr, null)); + setAdapter(mAdapter); + setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + String url = mAdapter.getViewString(view); + finishInput(url); + } + }); + setSelectAllOnFocus(true); + } + + public void setUrlInputListener(UrlInputListener listener) { + mListener = listener; + } + + public void forceIme() { + mInputManager.showSoftInput(this, 0); + } + + private void finishInput(String url) { + this.dismissDropDown(); + mInputManager.hideSoftInputFromWindow(getWindowToken(), 0); + if (url == null) { + mListener.onDismiss(); + } else { + mListener.onAction(url); + } + + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent evt) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + // catch back key in order to do slightly more cleanup than usual + finishInput(null); + return true; + } + return super.onKeyPreIme(keyCode, evt); + } + + interface UrlInputListener { + + public void onDismiss(); + + public void onAction(String text); + + } + + /** + * adapter used by suggestion dropdown + */ + class SuggestionsAdapter extends CursorAdapter implements Filterable { + + private Cursor mLastCursor; + private ContentResolver mContent; + private int mIndexText1; + private int mIndexText2; + private int mIndexIcon; + + public SuggestionsAdapter(Context context, Cursor c) { + super(context, c); + mContent = context.getContentResolver(); + mIndexText1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); + mIndexText2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL); + mIndexIcon = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); + } + + public String getViewString(View view) { + TextView tv2 = (TextView) view.findViewById(android.R.id.text2); + if (tv2.getText().length() > 0) { + return tv2.getText().toString(); + } else { + TextView tv1 = (TextView) view.findViewById(android.R.id.text1); + return tv1.getText().toString(); + } + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + final LayoutInflater inflater = LayoutInflater.from(context); + final View view = inflater.inflate( + R.layout.simple_dropdown_item_2line, parent, false); + bindView(view, context, cursor); + return view; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + TextView tv1 = (TextView) view.findViewById(android.R.id.text1); + TextView tv2 = (TextView) view.findViewById(android.R.id.text2); + ImageView ic1 = (ImageView) view.findViewById(R.id.icon1); + tv1.setText(cursor.getString(mIndexText1)); + String url = cursor.getString(mIndexText2); + tv2.setText((url != null) ? url : ""); + // assume an id + try { + int id = Integer.parseInt(cursor.getString(mIndexIcon)); + Drawable d = context.getResources().getDrawable(id); + ic1.setImageDrawable(d); + } catch (NumberFormatException nfx) { + } + } + + @Override + public String convertToString(Cursor cursor) { + return cursor.getString(mIndexText1); + } + + @Override + public Cursor runQueryOnBackgroundThread(CharSequence constraint) { + if (getFilterQueryProvider() != null) { + return getFilterQueryProvider().runQuery(constraint); + } + mLastCursor = BrowserProvider.getBookmarksSuggestions(mContent, + (constraint != null) ? constraint.toString() : null); + return mLastCursor; + } + + } + +} diff --git a/src/com/android/browser/WebDialog.java b/src/com/android/browser/WebDialog.java new file mode 100644 index 0000000..9995e8f --- /dev/null +++ b/src/com/android/browser/WebDialog.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.content.Context; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.inputmethod.InputMethodManager; +import android.webkit.WebView; +import android.widget.LinearLayout; + +/* package */ class WebDialog extends LinearLayout { + protected WebView mWebView; + protected BrowserActivity mBrowserActivity; + private boolean mIsVisible; + + /* package */ WebDialog(BrowserActivity context) { + super(context); + mBrowserActivity = context; + } + + /* dialogs that have cancel buttons can optionally share code by including a + * view with an id of 'done'. + */ + protected void addCancel() { + View button = findViewById(R.id.done); + if (button != null) button.setOnClickListener(mCancelListener); + } + + private View.OnClickListener mCancelListener = new View.OnClickListener() { + public void onClick(View v) { + mBrowserActivity.closeDialogs(); + } + }; + + protected void dismiss() { + startAnimation(AnimationUtils.loadAnimation(mBrowserActivity, + R.anim.dialog_exit)); + mIsVisible = false; + } + + /* + * Remove the soft keyboard from the screen. + */ + protected void hideSoftInput() { + InputMethodManager imm = (InputMethodManager) + mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mWebView.getWindowToken(), 0); + } + + protected boolean isVisible() { + return mIsVisible; + } + + /* package */ void setWebView(WebView webview) { + mWebView = webview; + } + + protected void show() { + startAnimation(AnimationUtils.loadAnimation(mBrowserActivity, + R.anim.dialog_enter)); + mIsVisible = true; + } + +} diff --git a/tests/src/com/android/browser/PopularUrlsTest.java b/tests/src/com/android/browser/PopularUrlsTest.java index b1760e9..547cb9f 100644 --- a/tests/src/com/android/browser/PopularUrlsTest.java +++ b/tests/src/com/android/browser/PopularUrlsTest.java @@ -307,7 +307,6 @@ public class PopularUrlsTest extends ActivityInstrumentationTestCase2<BrowserAct public void write() throws IOException { FileWriter output = null; - OutputStreamWriter writer = null; if (mFile.exists()) { mFile.delete(); } @@ -317,14 +316,8 @@ public class PopularUrlsTest extends ActivityInstrumentationTestCase2<BrowserAct output.write(page + newLine); output.write(url + newLine); } finally { - try { - if (writer != null) { - writer.close(); - } - } finally { - if (output != null) { - output.close(); - } + if (output != null) { + output.close(); } } } @@ -394,6 +387,7 @@ public class PopularUrlsTest extends ActivityInstrumentationTestCase2<BrowserAct if (mStatus.getIsRecovery()) { Log.e(TAG, "Recovering after crash: " + iterator.next()); + mStatus.incrementPage(); } while (mStatus.getIteration() < loopCount) { diff --git a/tests/src/com/android/browser/TestWebChromeClient.java b/tests/src/com/android/browser/TestWebChromeClient.java index d78eaed..53f8db3 100644 --- a/tests/src/com/android/browser/TestWebChromeClient.java +++ b/tests/src/com/android/browser/TestWebChromeClient.java @@ -195,7 +195,7 @@ abstract class TestWebChromeClient extends WebChromeClient { /** {@inheritDoc} */ @Override - public void openFileChooser(ValueCallback<Uri> uploadFile) { - mWrappedClient.openFileChooser(uploadFile); + public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType) { + mWrappedClient.openFileChooser(uploadFile, acceptType); } } |