Merge korg/donut into korg/master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4b11343..72e486b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -21,7 +21,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher"
android:sharedUserId="android.uid.shared"
- android:sharedUserLabel="@string/application_name">
+ android:sharedUserLabel="@string/uid_name">
<permission
android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
@@ -56,6 +56,7 @@
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.BIND_APPWIDGET" />
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
@@ -90,6 +91,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <activity
+ android:name="GesturesActivity"
+ android:label="@string/gestures_activity" />
<!-- Enable system-default search mode for any activity in Home -->
<meta-data
diff --git a/res/anim/fade_in_fast.xml b/res/anim/fade_in_fast.xml
new file mode 100644
index 0000000..4fa9847
--- /dev/null
+++ b/res/anim/fade_in_fast.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator"
+
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+
+ android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/res/anim/fade_out_fast.xml b/res/anim/fade_out_fast.xml
new file mode 100644
index 0000000..a061a6c
--- /dev/null
+++ b/res/anim/fade_out_fast.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator"
+
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+
+ android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/res/drawable/btn_circle.xml b/res/drawable/btn_circle.xml
new file mode 100644
index 0000000..9208010
--- /dev/null
+++ b/res/drawable/btn_circle.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/btn_circle_normal" />
+ <item android:state_window_focused="false" android:state_enabled="false"
+ android:drawable="@drawable/btn_circle_disable" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_circle_pressed" />
+ <item android:state_focused="true" android:state_enabled="true"
+ android:drawable="@drawable/btn_circle_selected" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/btn_circle_normal" />
+ <item android:state_focused="true"
+ android:drawable="@drawable/btn_circle_disable_focused" />
+ <item
+ android:drawable="@drawable/btn_circle_disable" />
+</selector>
diff --git a/res/drawable/btn_circle_disable.png b/res/drawable/btn_circle_disable.png
new file mode 100644
index 0000000..33b74a6
--- /dev/null
+++ b/res/drawable/btn_circle_disable.png
Binary files differ
diff --git a/res/drawable/btn_circle_disable_focused.png b/res/drawable/btn_circle_disable_focused.png
new file mode 100644
index 0000000..005ad8d
--- /dev/null
+++ b/res/drawable/btn_circle_disable_focused.png
Binary files differ
diff --git a/res/drawable/btn_circle_normal.png b/res/drawable/btn_circle_normal.png
new file mode 100644
index 0000000..fc5af1c
--- /dev/null
+++ b/res/drawable/btn_circle_normal.png
Binary files differ
diff --git a/res/drawable/btn_circle_pressed.png b/res/drawable/btn_circle_pressed.png
new file mode 100644
index 0000000..8f40afd
--- /dev/null
+++ b/res/drawable/btn_circle_pressed.png
Binary files differ
diff --git a/res/drawable/btn_circle_selected.png b/res/drawable/btn_circle_selected.png
new file mode 100644
index 0000000..c74fac2
--- /dev/null
+++ b/res/drawable/btn_circle_selected.png
Binary files differ
diff --git a/res/drawable/gestures_background.xml b/res/drawable/gestures_background.xml
new file mode 100755
index 0000000..34ec051
--- /dev/null
+++ b/res/drawable/gestures_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2009 Romain Guy
+
+ 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.
+-->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/texture_paper"
+ android:tileMode="repeat" />
diff --git a/res/drawable/ic_btn_round_plus.png b/res/drawable/ic_btn_round_plus.png
new file mode 100644
index 0000000..1ec8a95
--- /dev/null
+++ b/res/drawable/ic_btn_round_plus.png
Binary files differ
diff --git a/res/drawable/search_button_bg.9.png b/res/drawable/search_button_bg.9.png
new file mode 100644
index 0000000..b140b24
--- /dev/null
+++ b/res/drawable/search_button_bg.9.png
Binary files differ
diff --git a/res/drawable/search_button_voice.png b/res/drawable/search_button_voice.png
new file mode 100644
index 0000000..4c3d683
--- /dev/null
+++ b/res/drawable/search_button_voice.png
Binary files differ
diff --git a/res/drawable/search_floater.9.png b/res/drawable/search_floater.9.png
new file mode 100644
index 0000000..a2007ad
--- /dev/null
+++ b/res/drawable/search_floater.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget.xml b/res/drawable/textfield_searchwidget.xml
new file mode 100644
index 0000000..80f3dca
--- /dev/null
+++ b/res/drawable/textfield_searchwidget.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/textfield_searchwidget_default" />
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/textfield_searchwidget_pressed" />
+
+ <item android:state_enabled="true" android:state_focused="true"
+ android:drawable="@drawable/textfield_searchwidget_selected" />
+
+ <item android:state_enabled="true"
+ android:drawable="@drawable/textfield_searchwidget_default" />
+
+</selector>
diff --git a/res/drawable/textfield_searchwidget_default.9.png b/res/drawable/textfield_searchwidget_default.9.png
new file mode 100644
index 0000000..247abca
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_default.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget_pressed.9.png b/res/drawable/textfield_searchwidget_pressed.9.png
new file mode 100644
index 0000000..d628cbc
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_pressed.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget_selected.9.png b/res/drawable/textfield_searchwidget_selected.9.png
new file mode 100644
index 0000000..9e93527
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_selected.9.png
Binary files differ
diff --git a/res/drawable/texture_paper.jpg b/res/drawable/texture_paper.jpg
new file mode 100644
index 0000000..27f4fd6
--- /dev/null
+++ b/res/drawable/texture_paper.jpg
Binary files differ
diff --git a/res/drawable/wallpaper_frog.jpg b/res/drawable/wallpaper_frog.jpg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/drawable/wallpaper_frog.jpg
diff --git a/res/drawable/wallpaper_frog_small.jpg b/res/drawable/wallpaper_frog_small.jpg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/drawable/wallpaper_frog_small.jpg
diff --git a/res/drawable/wallpaper_lake.jpg b/res/drawable/wallpaper_lake.jpg
new file mode 100644
index 0000000..5ba522f
--- /dev/null
+++ b/res/drawable/wallpaper_lake.jpg
Binary files differ
diff --git a/res/drawable/wallpaper_turtle.jpg b/res/drawable/wallpaper_turtle.jpg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/drawable/wallpaper_turtle.jpg
diff --git a/res/drawable/wallpaper_turtle_small.jpg b/res/drawable/wallpaper_turtle_small.jpg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/drawable/wallpaper_turtle_small.jpg
diff --git a/res/layout-land/live_folder_grid.xml b/res/layout-land/live_folder_grid.xml
index d1b02a4..bb623b0 100644
--- a/res/layout-land/live_folder_grid.xml
+++ b/res/layout-land/live_folder_grid.xml
@@ -36,6 +36,7 @@
android:layout_height="0dip"
android:layout_weight="1"
+ android:cacheColorHint="#ff333333"
android:background="@drawable/box_launcher_bottom"
android:scrollbarAlwaysDrawVerticalTrack="true"
diff --git a/res/layout-land/user_folder.xml b/res/layout-land/user_folder.xml
index 61f859e..fbf337b 100644
--- a/res/layout-land/user_folder.xml
+++ b/res/layout-land/user_folder.xml
@@ -33,6 +33,7 @@
android:layout_height="0dip"
android:layout_weight="1"
+ android:cacheColorHint="#ff333333"
android:background="@drawable/box_launcher_bottom"
android:scrollbarAlwaysDrawVerticalTrack="true"
diff --git a/res/layout-port/live_folder_grid.xml b/res/layout-port/live_folder_grid.xml
index ec32d41..e5bcafe 100644
--- a/res/layout-port/live_folder_grid.xml
+++ b/res/layout-port/live_folder_grid.xml
@@ -36,6 +36,7 @@
android:layout_height="0dip"
android:layout_weight="1"
+ android:cacheColorHint="#ff333333"
android:background="@drawable/box_launcher_bottom"
android:scrollbarAlwaysDrawVerticalTrack="true"
diff --git a/res/layout-port/user_folder.xml b/res/layout-port/user_folder.xml
index 5795aea..27d70fc 100644
--- a/res/layout-port/user_folder.xml
+++ b/res/layout-port/user_folder.xml
@@ -33,6 +33,7 @@
android:layout_height="0dip"
android:layout_weight="1"
+ android:cacheColorHint="#ff333333"
android:background="@drawable/box_launcher_bottom"
android:scrollbarAlwaysDrawVerticalTrack="true"
diff --git a/res/layout/gestures.xml b/res/layout/gestures.xml
new file mode 100644
index 0000000..c9ea2a5
--- /dev/null
+++ b/res/layout/gestures.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Note: GesturesPanel is a special implementation that forces the widget
+ to be opaque for performance reasons. Make sure it visually is. -->
+<com.android.launcher.GesturesPanel
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:id="@+id/gestures_panel"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ViewSwitcher
+ android:id="@+id/gestures_actions"
+ android:layout_width="fill_parent"
+ android:layout_height="83dip"
+ android:layout_alignParentBottom="true"
+
+ android:inAnimation="@anim/fade_in_fast"
+ android:outAnimation="@anim/fade_out_fast"
+
+ android:foregroundGravity="top|fill_horizontal"
+ android:foreground="@*android:drawable/title_bar_shadow"
+ android:background="@android:drawable/title_bar_tall">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_gravity="center_horizontal"
+
+ android:gravity="center_vertical"
+
+ android:shadowColor="#FF000000"
+ android:shadowRadius="2.0"
+
+ android:drawablePadding="8dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/gestures_instructions" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_gravity="center_horizontal"
+
+ android:gravity="center_vertical"
+
+ android:shadowColor="#FF000000"
+ android:shadowRadius="2.0"
+
+ android:drawablePadding="8dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/gestures_instructions" />
+
+ </ViewSwitcher>
+
+ <android.gesture.GestureOverlayView
+ android:id="@+id/gestures_overlay"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0"
+ android:layout_alignParentTop="true"
+ android:layout_above="@id/gestures_actions"
+
+ android:background="@drawable/gestures_background"
+
+ android:gestureStrokeType="multiple" />
+
+ <ImageButton
+ style="@style/PlusButton"
+
+ android:id="@+id/gestures_add"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignTop="@id/gestures_actions"
+ android:layout_marginRight="5dip"
+ android:layout_marginTop="-47dip" />
+
+</com.android.launcher.GesturesPanel>
diff --git a/res/layout/gestures_settings.xml b/res/layout/gestures_settings.xml
new file mode 100644
index 0000000..4b1976f
--- /dev/null
+++ b/res/layout/gestures_settings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+
+ <TextView
+ android:id="@android:id/empty"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+
+ android:gravity="center_horizontal"
+
+ android:text="@string/gestures_loading"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</FrameLayout>
diff --git a/res/layout/gestures_settings_item.xml b/res/layout/gestures_settings_item.xml
new file mode 100644
index 0000000..1563dfe
--- /dev/null
+++ b/res/layout/gestures_settings_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+
+ android:drawablePadding="12dip"
+ android:paddingLeft="6dip"
+ android:paddingRight="6dip"
+
+ android:ellipsize="marquee"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
diff --git a/res/layout/live_folder_list.xml b/res/layout/live_folder_list.xml
index 1d32f88..f96a190 100644
--- a/res/layout/live_folder_list.xml
+++ b/res/layout/live_folder_list.xml
@@ -36,7 +36,7 @@
android:layout_height="0dip"
android:layout_weight="1"
- android:cacheColorHint="#00000000"
+ android:cacheColorHint="#ff333333"
android:background="@drawable/box_launcher_bottom" />
</com.android.launcher.LiveFolder>
diff --git a/res/layout/rename_folder.xml b/res/layout/rename_folder.xml
index 2c578f3..ba78995 100644
--- a/res/layout/rename_folder.xml
+++ b/res/layout/rename_folder.xml
@@ -21,6 +21,7 @@
android:orientation="vertical">
<TextView
+ android:id="@+id/label"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/rename_folder_label"
diff --git a/res/layout/widget_search.xml b/res/layout/widget_search.xml
index 1db8488..7ecf5bf 100644
--- a/res/layout/widget_search.xml
+++ b/res/layout/widget_search.xml
@@ -14,52 +14,47 @@
limitations under the License.
-->
-<com.android.launcher.Search xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher.Search
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
+ android:id="@+id/widget_search"
android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="horizontal"
- android:background="@drawable/search_bg"
- android:gravity="center_vertical"
- >
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="top">
- <ImageView
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:id="@+id/search_plate"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:paddingRight="3dip"
- android:src="@drawable/google_logo" />
-
- <com.android.launcher.SearchAutoCompleteTextView
- android:id="@+id/input"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:layout_marginTop="1dip"
- android:hint="@string/search_hint"
- android:focusableInTouchMode="false"
- android:singleLine="true"
- android:selectAllOnFocus="true"
- android:completionThreshold="1"
- android:inputType="textAutoComplete"
- android:imeOptions="actionSearch"
- android:lines="1"
- android:dropDownWidth="fill_parent"
- android:popupBackground="@drawable/spinner_dropdown_background"
+ android:orientation="horizontal"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+ android:paddingTop="7dip"
+ android:paddingBottom="13dip"
+ android:background="@drawable/search_floater" >
+
+ <TextView
+ android:id="@+id/search_src_text"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0"
+ android:editable="false"
+ android:inputType="none"
+ android:background="@drawable/textfield_searchwidget"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse"
+ android:textColor="@android:color/primary_text_light"
/>
- <ImageButton android:id="@+id/search_go_btn"
- android:layout_marginLeft="5dip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@*android:drawable/ic_btn_search"
- style="@style/SearchButton"
- />
-
- <ImageButton android:id="@+id/search_voice_btn"
- android:layout_marginLeft="4dip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@android:drawable/ic_btn_speak_now"
- style="@style/SearchButton"
- />
+ <ImageButton
+ android:id="@+id/search_voice_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_marginLeft="8dip"
+ android:background="@*android:drawable/btn_search_dialog_voice"
+ android:src="@*android:drawable/ic_btn_speak_now"
+ />
+
+ </LinearLayout>
</com.android.launcher.Search>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 7832ceb..f0cc369 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -26,20 +26,27 @@
<string name="rename_action">"OK"</string>
<string name="cancel_action">"Zrušit"</string>
<string name="menu_item_add_item">"Přidat na plochu"</string>
- <string name="group_applications">"Aplikace"</string>
- <string name="group_shortcuts">"Klávesové zkratky"</string>
+ <!-- no translation found for group_applications (4118484163419674240) -->
+ <skip />
+ <!-- no translation found for group_shortcuts (9133529424900391877) -->
+ <skip />
<string name="group_search">"Hledat"</string>
- <string name="group_folder">"Nová složka"</string>
- <string name="group_live_folders">"Složky"</string>
- <string name="group_widgets">"Widgety"</string>
- <string name="group_wallpapers">"Tapety"</string>
+ <!-- no translation found for group_folder (5143593791798929193) -->
+ <skip />
+ <!-- no translation found for group_live_folders (2664945399140647217) -->
+ <skip />
+ <!-- no translation found for group_widgets (6704978494073105844) -->
+ <skip />
+ <!-- no translation found for group_wallpapers (1568191644272224858) -->
+ <skip />
<string name="add_folder">"Složka"</string>
<string name="add_clock">"Hodiny"</string>
<string name="add_photo_frame">"Rámeček fotografie"</string>
<string name="add_search">"Vyhledávání"</string>
<string name="out_of_space">"Na této ploše již není místo."</string>
<string name="title_select_shortcut">"Vyberte zástupce"</string>
- <string name="title_select_live_folder">"Vybrat složku"</string>
+ <!-- no translation found for title_select_live_folder (3753447798805166749) -->
+ <skip />
<string name="menu_add">"Přidat"</string>
<string name="menu_wallpaper">"Tapeta"</string>
<string name="menu_search">"Hledat"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 9b39946..40f3cab 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -25,21 +25,28 @@
<string name="rename_folder_title">"Ordner umbenennen"</string>
<string name="rename_action">"OK"</string>
<string name="cancel_action">"Abbrechen"</string>
- <string name="menu_item_add_item">"Zum Startbildschirm hinzufügen"</string>
- <string name="group_applications">"Anwendungen"</string>
- <string name="group_shortcuts">"Verknüpfungen"</string>
+ <string name="menu_item_add_item">"Zur Startseite hinzufügen"</string>
+ <!-- no translation found for group_applications (4118484163419674240) -->
+ <skip />
+ <!-- no translation found for group_shortcuts (9133529424900391877) -->
+ <skip />
<string name="group_search">"Suchen"</string>
- <string name="group_folder">"Neuer Ordner"</string>
- <string name="group_live_folders">"Ordner"</string>
- <string name="group_widgets">"Widgets"</string>
- <string name="group_wallpapers">"Hintergrundbilder"</string>
+ <!-- no translation found for group_folder (5143593791798929193) -->
+ <skip />
+ <!-- no translation found for group_live_folders (2664945399140647217) -->
+ <skip />
+ <!-- no translation found for group_widgets (6704978494073105844) -->
+ <skip />
+ <!-- no translation found for group_wallpapers (1568191644272224858) -->
+ <skip />
<string name="add_folder">"Ordner"</string>
<string name="add_clock">"Uhr"</string>
<string name="add_photo_frame">"Bildrahmen"</string>
<string name="add_search">"Suchen"</string>
- <string name="out_of_space">"Auf dem Startbildschirm ist kein Platz mehr vorhanden."</string>
+ <string name="out_of_space">"Auf der Startseite ist kein Platz mehr vorhanden."</string>
<string name="title_select_shortcut">"Tastenkürzel auswählen"</string>
- <string name="title_select_live_folder">"Ordner auswählen"</string>
+ <!-- no translation found for title_select_live_folder (3753447798805166749) -->
+ <skip />
<string name="menu_add">"Hinzufügen"</string>
<string name="menu_wallpaper">"Hintergrund"</string>
<string name="menu_search">"Suchen"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..221ab86
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name">"Página principal"</string>
+ <string name="folder_name">"Carpeta"</string>
+ <string name="chooser_wallpaper">"Seleccionar papel tapiz desde"</string>
+ <string name="wallpaper_instructions">"Definir papel tapiz"</string>
+ <string name="pick_wallpaper">"Galería de papel tapiz"</string>
+ <string name="activity_not_found">"La aplicación no está instalada en tu computadora."</string>
+ <string name="rename_folder_label">"Nombre de carpeta"</string>
+ <string name="rename_folder_title">"Cambiar nombre de carpeta"</string>
+ <string name="rename_action">"Aceptar"</string>
+ <string name="cancel_action">"Cancelar"</string>
+ <string name="menu_item_add_item">"Agregar a la pantalla Página principal"</string>
+ <!-- no translation found for group_applications (4118484163419674240) -->
+ <skip />
+ <!-- no translation found for group_shortcuts (9133529424900391877) -->
+ <skip />
+ <string name="group_search">"Buscar"</string>
+ <!-- no translation found for group_folder (5143593791798929193) -->
+ <skip />
+ <!-- no translation found for group_live_folders (2664945399140647217) -->
+ <skip />
+ <!-- no translation found for group_widgets (6704978494073105844) -->
+ <skip />
+ <!-- no translation found for group_wallpapers (1568191644272224858) -->
+ <skip />
+ <string name="add_folder">"Carpeta"</string>
+ <string name="add_clock">"Reloj"</string>
+ <string name="add_photo_frame">"Marco de imagen"</string>
+ <string name="add_search">"Buscar"</string>
+ <string name="out_of_space">"No hay más espacio en esta pantalla de la página principal"</string>
+ <!-- no translation found for shortcut_installed (6623689857689040689) -->
+ <skip />
+ <!-- no translation found for shortcut_uninstalled (2758997515869993129) -->
+ <skip />
+ <!-- no translation found for shortcut_duplicate (4887186782641532074) -->
+ <skip />
+ <string name="title_select_shortcut">"Seleccionar acceso directo"</string>
+ <!-- no translation found for title_select_live_folder (3753447798805166749) -->
+ <skip />
+ <string name="menu_add">"Agregar"</string>
+ <string name="menu_wallpaper">"Papel tapiz"</string>
+ <string name="menu_search">"Buscar"</string>
+ <string name="menu_notifications">"Notificaciones"</string>
+ <string name="menu_settings">"Configuración"</string>
+ <string name="permlab_install_shortcut">"instalar accesos directos"</string>
+ <string name="permdesc_install_shortcut">"Permite a una aplicación agregar accesos directos sin intervención del usuario."</string>
+ <string name="permlab_uninstall_shortcut">"desinstalar papel tapiz"</string>
+ <string name="permdesc_uninstall_shortcut">"Permite a una aplicación suprimir accesos directos sin intervención del usuario."</string>
+ <string name="permlab_read_settings">"leer configuración y accesos directos de la página principal"</string>
+ <string name="permdesc_read_settings">"Permite a una aplicación leer la configuración y los accesos directos de la página principal."</string>
+ <string name="permlab_write_settings">"escribir configuración y accesos directos de la página principal"</string>
+ <string name="permdesc_write_settings">"Permite a una aplicación cambiar la configuración y los accesos directos de la página principal."</string>
+ <string name="search_hint">"Búsqueda de Google"</string>
+ <string name="gadget_error_text">"Problema al cargar el widget"</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index e0f01be..7558565 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -36,12 +36,12 @@
<string name="add_folder">"Dossier"</string>
<string name="add_clock">"Horloge"</string>
<string name="add_photo_frame">"Cadre d\'image"</string>
- <string name="add_search">"Recherche"</string>
+ <string name="add_search">"Rechercher"</string>
<string name="out_of_space">"Plus d\'espace libre sur l\'écran d\'accueil."</string>
<string name="title_select_shortcut">"Sélectionner un raccourci"</string>
<string name="title_select_live_folder">"Sélectionner le dossier"</string>
<string name="menu_add">"Ajouter"</string>
- <string name="menu_wallpaper">"Fond d\'écran"</string>
+ <string name="menu_wallpaper">"Arrière-plan"</string>
<string name="menu_search">"Rechercher"</string>
<string name="menu_notifications">"Notifications"</string>
<string name="menu_settings">"Paramètres"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index e6815dd..954b5d7 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -26,20 +26,27 @@
<string name="rename_action">"OK"</string>
<string name="cancel_action">"Avbryt"</string>
<string name="menu_item_add_item">"Legg til skrivebord"</string>
- <string name="group_applications">"Applikasjoner"</string>
- <string name="group_shortcuts">"Snarveier"</string>
+ <!-- no translation found for group_applications (4118484163419674240) -->
+ <skip />
+ <!-- no translation found for group_shortcuts (9133529424900391877) -->
+ <skip />
<string name="group_search">"Søk"</string>
- <string name="group_folder">"Ny mappe"</string>
- <string name="group_live_folders">"Mapper"</string>
- <string name="group_widgets">"Skrivebordselementer"</string>
- <string name="group_wallpapers">"Bakgrunner"</string>
+ <!-- no translation found for group_folder (5143593791798929193) -->
+ <skip />
+ <!-- no translation found for group_live_folders (2664945399140647217) -->
+ <skip />
+ <!-- no translation found for group_widgets (6704978494073105844) -->
+ <skip />
+ <!-- no translation found for group_wallpapers (1568191644272224858) -->
+ <skip />
<string name="add_folder">"Mappe"</string>
<string name="add_clock">"Klokke"</string>
<string name="add_photo_frame">"Bilderamme"</string>
<string name="add_search">"Søk"</string>
<string name="out_of_space">"Ikke nok plass på skrivebordet."</string>
<string name="title_select_shortcut">"Velg snarvei"</string>
- <string name="title_select_live_folder">"Velg mappe"</string>
+ <!-- no translation found for title_select_live_folder (3753447798805166749) -->
+ <skip />
<string name="menu_add">"Legg til"</string>
<string name="menu_wallpaper">"Bakgrunnsbilde"</string>
<string name="menu_search">"Søk"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 0000000..5fe05ce
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name">"Página Inicial do Google"</string>
+ <string name="folder_name">"Pasta"</string>
+ <string name="chooser_wallpaper">"Selecionar papel de parede da"</string>
+ <string name="wallpaper_instructions">"Definir papel de parede"</string>
+ <string name="pick_wallpaper">"Galeria de papéis de parede"</string>
+ <string name="activity_not_found">"O aplicativo não está instalado no seu telefone."</string>
+ <string name="rename_folder_label">"Nome da pasta"</string>
+ <string name="rename_folder_title">"Renomear pasta"</string>
+ <string name="rename_action">"OK"</string>
+ <string name="cancel_action">"Cancelar"</string>
+ <string name="menu_item_add_item">"Adicionar à tela inicial"</string>
+ <!-- no translation found for group_applications (4118484163419674240) -->
+ <skip />
+ <!-- no translation found for group_shortcuts (9133529424900391877) -->
+ <skip />
+ <!-- no translation found for group_search (5905328940867162196) -->
+ <skip />
+ <!-- no translation found for group_folder (5143593791798929193) -->
+ <skip />
+ <!-- no translation found for group_live_folders (2664945399140647217) -->
+ <skip />
+ <!-- no translation found for group_widgets (6704978494073105844) -->
+ <skip />
+ <!-- no translation found for group_wallpapers (1568191644272224858) -->
+ <skip />
+ <string name="add_folder">"Pasta"</string>
+ <string name="add_clock">"Relógio"</string>
+ <string name="add_photo_frame">"Moldura"</string>
+ <string name="add_search">"Pesquisar"</string>
+ <string name="out_of_space">"Não há mais espaço nesta página inicial."</string>
+ <!-- no translation found for shortcut_installed (6623689857689040689) -->
+ <skip />
+ <!-- no translation found for shortcut_uninstalled (2758997515869993129) -->
+ <skip />
+ <!-- no translation found for shortcut_duplicate (4887186782641532074) -->
+ <skip />
+ <!-- no translation found for title_select_shortcut (2858897527672831763) -->
+ <skip />
+ <!-- no translation found for title_select_live_folder (3753447798805166749) -->
+ <skip />
+ <string name="menu_add">"Adicionar"</string>
+ <string name="menu_wallpaper">"Papel de parede"</string>
+ <string name="menu_search">"Pesquisar"</string>
+ <string name="menu_notifications">"Notificações"</string>
+ <!-- no translation found for menu_gestures (514678675575912237) -->
+ <skip />
+ <string name="menu_settings">"Configurações"</string>
+ <string name="permlab_install_shortcut">"instalar atalhos"</string>
+ <string name="permdesc_install_shortcut">"Permite que um aplicativo adicione atalhos sem a intervenção do usuário."</string>
+ <string name="permlab_uninstall_shortcut">"desinstalar atalhos"</string>
+ <string name="permdesc_uninstall_shortcut">"Permite que um aplicativo remova atalhos sem a intervenção do usuário."</string>
+ <string name="permlab_read_settings">"leia as configurações e atalhos da página inicial"</string>
+ <string name="permdesc_read_settings">"Permite que um aplicativo leia as configurações e atalhos na página inicial."</string>
+ <string name="permlab_write_settings">"grave as configurações e atalhos da página inicial"</string>
+ <string name="permdesc_write_settings">"Permite que um aplicativo altere as configurações e atalhos na página inicial."</string>
+ <string name="search_hint">"Google Search"</string>
+ <!-- no translation found for gadget_error_text (8359351016167075858) -->
+ <skip />
+ <!-- no translation found for gestures_instructions (1099425576530504561) -->
+ <skip />
+ <!-- no translation found for gestures_unknown (309008617423088837) -->
+ <skip />
+ <!-- no translation found for gestures_created (8116808500193613741) -->
+ <skip />
+ <!-- no translation found for gestures_failed (5113829215530311557) -->
+ <skip />
+ <!-- no translation found for gestures_loading (6325241735907189384) -->
+ <skip />
+ <!-- no translation found for gestures_empty (8923837437173732411) -->
+ <skip />
+ <!-- no translation found for gestures_activity (3366108186921084558) -->
+ <skip />
+ <!-- no translation found for gestures_rename (713510200890627175) -->
+ <skip />
+ <!-- no translation found for gestures_delete (6962559975386260201) -->
+ <skip />
+ <!-- no translation found for gestures_delete_success (2961655691848002690) -->
+ <skip />
+ <!-- no translation found for gestures_rename_title (7463326669074661915) -->
+ <skip />
+ <!-- no translation found for gestures_rename_label (8790363956270703745) -->
+ <skip />
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 775eb20..3da3b5b 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -26,34 +26,27 @@
<string name="rename_action">"確定"</string>
<string name="cancel_action">"取消"</string>
<string name="menu_item_add_item">"新增至首頁畫面"</string>
- <!-- no translation found for group_applications (4118484163419674240) -->
- <skip />
- <!-- no translation found for group_shortcuts (9133529424900391877) -->
- <skip />
+ <string name="group_applications">"應用程式"</string>
+ <string name="group_shortcuts">"捷徑"</string>
<string name="group_search">"搜尋"</string>
- <!-- no translation found for group_folder (5143593791798929193) -->
- <skip />
- <!-- no translation found for group_live_folders (2664945399140647217) -->
- <skip />
- <!-- no translation found for group_widgets (6704978494073105844) -->
- <skip />
- <!-- no translation found for group_wallpapers (1568191644272224858) -->
- <skip />
+ <string name="group_folder">"新資料夾"</string>
+ <string name="group_live_folders">"資料夾"</string>
+ <string name="group_widgets">"小工具"</string>
+ <string name="group_wallpapers">"桌布"</string>
<string name="add_folder">"資料夾"</string>
<string name="add_clock">"時鐘"</string>
<string name="add_photo_frame">"相框"</string>
<string name="add_search">"搜尋"</string>
<string name="out_of_space">"首頁已無空間"</string>
<string name="title_select_shortcut">"選取捷徑"</string>
- <!-- no translation found for title_select_live_folder (3753447798805166749) -->
- <skip />
+ <string name="title_select_live_folder">"選取資料夾"</string>
<string name="menu_add">"新增"</string>
<string name="menu_wallpaper">"桌布"</string>
<string name="menu_search">"搜尋"</string>
- <string name="menu_notifications">"通知選項"</string>
+ <string name="menu_notifications">"通知"</string>
<string name="menu_settings">"設定"</string>
<string name="permlab_install_shortcut">"安裝捷徑"</string>
- <string name="permdesc_install_shortcut">"允許應用程式自動新增捷徑。"</string>
+ <string name="permdesc_install_shortcut">"允許應用程式自動新增快速鍵。"</string>
<string name="permlab_uninstall_shortcut">"解除安裝捷徑"</string>
<string name="permdesc_uninstall_shortcut">"允許應用程式自動移除捷徑。"</string>
<string name="permlab_read_settings">"讀取首頁設定和捷徑"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5574944..e1b4843 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -23,5 +23,8 @@
<color name="bubble_dark_background">#B2191919</color>
<color name="delete_color_filter">#A5FF0000</color>
- <color name="appwidget_error_color">#fccc</color>
+ <color name="appwidget_error_color">#FCCC</color>
+ <color name="snag_callout_color">#F444</color>
+
+ <color name="gesture_color">#FFFFFF00</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4ae6686..b802353 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -16,4 +16,6 @@
<resources>
<dimen name="search_widget_inset">19dip</dimen>
+ <dimen name="gesture_thumbnail_inset">8dip</dimen>
+ <dimen name="gesture_thumbnail_size">64dip</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b0421b2..e43459d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -22,6 +22,8 @@
<skip />
<!-- Application name -->
<string name="application_name">Home</string>
+ <!-- Name for all applications running as this uid. -->
+ <string name="uid_name">Android Core Apps</string>
<!-- Default folder name -->
<string name="folder_name">Folder</string>
<!-- Title of dialog that appears after user selects Wallpaper from menu -->
@@ -68,10 +70,16 @@
<string name="add_clock">Clock</string>
<!-- Options in "Add to Home" dialog box; Name of the Picture frame widget-->
<string name="add_photo_frame">Picture frame</string>
- <!-- Options in "Add to Home" dialog box; Name of the Google Search widget-->
+ <!-- Options in "Add to Home" dialog box; Name of the global search widget-->
<string name="add_search">Search</string>
<!-- Error message when user has filled a home screen, possibly not used -->
<string name="out_of_space">No more room on this Home screen.</string>
+ <!-- Message displayed when a shortcut is created by an external application -->
+ <string name="shortcut_installed">Shortcut \"%s\" is installed.</string>
+ <!-- Message displayed when a shortcut is uninstalled by an external application -->
+ <string name="shortcut_uninstalled">Shortcut \"%s\" was removed.</string>
+ <!-- Message displayed when an external application attemps to create a shortcut that already exists -->
+ <string name="shortcut_duplicate">Shortcut \"%s\" already exists.</string>
<!-- Title of dialog when user is selecting shortcut to add to homescreen -->
<string name="title_select_shortcut">Select shortcut</string>
@@ -84,11 +92,13 @@
<string name="menu_add">Add</string>
<!-- Noun, menu item used to set the desktop's wallpaper -->
<string name="menu_wallpaper">Wallpaper</string>
- <!-- Verb, menu item used to initiate a Google Search -->
+ <!-- Verb, menu item used to initiate global search -->
<string name="menu_search">Search</string>
<!-- Noun, menu item used to bring down the notifications shade -->
<string name="menu_notifications">Notifications</string>
- <!-- Noun, menu item used to show the system settings -->
+ <!-- Noun, menu item used to show the gestures settings -->
+ <string name="menu_gestures">Gestures</string>
+ <!-- Noun, menu item used to show the system settings -->
<string name="menu_settings">Settings</string>
<!-- Permissions: -->
@@ -107,7 +117,9 @@
<!-- Widgets: -->
<skip />
- <!-- This is the hint text shown in the search widget, before text is entered.
+
+ <!-- TODO: Determine if this can be removed.
+ This is the hint text shown in the search widget, before text is entered.
This translation SHOULD MATCH the string "search_hint" which is found in
GoogleSearch/res/values/strings.xml -->
<string name="search_hint">Google Search</string>
@@ -115,4 +127,43 @@
<!-- Text to show user in place of a gadget when we can't display it properly -->
<string name="gadget_error_text">Problem loading widget</string>
+ <!-- Gestures: -->
+ <skip />
+
+ <!-- Message displayed when the user enters gestures mode and is asked to draw a gesture -->
+ <string name="gestures_instructions">Draw a gesture</string>
+ <!-- Label of the button that the user clicks to create a new gesture-based shortcut -->
+ <string name="gestures_add">Add shortcut</string>
+ <!-- Label of the button that the user clicks to see the list of gesture-based shortcuts -->
+ <string name="gestures_show">Show shortcut</string>
+ <!-- Message displayed when the gesture entered by the user cannot be recognized -->
+ <string name="gestures_unknown">Unrecognized</string>
+ <!-- Message displayed when the user has successfully created a new gesture -->
+ <string name="gestures_created">Added gesture "%s"</string>
+ <!-- Message displayed when the user could not create a new gesture -->
+ <string name="gestures_failed">Gesture could not be created</string>
+ <!-- Message displayed when the user opens the gestures settings screen -->
+ <string name="gestures_loading">Loading gestures...</string>
+ <!-- Message displayed when the user has no gestures -->
+ <string name="gestures_empty">No gestures</string>
+ <!-- Title of the screen used to view/manage gestures -->
+ <string name="gestures_activity">Gestures</string>
+ <!-- Noun, menu item used to rename a gesture -->
+ <string name="gestures_rename">Rename</string>
+ <!-- Noun, menu item used to remove a gesture -->
+ <string name="gestures_delete">Delete</string>
+ <!-- Message displayed when a gesture is successfully deleted -->
+ <string name="gestures_delete_success">Gesture deleted</string>
+ <!-- Title of dialog box -->
+ <string name="gestures_rename_title">Rename gesture</string>
+ <!-- Label of gesture name field in Rename gesture dialog box -->
+ <string name="gestures_rename_label">Gesture name</string>
+ <!-- Title of the preferences group showing the user's gestures -->
+ <string name="gestures_group_gestures">Gestures</string>
+ <!-- Title of the preferences group showing the gestures settings -->
+ <string name="gestures_group_settings">Settings</string>
+ <!-- Label of the setting used to enable/disable the home key to draw gestures -->
+ <string name="gestures_preference_hotkey_title">Use home to launch</string>
+ <!-- Summary of the setting used to enable/disable the home key to draw gestures -->
+ <string name="gestures_preference_hotkey_summary">Press home key to draw gestures</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 9b06d26..5319bb0 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -60,4 +60,10 @@
<item name="android:paddingLeft">10dip</item>
<item name="android:paddingRight">10dip</item>
</style>
+
+ <style name="PlusButton">
+ <item name="android:background">@drawable/btn_circle</item>
+ <item name="android:src">@drawable/ic_btn_round_plus</item>
+ </style>
+
</resources>
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace.xml
index ae2e2a6..9197c30 100644
--- a/res/xml/default_workspace.xml
+++ b/res/xml/default_workspace.xml
@@ -16,11 +16,6 @@
<favorites xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher">
- <clock
- launcher:screen="2"
- launcher:x="1"
- launcher:y="0" />
-
<search
launcher:screen="1"
launcher:x="0"
diff --git a/src/com/android/launcher/ApplicationInfo.java b/src/com/android/launcher/ApplicationInfo.java
index 9bc0950..cee9f3b 100644
--- a/src/com/android/launcher/ApplicationInfo.java
+++ b/src/com/android/launcher/ApplicationInfo.java
@@ -61,7 +61,7 @@
Intent.ShortcutIconResource iconResource;
ApplicationInfo() {
- itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
}
public ApplicationInfo(ApplicationInfo info) {
@@ -80,7 +80,7 @@
/**
* Creates the application intent based on a component name and various launch flags.
- * Sets {@link #itemType} to {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}.
+ * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
*
* @param className the class name of the component representing the intent
* @param launchFlags the launch flags
@@ -90,7 +90,7 @@
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setComponent(className);
intent.setFlags(launchFlags);
- itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
}
@Override
@@ -98,22 +98,24 @@
super.onAddToDatabase(values);
String titleStr = title != null ? title.toString() : null;
- values.put(LauncherSettings.Favorites.TITLE, titleStr);
+ values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
- String uri = intent != null ? intent.toURI() : null;
- values.put(LauncherSettings.Favorites.INTENT, uri);
+ String uri = intent != null ? intent.toUri(0) : null;
+ values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
if (customIcon) {
- values.put(LauncherSettings.Favorites.ICON_TYPE,
- LauncherSettings.Favorites.ICON_TYPE_BITMAP);
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+ LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP);
Bitmap bitmap = ((FastBitmapDrawable) icon).getBitmap();
writeBitmap(values, bitmap);
} else {
- values.put(LauncherSettings.Favorites.ICON_TYPE,
- LauncherSettings.Favorites.ICON_TYPE_RESOURCE);
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+ LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
if (iconResource != null) {
- values.put(LauncherSettings.Favorites.ICON_PACKAGE, iconResource.packageName);
- values.put(LauncherSettings.Favorites.ICON_RESOURCE, iconResource.resourceName);
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
+ iconResource.packageName);
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+ iconResource.resourceName);
}
}
}
diff --git a/src/com/android/launcher/CellLayout.java b/src/com/android/launcher/CellLayout.java
index 91f0420..73dbb3e 100644
--- a/src/com/android/launcher/CellLayout.java
+++ b/src/com/android/launcher/CellLayout.java
@@ -98,6 +98,18 @@
}
}
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ // Cancel long press for all children
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.cancelLongPress();
+ }
+ }
+
int getCountX() {
return mPortrait ? mShortAxisCells : mLongAxisCells;
}
@@ -174,7 +186,7 @@
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
final boolean[][] occupied = mOccupied;
- findOccupiedCells(xCount, yCount, occupied);
+ findOccupiedCells(xCount, yCount, occupied, null);
cellInfo.cell = null;
cellInfo.cellX = cellXY[0];
@@ -215,7 +227,7 @@
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
final boolean[][] occupied = mOccupied;
- findOccupiedCells(xCount, yCount, occupied);
+ findOccupiedCells(xCount, yCount, occupied, null);
findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
@@ -315,7 +327,7 @@
return true;
}
- CellInfo findAllVacantCells(boolean[] occupiedCells) {
+ CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
final boolean portrait = mPortrait;
final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
@@ -329,7 +341,7 @@
}
}
} else {
- findOccupiedCells(xCount, yCount, occupied);
+ findOccupiedCells(xCount, yCount, occupied, ignoreView);
}
CellInfo cellInfo = new CellInfo();
@@ -518,7 +530,7 @@
final View view = getChildAt(i);
view.setDrawingCacheEnabled(enabled);
// Update the drawing caches
- view.buildDrawingCache();
+ view.buildDrawingCache(true);
}
}
@@ -527,64 +539,72 @@
super.setChildrenDrawnWithCacheEnabled(enabled);
}
- boolean acceptChildDrop(int x, int y, int cellHSpan, int cellVSpan, View cell) {
- int[] cellXY = mCellXY;
- pointToCellRounded(x, y, cellXY);
- int cellX = cellXY[0];
- int cellY = cellXY[1];
-
- return findCell(cellX, cellY, cellHSpan, cellVSpan, cell) == null;
- }
-
/**
- * Finds the first View intersecting with the specified cell. If the cell is outside
- * of the layout, this is returned.
- *
- * @param cellX The X location of the cell to test.
- * @param cellY The Y location of the cell to test.
- * @param cellHSpan The horizontal span of the cell to test.
- * @param cellVSpan The vertical span of the cell to test.
- * @param ignoreCell View to ignore during the test.
- *
- * @return Returns the first View intersecting with the specified cell, this if the cell
- * lies outside of this layout's grid or null if no View was found.
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param vacantCells Pre-computed set of vacant cells to search.
+ * @param recycle Previously returned value to possibly recycle.
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
*/
- View findCell(int cellX, int cellY, int cellHSpan, int cellVSpan, View ignoreCell) {
- if (cellX < 0 || cellX + cellHSpan > (mPortrait ? mShortAxisCells : mLongAxisCells) ||
- cellY < 0 || cellY + cellVSpan > (mPortrait ? mLongAxisCells : mShortAxisCells)) {
- return this;
+ int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
+ CellInfo vacantCells, int[] recycle) {
+
+ // Keep track of best-scoring drop area
+ final int[] bestXY = recycle != null ? recycle : new int[2];
+ final int[] cellXY = mCellXY;
+ double bestDistance = Double.MAX_VALUE;
+
+ // Bail early if vacant cells aren't valid
+ if (!vacantCells.valid) {
+ return null;
}
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View view = getChildAt(i);
- if (view == ignoreCell) {
+ // Look across all vacant cells for best fit
+ final int size = vacantCells.vacantCells.size();
+ for (int i = 0; i < size; i++) {
+ final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
+
+ // Reject if vacant cell isn't our exact size
+ if (cell.spanX != spanX || cell.spanY != spanY) {
continue;
}
-
- final LayoutParams lp = (LayoutParams) view.getLayoutParams();
- if (cellX < lp.cellX + lp.cellHSpan && lp.cellX < cellX + cellHSpan &&
- cellY < lp.cellY + lp.cellVSpan && lp.cellY < cellY + cellVSpan) {
- return view;
+
+ // Score is center distance from requested pixel
+ cellToPoint(cell.cellX, cell.cellY, cellXY);
+
+ double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
+ Math.pow(cellXY[1] - pixelY, 2));
+ if (distance <= bestDistance) {
+ bestDistance = distance;
+ bestXY[0] = cell.cellX;
+ bestXY[1] = cell.cellY;
}
}
- return null;
+ // Return null if no suitable location found
+ if (bestDistance < Double.MAX_VALUE) {
+ return bestXY;
+ } else {
+ return null;
+ }
}
-
+
/**
* Drop a child at the specified position
*
* @param child The child that is being dropped
- * @param cellX The child's new x location
- * @param cellY The child's new y location
+ * @param targetXY Destination area to move to
*/
- void onDropChild(View child, int cellX, int cellY) {
- int[] cellXY = mCellXY;
- pointToCellRounded(cellX, cellY, cellXY);
+ void onDropChild(View child, int[] targetXY) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.cellX = cellXY[0];
- lp.cellY = cellXY[1];
+ lp.cellX = targetXY[0];
+ lp.cellY = targetXY[1];
lp.isDragging = false;
mDragRect.setEmpty();
child.requestLayout();
@@ -688,7 +708,7 @@
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
final boolean[][] occupied = mOccupied;
- findOccupiedCells(xCount, yCount, occupied);
+ findOccupiedCells(xCount, yCount, occupied, null);
return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
}
@@ -723,7 +743,7 @@
final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
final boolean[][] occupied = mOccupied;
- findOccupiedCells(xCount, yCount, occupied);
+ findOccupiedCells(xCount, yCount, occupied, null);
final boolean[] flat = new boolean[xCount * yCount];
for (int y = 0; y < yCount; y++) {
@@ -735,7 +755,7 @@
return flat;
}
- private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied) {
+ private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
for (int x = 0; x < xCount; x++) {
for (int y = 0; y < yCount; y++) {
occupied[x][y] = false;
@@ -745,7 +765,7 @@
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
- if (child instanceof Folder) {
+ if (child instanceof Folder || child.equals(ignoreView)) {
continue;
}
LayoutParams lp = (LayoutParams) child.getLayoutParams();
@@ -919,7 +939,7 @@
int maxVacantSpanYSpanX;
final Rect current = new Rect();
- private void clearVacantCells() {
+ void clearVacantCells() {
final ArrayList<VacantCell> list = vacantCells;
final int count = list.size();
@@ -960,6 +980,10 @@
* @return True if a vacant cell of the specified dimension was found, false otherwise.
*/
boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
+ return findCellForSpan(cellXY, spanX, spanY, true);
+ }
+
+ boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
final ArrayList<VacantCell> list = vacantCells;
final int count = list.size();
@@ -993,7 +1017,7 @@
}
}
- clearVacantCells();
+ if (clear) clearVacantCells();
return found;
}
diff --git a/src/com/android/launcher/DeleteZone.java b/src/com/android/launcher/DeleteZone.java
index 7f92c23..02e8011 100644
--- a/src/com/android/launcher/DeleteZone.java
+++ b/src/com/android/launcher/DeleteZone.java
@@ -19,6 +19,7 @@
import android.widget.ImageView;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.TranslateAnimation;
@@ -77,6 +78,10 @@
Object dragInfo) {
return true;
}
+
+ public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+ return null;
+ }
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
final ItemInfo item = (ItemInfo) dragInfo;
diff --git a/src/com/android/launcher/DragLayer.java b/src/com/android/launcher/DragLayer.java
index b542de6..070c938 100644
--- a/src/com/android/launcher/DragLayer.java
+++ b/src/com/android/launcher/DragLayer.java
@@ -53,7 +53,7 @@
private boolean mShouldDrop;
private float mLastMotionX;
private float mLastMotionY;
-
+
/**
* The bitmap that is currently being dragged
*/
@@ -67,42 +67,42 @@
* X offset from where we touched on the cell to its upper-left corner
*/
private float mTouchOffsetX;
-
+
/**
* Y offset from where we touched on the cell to its upper-left corner
*/
private float mTouchOffsetY;
-
+
/**
* Utility rectangle
*/
private Rect mDragRect = new Rect();
-
+
/**
* Where the drag originated
*/
private DragSource mDragSource;
-
+
/**
* The data associated with the object being dragged
*/
private Object mDragInfo;
- private final Rect mRect = new Rect();
+ private final Rect mRect = new Rect();
private final int[] mDropCoordinates = new int[2];
private final Vibrator mVibrator = new Vibrator();
-
+
private DragListener mListener;
private DragScroller mDragScroller;
-
+
private static final int SCROLL_OUTSIDE_ZONE = 0;
private static final int SCROLL_WAITING_IN_ZONE = 1;
private static final int SCROLL_LEFT = 0;
private static final int SCROLL_RIGHT = 1;
-
+
private int mScrollState = SCROLL_OUTSIDE_ZONE;
private ScrollRunnable mScrollRunnable = new ScrollRunnable();
@@ -113,8 +113,25 @@
private DropTarget mLastDropTarget;
private final Paint mTrashPaint = new Paint();
+ private final Paint mEstimatedPaint = new Paint();
private Paint mDragPaint;
+ /**
+ * If true, draw a "snag" showing where the object currently being dragged
+ * would end up if dropped from current location.
+ */
+ private static final boolean DRAW_TARGET_SNAG = false;
+
+ private Rect mEstimatedRect = new Rect();
+ private float[] mDragCenter = new float[2];
+ private float[] mEstimatedCenter = new float[2];
+ private boolean mDrawEstimated = false;
+
+ private int mTriggerWidth = -1;
+ private int mTriggerHeight = -1;
+
+ private static final int DISTANCE_DRAW_SNAG = 20;
+
private static final int ANIMATION_STATE_STARTING = 1;
private static final int ANIMATION_STATE_RUNNING = 2;
private static final int ANIMATION_STATE_DONE = 3;
@@ -141,6 +158,13 @@
final int srcColor = context.getResources().getColor(R.color.delete_color_filter);
mTrashPaint.setColorFilter(new PorterDuffColorFilter(srcColor, PorterDuff.Mode.SRC_ATOP));
+
+ // Make estimated paint area in gray
+ int snagColor = context.getResources().getColor(R.color.snag_callout_color);
+ mEstimatedPaint.setColor(snagColor);
+ mEstimatedPaint.setStrokeWidth(3);
+ mEstimatedPaint.setAntiAlias(true);
+
}
public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
@@ -171,12 +195,23 @@
boolean willNotCache = v.willNotCacheDrawing();
v.setWillNotCacheDrawing(false);
- v.buildDrawingCache();
+ // Reset the drawing cache background color to fully transparent
+ // for the duration of this operation
+ int color = v.getDrawingCacheBackgroundColor();
+ v.setDrawingCacheBackgroundColor(0);
+
+ if (color != 0) {
+ v.destroyDrawingCache();
+ }
+ v.buildDrawingCache();
Bitmap viewBitmap = v.getDrawingCache();
int width = viewBitmap.getWidth();
int height = viewBitmap.getHeight();
+ mTriggerWidth = width * 2 / 3;
+ mTriggerHeight = height * 2 / 3;
+
Matrix scale = new Matrix();
float scaleFactor = v.getWidth();
scaleFactor = (scaleFactor + DRAG_SCALE) /scaleFactor;
@@ -191,6 +226,7 @@
mDragBitmap = Bitmap.createBitmap(viewBitmap, 0, 0, width, height, scale, true);
v.destroyDrawingCache();
v.setWillNotCacheDrawing(willNotCache);
+ v.setDrawingCacheBackgroundColor(color);
final Bitmap dragBitmap = mDragBitmap;
mBitmapOffsetX = (dragBitmap.getWidth() - width) / 2;
@@ -252,6 +288,13 @@
break;
}
} else {
+ // Only draw estimate drop "snag" when requested
+ if (DRAW_TARGET_SNAG && mDrawEstimated) {
+ canvas.drawLine(mDragCenter[0], mDragCenter[1], mEstimatedCenter[0], mEstimatedCenter[1], mEstimatedPaint);
+ canvas.drawCircle(mEstimatedCenter[0], mEstimatedCenter[1], 8, mEstimatedPaint);
+ }
+
+ // Draw actual icon being dragged
canvas.drawBitmap(mDragBitmap,
mScrollX + mLastMotionX - mTouchOffsetX - mBitmapOffsetX,
mScrollY + mLastMotionY - mTouchOffsetY - mBitmapOffsetY, mDragPaint);
@@ -355,8 +398,16 @@
left = (int) (scrollX + x - touchX - offsetX);
top = (int) (scrollY + y - touchY - offsetY);
+ // Invalidate current icon position
rect.union(left - 1, top - 1, left + width + 1, top + height + 1);
- invalidate(rect);
+
+ mDragCenter[0] = rect.centerX();
+ mDragCenter[1] = rect.centerY();
+
+ // Invalidate any old estimated location
+ if (DRAW_TARGET_SNAG && mDrawEstimated) {
+ rect.union(mEstimatedRect);
+ }
final int[] coordinates = mDropCoordinates;
DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
@@ -378,6 +429,33 @@
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
}
}
+
+ // Render estimated drop "snag" only outside of width
+ mDrawEstimated = false;
+ if (DRAW_TARGET_SNAG && dropTarget != null) {
+ Rect foundEstimate = dropTarget.estimateDropLocation(mDragSource,
+ (int) (scrollX + mLastMotionX), (int) (scrollY + mLastMotionY),
+ (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo, mEstimatedRect);
+
+ if (foundEstimate != null) {
+ mEstimatedCenter[0] = foundEstimate.centerX();
+ mEstimatedCenter[1] = foundEstimate.centerY();
+
+ int deltaX = (int) Math.abs(mEstimatedCenter[0] - mDragCenter[0]);
+ int deltaY = (int) Math.abs(mEstimatedCenter[1] - mDragCenter[1]);
+
+ if (deltaX > mTriggerWidth || deltaY > mTriggerHeight) {
+ mDrawEstimated = true;
+ }
+ }
+ }
+
+ // Include new estimated area in invalidated rectangle
+ if (DRAW_TARGET_SNAG && mDrawEstimated) {
+ rect.union(mEstimatedRect);
+ }
+ invalidate(rect);
+
mLastDropTarget = dropTarget;
boolean inDragRegion = false;
@@ -478,9 +556,15 @@
}
if (target == null) {
if (child instanceof DropTarget) {
- dropCoordinates[0] = x;
- dropCoordinates[1] = y;
- return (DropTarget) child;
+ // Only consider this child if they will accept
+ DropTarget childTarget = (DropTarget) child;
+ if (childTarget.acceptDrop(mDragSource, x, y, 0, 0, mDragInfo)) {
+ dropCoordinates[0] = x;
+ dropCoordinates[1] = y;
+ return (DropTarget) child;
+ } else {
+ return null;
+ }
}
} else {
return target;
@@ -501,7 +585,7 @@
}
public void removeDragListener(DragListener l) {
- mListener = null;
+ mListener = null;
}
/**
@@ -531,6 +615,7 @@
public void run() {
if (mDragScroller != null) {
+ mDrawEstimated = false;
if (mDirection == SCROLL_LEFT) {
mDragScroller.scrollLeft();
} else {
@@ -543,5 +628,5 @@
void setDirection(int direction) {
mDirection = direction;
}
- }
+ }
}
diff --git a/src/com/android/launcher/DropTarget.java b/src/com/android/launcher/DropTarget.java
index 8129089..4835323 100644
--- a/src/com/android/launcher/DropTarget.java
+++ b/src/com/android/launcher/DropTarget.java
@@ -16,6 +16,8 @@
package com.android.launcher;
+import android.graphics.Rect;
+
/**
* Interface defining an object that can receive a drag.
*
@@ -42,18 +44,38 @@
void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);
/**
- * Indicates whether a drop action can occur at the specified location. The method
- * {@link #onDrop(DragSource, int, int, int, int, Object)} will be invoked on this
- * drop target only if this method returns true.
- *
+ * Check if a drop action can occur at, or near, the requested location.
+ * This may be called repeatedly during a drag, so any calls should return
+ * quickly.
+ *
* @param source DragSource where the drag started
* @param x X coordinate of the drop location
* @param y Y coordinate of the drop location
- * @param xOffset Horizontal offset with the object being dragged where the original touch happened
- * @param yOffset Vertical offset with the object being dragged where the original touch happened
+ * @param xOffset Horizontal offset with the object being dragged where the
+ * original touch happened
+ * @param yOffset Vertical offset with the object being dragged where the
+ * original touch happened
* @param dragInfo Data associated with the object being dragged
- *
- * return True if the drop is accepted, false otherwise.
+ * @return True if the drop will be accepted, false otherwise.
*/
boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);
+
+ /**
+ * Estimate the surface area where this object would land if dropped at the
+ * given location.
+ *
+ * @param source DragSource where the drag started
+ * @param x X coordinate of the drop location
+ * @param y Y coordinate of the drop location
+ * @param xOffset Horizontal offset with the object being dragged where the
+ * original touch happened
+ * @param yOffset Vertical offset with the object being dragged where the
+ * original touch happened
+ * @param dragInfo Data associated with the object being dragged
+ * @param recycle {@link Rect} object to be possibly recycled.
+ * @return Estimated area that would be occupied if object was dropped at
+ * the given location. Should return null if no estimate is found,
+ * or if this target doesn't provide estimations.
+ */
+ Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle);
}
diff --git a/src/com/android/launcher/Folder.java b/src/com/android/launcher/Folder.java
index bcbccf7..fb4e8d6 100644
--- a/src/com/android/launcher/Folder.java
+++ b/src/com/android/launcher/Folder.java
@@ -24,7 +24,7 @@
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.AbsListView;
-import android.widget.ListAdapter;
+import android.widget.BaseAdapter;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
@@ -122,10 +122,14 @@
*
* @param adapter The list of applications to display in the folder.
*/
- void setContentAdapter(ListAdapter adapter) {
+ void setContentAdapter(BaseAdapter adapter) {
mContent.setAdapter(adapter);
}
+ void notifyDataSetChanged() {
+ ((BaseAdapter) mContent.getAdapter()).notifyDataSetChanged();
+ }
+
void setLauncher(Launcher launcher) {
mLauncher = launcher;
}
diff --git a/src/com/android/launcher/FolderIcon.java b/src/com/android/launcher/FolderIcon.java
index 667f92e..a56101d 100644
--- a/src/com/android/launcher/FolderIcon.java
+++ b/src/com/android/launcher/FolderIcon.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -69,6 +70,10 @@
&& item.container != mInfo.id;
}
+ public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+ return null;
+ }
+
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
final ApplicationInfo item = (ApplicationInfo) dragInfo;
// TODO: update open folder that is looking at this data
diff --git a/src/com/android/launcher/GesturesActivity.java b/src/com/android/launcher/GesturesActivity.java
new file mode 100644
index 0000000..3ea49e2
--- /dev/null
+++ b/src/com/android/launcher/GesturesActivity.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher;
+
+import android.app.ListActivity;
+import android.app.Dialog;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.os.AsyncTask;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import android.widget.AdapterView;
+import android.widget.Toast;
+import android.widget.EditText;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.gesture.GestureLibrary;
+import android.gesture.Gesture;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.text.TextUtils;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.Map;
+
+public class GesturesActivity extends ListActivity {
+ private static final int MENU_ID_RENAME = 1;
+ private static final int MENU_ID_REMOVE = 2;
+
+ private static final int DIALOG_RENAME_GESTURE = 1;
+
+ // Type: long (id)
+ private static final String GESTURES_INFO_ID = "gestures.info_id";
+
+ private final Comparator<ApplicationInfo> mSorter =
+ new LauncherModel.ApplicationInfoComparator();
+
+ private GesturesAdapter mAdapter;
+ private GestureLibrary mStore;
+ private GesturesLoadTask mTask;
+ private TextView mEmpty;
+
+ private Dialog mRenameDialog;
+ private EditText mInput;
+ private ApplicationInfo mCurrentRenameInfo;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.gestures_settings);
+
+ mAdapter = new GesturesAdapter(this);
+ setListAdapter(mAdapter);
+
+ mStore = Launcher.getGestureLibrary();
+ mEmpty = (TextView) findViewById(android.R.id.empty);
+ mTask = (GesturesLoadTask) new GesturesLoadTask().execute();
+
+ registerForContextMenu(getListView());
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (mTask != null && mTask.getStatus() != GesturesLoadTask.Status.FINISHED) {
+ mTask.cancel(true);
+ mTask = null;
+ }
+
+ cleanupRenameDialog();
+ }
+
+ private void checkForEmpty() {
+ if (mAdapter.getCount() == 0) {
+ mEmpty.setText(R.string.gestures_empty);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ if (mCurrentRenameInfo != null) {
+ outState.putLong(GESTURES_INFO_ID, mCurrentRenameInfo.id);
+ }
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle state) {
+ super.onRestoreInstanceState(state);
+
+ long id = state.getLong(GESTURES_INFO_ID, -1);
+ if (id != -1) {
+ mCurrentRenameInfo = Launcher.getModel().queryGesture(this, String.valueOf(id));
+ }
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenu.ContextMenuInfo menuInfo) {
+
+ super.onCreateContextMenu(menu, v, menuInfo);
+
+ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+ menu.setHeaderTitle(((TextView) info.targetView).getText());
+
+ menu.add(0, MENU_ID_RENAME, 0, R.string.gestures_rename);
+ menu.add(0, MENU_ID_REMOVE, 0, R.string.gestures_delete);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)
+ item.getMenuInfo();
+ final ApplicationInfo info = (ApplicationInfo) menuInfo.targetView.getTag();
+
+ switch (item.getItemId()) {
+ case MENU_ID_RENAME:
+ renameGesture(info);
+ return true;
+ case MENU_ID_REMOVE:
+ deleteGesture(info);
+ return true;
+ }
+
+ return super.onContextItemSelected(item);
+ }
+
+ private void renameGesture(ApplicationInfo info) {
+ mCurrentRenameInfo = info;
+ showDialog(DIALOG_RENAME_GESTURE);
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ if (id == DIALOG_RENAME_GESTURE) {
+ return createRenameDialog();
+ }
+ return super.onCreateDialog(id);
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ super.onPrepareDialog(id, dialog);
+ if (id == DIALOG_RENAME_GESTURE) {
+ mInput.setText(mCurrentRenameInfo.title);
+ }
+ }
+
+ private Dialog createRenameDialog() {
+ final View layout = View.inflate(this, R.layout.rename_folder, null);
+ mInput = (EditText) layout.findViewById(R.id.folder_name);
+ ((TextView) layout.findViewById(R.id.label)).setText(R.string.gestures_rename_label);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setIcon(0);
+ builder.setTitle(getString(R.string.gestures_rename_title));
+ builder.setCancelable(true);
+ builder.setOnCancelListener(new Dialog.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ cleanupRenameDialog();
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel_action),
+ new Dialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ cleanupRenameDialog();
+ }
+ }
+ );
+ builder.setPositiveButton(getString(R.string.rename_action),
+ new Dialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ changeGestureName();
+ }
+ }
+ );
+ builder.setView(layout);
+ return builder.create();
+ }
+
+ private void changeGestureName() {
+ final String name = mInput.getText().toString();
+ if (!TextUtils.isEmpty(name)) {
+ final ApplicationInfo renameInfo = mCurrentRenameInfo;
+ final GesturesActivity.GesturesAdapter adapter = mAdapter;
+ final int count = adapter.getCount();
+
+ // Simple linear search, there should not be enough items to warrant
+ // a more sophisticated search
+ for (int i = 0; i < count; i++) {
+ final ApplicationInfo info = adapter.getItem(i);
+ if (info.id == renameInfo.id) {
+ info.title = mInput.getText();
+ LauncherModel.updateGestureInDatabase(this, info);
+ break;
+ }
+ }
+
+ adapter.notifyDataSetChanged();
+ }
+ mCurrentRenameInfo = null;
+ }
+
+ private void cleanupRenameDialog() {
+ if (mRenameDialog != null) {
+ mRenameDialog.dismiss();
+ mRenameDialog = null;
+ }
+ mCurrentRenameInfo = null;
+ }
+
+ private void deleteGesture(ApplicationInfo info) {
+ mStore.removeEntry(String.valueOf(info.id));
+ // TODO: On a thread?
+ mStore.save();
+
+ final GesturesActivity.GesturesAdapter adapter = mAdapter;
+ adapter.setNotifyOnChange(false);
+ adapter.remove(info);
+ adapter.sort(mSorter);
+ checkForEmpty();
+ adapter.notifyDataSetChanged();
+
+ LauncherModel.deleteGestureFromDatabase(this, info);
+
+ Toast.makeText(this, R.string.gestures_delete_success, Toast.LENGTH_SHORT).show();
+ }
+
+ private class GesturesLoadTask extends AsyncTask<Void, ApplicationInfo, Boolean> {
+ private int mThumbnailSize;
+ private int mThumbnailInset;
+ private int mPathColor;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ final Resources resources = getResources();
+ mPathColor = resources.getColor(R.color.gesture_color);
+ mThumbnailInset = (int) resources.getDimension(R.dimen.gesture_thumbnail_inset);
+ mThumbnailSize = (int) resources.getDimension(R.dimen.gesture_thumbnail_size);
+ }
+
+ protected Boolean doInBackground(Void... params) {
+ if (isCancelled()) return Boolean.FALSE;
+
+ final GestureLibrary store = mStore;
+
+ if (store.load()) {
+ final LauncherModel model = Launcher.getModel();
+
+ for (String name : store.getGestureEntries()) {
+ if (isCancelled()) break;
+
+ final Gesture gesture = store.getGestures(name).get(0);
+ final Bitmap bitmap = gesture.toBitmap(mThumbnailSize, mThumbnailSize,
+ mThumbnailInset, mPathColor);
+ final ApplicationInfo info = model.queryGesture(GesturesActivity.this, name);
+
+ mAdapter.addBitmap(info.id, bitmap);
+ publishProgress(info);
+ }
+
+ return Boolean.TRUE;
+ }
+
+ return Boolean.FALSE;
+ }
+
+ @Override
+ protected void onProgressUpdate(ApplicationInfo... values) {
+ super.onProgressUpdate(values);
+
+ final GesturesActivity.GesturesAdapter adapter = mAdapter;
+ adapter.setNotifyOnChange(false);
+
+ for (ApplicationInfo info : values) {
+ adapter.add(info);
+ }
+
+ adapter.sort(mSorter);
+ adapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected void onPostExecute(Boolean aBoolean) {
+ super.onPostExecute(aBoolean);
+ checkForEmpty();
+ }
+ }
+
+ private class GesturesAdapter extends ArrayAdapter<ApplicationInfo> {
+ private final LayoutInflater mInflater;
+ private final Map<Long, Drawable> mThumbnails = Collections.synchronizedMap(
+ new HashMap<Long, Drawable>());
+
+ public GesturesAdapter(Context context) {
+ super(context, 0);
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ void addBitmap(Long id, Bitmap bitmap) {
+ mThumbnails.put(id, new BitmapDrawable(bitmap));
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.gestures_settings_item, parent, false);
+ }
+
+ final ApplicationInfo info = getItem(position);
+ final TextView label = (TextView) convertView;
+
+ label.setTag(info);
+ label.setText(info.title);
+ label.setCompoundDrawablesWithIntrinsicBounds(info.icon, null,
+ mThumbnails.get(info.id), null);
+
+ return convertView;
+ }
+ }
+}
diff --git a/src/com/android/launcher/GesturesConstants.java b/src/com/android/launcher/GesturesConstants.java
new file mode 100644
index 0000000..3151ea3
--- /dev/null
+++ b/src/com/android/launcher/GesturesConstants.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher;
+
+interface GesturesConstants {
+ final double PREDICTION_THRESHOLD = 1.0;
+ final String STORE_NAME = "gestures";
+ final long MATCH_DELAY = 370;
+ final float LENGTH_THRESHOLD = 120.0f;
+ int PATH_SAMPLE_COUNT = 10;
+}
diff --git a/src/com/android/launcher/GesturesPanel.java b/src/com/android/launcher/GesturesPanel.java
new file mode 100644
index 0000000..33dc102
--- /dev/null
+++ b/src/com/android/launcher/GesturesPanel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher;
+
+import android.widget.RelativeLayout;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+
+public class GesturesPanel extends RelativeLayout {
+ public GesturesPanel(Context context) {
+ super(context);
+ }
+
+ public GesturesPanel(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return true;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK &&
+ event.getAction() == KeyEvent.ACTION_DOWN) {
+
+ ((Launcher) mContext).hideGesturesPanel();
+
+ return true;
+ }
+
+ return super.dispatchKeyEvent(event);
+ }
+}
diff --git a/src/com/android/launcher/InstallShortcutReceiver.java b/src/com/android/launcher/InstallShortcutReceiver.java
index a1e954a..7c727a8 100644
--- a/src/com/android/launcher/InstallShortcutReceiver.java
+++ b/src/com/android/launcher/InstallShortcutReceiver.java
@@ -21,11 +21,19 @@
import android.content.Intent;
import android.content.ContentResolver;
import android.database.Cursor;
+import android.widget.Toast;
public class InstallShortcutReceiver extends BroadcastReceiver {
+ private static final String ACTION_INSTALL_SHORTCUT =
+ "com.android.launcher.action.INSTALL_SHORTCUT";
+
private final int[] mCoordinates = new int[2];
public void onReceive(Context context, Intent data) {
+ if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
+ return;
+ }
+
int screen = Launcher.getScreen();
if (!installShortcut(context, data, screen)) {
@@ -37,6 +45,8 @@
}
private boolean installShortcut(Context context, Intent data, int screen) {
+ String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+
if (findEmptyCell(context, mCoordinates, screen)) {
CellLayout.CellInfo cell = new CellLayout.CellInfo();
cell.cellX = mCoordinates[0];
@@ -44,7 +54,6 @@
cell.screen = screen;
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
- String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
if (intent.getAction() == null) {
intent.setAction(Intent.ACTION_VIEW);
@@ -55,9 +64,17 @@
boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
if (duplicate || !LauncherModel.shortcutExists(context, name, intent)) {
Launcher.addShortcut(context, data, cell, true);
+ Toast.makeText(context, context.getString(R.string.shortcut_installed, name),
+ Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(context, context.getString(R.string.shortcut_duplicate, name),
+ Toast.LENGTH_SHORT).show();
}
return true;
+ } else {
+ Toast.makeText(context, context.getString(R.string.out_of_space),
+ Toast.LENGTH_SHORT).show();
}
return false;
diff --git a/src/com/android/launcher/ItemInfo.java b/src/com/android/launcher/ItemInfo.java
index 51449a7..71cee18 100644
--- a/src/com/android/launcher/ItemInfo.java
+++ b/src/com/android/launcher/ItemInfo.java
@@ -76,6 +76,11 @@
*/
int spanY = 1;
+ /**
+ * Indicates whether the item is a gesture.
+ */
+ boolean isGesture = false;
+
ItemInfo() {
}
@@ -96,13 +101,15 @@
* @param values
*/
void onAddToDatabase(ContentValues values) {
- values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
- values.put(LauncherSettings.Favorites.CONTAINER, container);
- values.put(LauncherSettings.Favorites.SCREEN, screen);
- values.put(LauncherSettings.Favorites.CELLX, cellX);
- values.put(LauncherSettings.Favorites.CELLY, cellY);
- values.put(LauncherSettings.Favorites.SPANX, spanX);
- values.put(LauncherSettings.Favorites.SPANY, spanY);
+ values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType);
+ if (!isGesture) {
+ values.put(LauncherSettings.Favorites.CONTAINER, container);
+ values.put(LauncherSettings.Favorites.SCREEN, screen);
+ values.put(LauncherSettings.Favorites.CELLX, cellX);
+ values.put(LauncherSettings.Favorites.CELLY, cellY);
+ values.put(LauncherSettings.Favorites.SPANX, spanX);
+ values.put(LauncherSettings.Favorites.SPANY, spanY);
+ }
}
static void writeBitmap(ContentValues values, Bitmap bitmap) {
diff --git a/src/com/android/launcher/Launcher.java b/src/com/android/launcher/Launcher.java
index 7dd3418..8863bd9 100644
--- a/src/com/android/launcher/Launcher.java
+++ b/src/com/android/launcher/Launcher.java
@@ -20,6 +20,8 @@
import android.app.AlertDialog;
import android.app.Application;
import android.app.Dialog;
+import android.app.ISearchManager;
+import android.app.IWallpaperService;
import android.app.SearchManager;
import android.app.StatusBarManager;
import android.content.ActivityNotFoundException;
@@ -30,36 +32,34 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.content.Intent.ShortcutIconResource;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Message;
import android.os.MessageQueue;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.Message;
-import android.provider.*;
-import android.telephony.PhoneNumberUtils;
+import android.provider.LiveFolders;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
-import android.util.Log;
import static android.util.Log.*;
import android.view.Display;
import android.view.KeyEvent;
@@ -68,20 +68,33 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.view.MotionEvent;
+import android.view.Gravity;
import android.view.View.OnLongClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
import android.widget.GridView;
import android.widget.SlidingDrawer;
-import android.app.IWallpaperService;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+import android.widget.ViewSwitcher;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
+import android.gesture.GestureOverlayView;
+import android.gesture.GestureLibraries;
+import android.gesture.GestureLibrary;
+import android.gesture.Gesture;
+import android.gesture.Prediction;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.LinkedList;
+import java.io.DataOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.DataInputStream;
/**
* Default launcher application.
@@ -94,6 +107,9 @@
private static final boolean PROFILE_DRAWER = false;
private static final boolean PROFILE_ROTATE = false;
private static final boolean DEBUG_USER_INTERFACE = false;
+ private static final boolean DEBUG_GESTURES = false;
+
+ private static final boolean CONFIG_GESTURES_IMMEDIATE_MODE = true;
private static final int WALLPAPER_SCREENS_SPAN = 2;
@@ -102,7 +118,8 @@
private static final int MENU_WALLPAPER_SETTINGS = MENU_ADD + 1;
private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1;
- private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1;
+ private static final int MENU_GESTURES = MENU_NOTIFICATIONS + 1;
+ private static final int MENU_SETTINGS = MENU_GESTURES + 1;
private static final int REQUEST_CREATE_SHORTCUT = 1;
private static final int REQUEST_CREATE_LIVE_FOLDER = 4;
@@ -111,6 +128,9 @@
private static final int REQUEST_PICK_SHORTCUT = 7;
private static final int REQUEST_PICK_LIVE_FOLDER = 8;
private static final int REQUEST_PICK_APPWIDGET = 9;
+ private static final int REQUEST_PICK_GESTURE_ACTION = 10;
+ private static final int REQUEST_CREATE_GESTURE_ACTION = 11;
+ private static final int REQUEST_CREATE_GESTURE_APPLICATION_ACTION = 12;
static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
@@ -120,15 +140,12 @@
static final int SCREEN_COUNT = 3;
static final int DEFAULT_SCREN = 1;
static final int NUMBER_CELLS_X = 4;
- static final int NUMBER_CELLS_Y = 4;
+ static final int NUMBER_CELLS_Y = 4;
private static final int DIALOG_CREATE_SHORTCUT = 1;
- static final int DIALOG_RENAME_FOLDER = 2;
+ static final int DIALOG_RENAME_FOLDER = 2;
- private static final String PREFERENCES = "launcher";
- private static final String KEY_LOCALE = "locale";
- private static final String KEY_MCC = "mcc";
- private static final String KEY_MNC = "mnc";
+ private static final String PREFERENCES = "launcher.preferences";
// Type: int
private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
@@ -156,6 +173,12 @@
private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
// Type: long
private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
+ // Type: Gesture (Parcelable)
+ private static final String RUNTIME_STATE_PENDING_GESTURE = "launcher.gesture";
+ // Type: boolean
+ private static final String RUNTIME_STATE_GESTURES_PANEL = "launcher.gesture_panel_showing";
+ // Type: Gesture (Parcelable)
+ private static final String RUNTIME_STATE_GESTURES_PANEL_GESTURE = "launcher.gesture_panel_gesture";
private static final LauncherModel sModel = new LauncherModel();
@@ -166,20 +189,21 @@
private static WallpaperIntentReceiver sWallpaperReceiver;
+ private static GestureLibrary sLibrary;
+
private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
private final ContentObserver mObserver = new FavoritesChangeObserver();
- private final ContentObserver mAppWidgetResetObserver = new AppWidgetResetObserver();
private LayoutInflater mInflater;
private DragLayer mDragLayer;
private Workspace mWorkspace;
-
+
private AppWidgetManager mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
-
+
static final int APPWIDGET_HOST_ID = 1024;
-
+
private CellLayout.CellInfo mAddItemCellInfo;
private CellLayout.CellInfo mMenuAddInfo;
private final int[] mCellCoordinates = new int[2];
@@ -196,6 +220,8 @@
private SpannableStringBuilder mDefaultKeySsb = null;
private boolean mDestroyed;
+
+ private boolean mIsNewIntent;
private boolean mRestoring;
private boolean mWaitingForResult;
@@ -204,17 +230,33 @@
private Bundle mSavedInstanceState;
private DesktopBinder mBinder;
-
+
+ private View mGesturesPanel;
+ private GestureOverlayView mGesturesOverlay;
+ private ViewSwitcher mGesturesPrompt;
+ private ImageView mGesturesAdd;
+ private PopupWindow mGesturesWindow;
+ private Launcher.GesturesProcessor mGesturesProcessor;
+ private Gesture mCurrentGesture;
+ private GesturesAction mGesturesAction;
+ private boolean mHideGesturesPanel;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInflater = getLayoutInflater();
-
+
+ if (sLibrary == null) {
+ // The context is not kept by the library so it's safe to do this
+ sLibrary = GestureLibraries.fromPrivateFile(Launcher.this,
+ GesturesConstants.STORE_NAME);
+ }
+
mAppWidgetManager = AppWidgetManager.getInstance(this);
-
+
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();
-
+
if (PROFILE_STARTUP) {
android.os.Debug.startMethodTracing("/sdcard/launcher");
}
@@ -243,28 +285,82 @@
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
}
-
+
private void checkForLocaleChange() {
- final SharedPreferences preferences = getSharedPreferences(PREFERENCES, MODE_PRIVATE);
+ final LocaleConfiguration localeConfiguration = new LocaleConfiguration();
+ readConfiguration(this, localeConfiguration);
+
final Configuration configuration = getResources().getConfiguration();
- final String previousLocale = preferences.getString(KEY_LOCALE, null);
+ final String previousLocale = localeConfiguration.locale;
final String locale = configuration.locale.toString();
- final int previousMcc = preferences.getInt(KEY_MCC, -1);
+ final int previousMcc = localeConfiguration.mcc;
final int mcc = configuration.mcc;
- final int previousMnc = preferences.getInt(KEY_MNC, -1);
+ final int previousMnc = localeConfiguration.mnc;
final int mnc = configuration.mnc;
mLocaleChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
if (mLocaleChanged) {
- final SharedPreferences.Editor editor = preferences.edit();
- editor.putString(KEY_LOCALE, locale);
- editor.putInt(KEY_MCC, mcc);
- editor.putInt(KEY_MNC, mnc);
- editor.commit();
+ localeConfiguration.locale = locale;
+ localeConfiguration.mcc = mcc;
+ localeConfiguration.mnc = mnc;
+
+ writeConfiguration(this, localeConfiguration);
+ }
+ }
+
+ private static class LocaleConfiguration {
+ public String locale;
+ public int mcc = -1;
+ public int mnc = -1;
+ }
+
+ private static void readConfiguration(Context context, LocaleConfiguration configuration) {
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(context.openFileInput(PREFERENCES));
+ configuration.locale = in.readUTF();
+ configuration.mcc = in.readInt();
+ configuration.mnc = in.readInt();
+ } catch (FileNotFoundException e) {
+ // Ignore
+ } catch (IOException e) {
+ // Ignore
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
+ DataOutputStream out = null;
+ try {
+ out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE));
+ out.writeUTF(configuration.locale);
+ out.writeInt(configuration.mcc);
+ out.writeInt(configuration.mnc);
+ out.flush();
+ } catch (FileNotFoundException e) {
+ // Ignore
+ } catch (IOException e) {
+ //noinspection ResultOfMethodCallIgnored
+ context.getFileStreamPath(PREFERENCES).delete();
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
}
}
@@ -305,19 +401,25 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mWaitingForResult = false;
+
// The pattern used here is that a user PICKs a specific application,
// which, depending on the target, might need to CREATE the actual target.
-
+
// For example, the user would PICK_SHORTCUT for "Music playlist", and we
// launch over to the Music app to actually CREATE_SHORTCUT.
-
- if (resultCode == RESULT_OK && mAddItemCellInfo != null) {
+
+ if (resultCode == RESULT_OK && (mAddItemCellInfo != null ||
+ ((requestCode == REQUEST_PICK_GESTURE_ACTION ||
+ requestCode == REQUEST_CREATE_GESTURE_ACTION ||
+ requestCode == REQUEST_CREATE_GESTURE_APPLICATION_ACTION) && mCurrentGesture != null))) {
+
switch (requestCode) {
case REQUEST_PICK_APPLICATION:
completeAddApplication(this, data, mAddItemCellInfo, !mDesktopLocked);
break;
case REQUEST_PICK_SHORTCUT:
- addShortcut(data);
+ processShortcut(data, REQUEST_PICK_APPLICATION, REQUEST_CREATE_SHORTCUT);
break;
case REQUEST_CREATE_SHORTCUT:
completeAddShortcut(data, mAddItemCellInfo, !mDesktopLocked);
@@ -334,6 +436,16 @@
case REQUEST_CREATE_APPWIDGET:
completeAddAppWidget(data, mAddItemCellInfo, !mDesktopLocked);
break;
+ case REQUEST_PICK_GESTURE_ACTION:
+ processShortcut(data, REQUEST_CREATE_GESTURE_APPLICATION_ACTION,
+ REQUEST_CREATE_GESTURE_ACTION);
+ break;
+ case REQUEST_CREATE_GESTURE_ACTION:
+ completeCreateGesture(data, true);
+ break;
+ case REQUEST_CREATE_GESTURE_APPLICATION_ACTION:
+ completeCreateGesture(data, false);
+ break;
}
} else if (requestCode == REQUEST_PICK_APPWIDGET &&
resultCode == RESULT_CANCELED && data != null) {
@@ -343,7 +455,6 @@
mAppWidgetHost.deleteAppWidgetId(appWidgetId);
}
}
- mWaitingForResult = false;
}
@Override
@@ -353,21 +464,55 @@
if (mRestoring) {
startLoaders();
}
+
+ // If this was a new intent (i.e., the mIsNewIntent flag got set to true by
+ // onNewIntent), then close the search dialog if needed, because it probably
+ // came from the user pressing 'home' (rather than, for example, pressing 'back').
+ if (mIsNewIntent) {
+ // Post to a handler so that this happens after the search dialog tries to open
+ // itself again.
+ mWorkspace.post(new Runnable() {
+ public void run() {
+ ISearchManager searchManagerService = ISearchManager.Stub.asInterface(
+ ServiceManager.getService(Context.SEARCH_SERVICE));
+ try {
+ searchManagerService.stopSearch();
+ } catch (RemoteException e) {
+ e(LOG_TAG, "error stopping search", e);
+ }
+ }
+ });
+ }
+
+ mIsNewIntent = false;
}
@Override
protected void onPause() {
super.onPause();
- closeDrawer(false);
+ if (mGesturesWindow != null) {
+ mGesturesWindow.setAnimationStyle(0);
+ mGesturesWindow.update();
+ }
+ closeDrawer(false);
}
-
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mHideGesturesPanel) {
+ mHideGesturesPanel = false;
+ hideGesturesPanel();
+ }
+ }
+
@Override
public Object onRetainNonConfigurationInstance() {
// Flag any binder to stop early before switching
if (mBinder != null) {
mBinder.mTerminate = true;
}
-
+
if (PROFILE_ROTATE) {
android.os.Debug.startMethodTracing("/sdcard/launcher-rotate");
}
@@ -387,45 +532,29 @@
boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
keyCode, event);
if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
- // something usable has been typed - dispatch it now.
- final String str = mDefaultKeySsb.toString();
-
- boolean isDialable = true;
- final int count = str.length();
- for (int i = 0; i < count; i++) {
- if (!PhoneNumberUtils.isReallyDialable(str.charAt(i))) {
- isDialable = false;
- break;
- }
- }
- Intent intent;
- if (isDialable) {
- intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", str, null));
- } else {
- intent = new Intent(Contacts.Intents.UI.FILTER_CONTACTS_ACTION);
- intent.putExtra(Contacts.Intents.UI.FILTER_TEXT_EXTRA_KEY, str);
- }
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-
- try {
- startActivity(intent);
- } catch (android.content.ActivityNotFoundException ex) {
- // Oh well... no one knows how to filter/dial. Life goes on.
- }
-
- mDefaultKeySsb.clear();
- mDefaultKeySsb.clearSpans();
- Selection.setSelection(mDefaultKeySsb, 0);
-
- return true;
+ // something usable has been typed - start a search
+ // the typed text will be retrieved and cleared by
+ // showSearchDialog()
+ // If there are multiple keystrokes before the search dialog takes focus,
+ // onSearchRequested() will be called for every keystroke,
+ // but it is idempotent, so it's fine.
+ return onSearchRequested();
}
}
return handled;
}
+ private String getTypedText() {
+ return mDefaultKeySsb.toString();
+ }
+
+ private void clearTypedText() {
+ mDefaultKeySsb.clear();
+ mDefaultKeySsb.clearSpans();
+ Selection.setSelection(mDefaultKeySsb, 0);
+ }
+
/**
* Restores the previous state, if it exists.
*
@@ -464,6 +593,29 @@
mFolderInfo = sModel.getFolderById(this, id);
mRestoring = true;
}
+
+ mCurrentGesture = (Gesture) savedState.get(RUNTIME_STATE_PENDING_GESTURE);
+
+ boolean gesturesShowing = savedState.getBoolean(RUNTIME_STATE_GESTURES_PANEL, false);
+ if (gesturesShowing) {
+ if (mCurrentGesture == null) {
+ mCurrentGesture = (Gesture) savedState.get(RUNTIME_STATE_GESTURES_PANEL_GESTURE);
+ }
+ final Gesture gesture = mCurrentGesture;
+ mWorkspace.post(new Runnable() {
+ public void run() {
+ showGesturesPanel(false);
+ mGesturesProcessor.matchGesture(gesture, false);
+ mWorkspace.post(new Runnable() {
+ public void run() {
+ if (gesture != null) {
+ mGesturesOverlay.setGesture(gesture);
+ }
+ }
+ });
+ }
+ });
+ }
}
/**
@@ -495,7 +647,7 @@
drawer.setOnDrawerCloseListener(drawerManager);
drawer.setOnDrawerScrollListener(drawerManager);
- grid.setTextFilterEnabled(true);
+ grid.setTextFilterEnabled(false);
grid.setDragger(dragLayer);
grid.setLauncher(this);
@@ -511,6 +663,69 @@
dragLayer.setIgnoredDropTarget(grid);
dragLayer.setDragScoller(workspace);
dragLayer.setDragListener(deleteZone);
+
+ mGesturesPanel = mInflater.inflate(R.layout.gestures, mDragLayer, false);
+ final View gesturesPanel = mGesturesPanel;
+
+ mGesturesPrompt = (ViewSwitcher) gesturesPanel.findViewById(R.id.gestures_actions);
+ mGesturesAction = new GesturesAction();
+
+ mGesturesPrompt.getChildAt(0).setOnClickListener(mGesturesAction);
+ mGesturesPrompt.getChildAt(1).setOnClickListener(mGesturesAction);
+
+ mGesturesAdd = (ImageView) gesturesPanel.findViewById(R.id.gestures_add);
+ final ImageView gesturesAdd = mGesturesAdd;
+ gesturesAdd.setAlpha(128);
+ gesturesAdd.setEnabled(false);
+ gesturesAdd.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ createGesture();
+ }
+ });
+
+ mGesturesOverlay = (GestureOverlayView) gesturesPanel.findViewById(R.id.gestures_overlay);
+ mGesturesProcessor = new GesturesProcessor();
+
+ final GestureOverlayView overlay = mGesturesOverlay;
+ overlay.addOnGestureListener(mGesturesProcessor);
+ overlay.getGesturePaint().setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
+ }
+
+ private void createGesture() {
+ if (!mWaitingForResult) {
+ mCurrentGesture = mGesturesOverlay.getGesture();
+ mWaitingForResult = true;
+ pickShortcut(REQUEST_PICK_GESTURE_ACTION, R.string.title_select_shortcut);
+ }
+ }
+
+ private void completeCreateGesture(Intent data, boolean isShortcut) {
+ ApplicationInfo info;
+
+ if (isShortcut) {
+ info = infoFromShortcutIntent(this, data);
+ } else {
+ info = infoFromApplicationIntent(this, data);
+ }
+
+ boolean success = false;
+ if (info != null) {
+ info.isGesture = true;
+
+ if (LauncherModel.addGestureToDatabase(this, info, false)) {
+ mGesturesProcessor.addGesture(String.valueOf(info.id), mCurrentGesture);
+ mGesturesProcessor.update(info, mCurrentGesture);
+ Toast.makeText(this, getString(R.string.gestures_created, info.title),
+ Toast.LENGTH_SHORT).show();
+ success = true;
+ }
+ }
+
+ if (!success) {
+ Toast.makeText(this, getString(R.string.gestures_failed), Toast.LENGTH_SHORT).show();
+ }
+
+ mCurrentGesture = null;
}
/**
@@ -561,33 +776,41 @@
cellInfo.screen = mWorkspace.getCurrentScreen();
if (!findSingleSlot(cellInfo)) return;
- // Find details for this application
+ final ApplicationInfo info = infoFromApplicationIntent(context, data);
+ if (info != null) {
+ mWorkspace.addApplicationShortcut(info, cellInfo, insertAtFirst);
+ }
+ }
+
+ private static ApplicationInfo infoFromApplicationIntent(Context context, Intent data) {
ComponentName component = data.getComponent();
PackageManager packageManager = context.getPackageManager();
ActivityInfo activityInfo = null;
try {
activityInfo = packageManager.getActivityInfo(component, 0 /* no flags */);
} catch (NameNotFoundException e) {
- Log.e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e);
+ e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e);
}
-
+
if (activityInfo != null) {
ApplicationInfo itemInfo = new ApplicationInfo();
-
+
itemInfo.title = activityInfo.loadLabel(packageManager);
if (itemInfo.title == null) {
itemInfo.title = activityInfo.name;
}
-
+
itemInfo.setActivity(component, Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
itemInfo.icon = activityInfo.loadIcon(packageManager);
itemInfo.container = ItemInfo.NO_ID;
- mWorkspace.addApplicationShortcut(itemInfo, cellInfo, insertAtFirst);
+ return itemInfo;
}
+
+ return null;
}
-
+
/**
* Add a shortcut to the workspace.
*
@@ -599,7 +822,7 @@
boolean insertAtFirst) {
cellInfo.screen = mWorkspace.getCurrentScreen();
if (!findSingleSlot(cellInfo)) return;
-
+
final ApplicationInfo info = addShortcut(this, data, cellInfo, false);
if (!mRestoring) {
@@ -612,7 +835,7 @@
}
}
-
+
/**
* Add a widget to the workspace.
*
@@ -624,15 +847,15 @@
Bundle extras = data.getExtras();
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
-
+
d(LOG_TAG, "dumping extras content="+extras.toString());
-
+
AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
-
+
// Calculate the grid spans needed to fit this widget
CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight);
-
+
// Try finding open space on Launcher screen
final int[] xy = mCellCoordinates;
if (!findSlot(cellInfo, xy, spans[0], spans[1])) return;
@@ -641,34 +864,42 @@
LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
launcherInfo.spanX = spans[0];
launcherInfo.spanY = spans[1];
-
+
LauncherModel.addItemToDatabase(this, launcherInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
if (!mRestoring) {
sModel.addDesktopAppWidget(launcherInfo);
-
+
// Perform actual inflation because we're live
launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
-
+
launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
launcherInfo.hostView.setTag(launcherInfo);
-
+
mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1],
launcherInfo.spanX, launcherInfo.spanY, insertAtFirst);
} else if (sModel.isDesktopLoaded()) {
sModel.addDesktopAppWidget(launcherInfo);
}
}
-
+
public LauncherAppWidgetHost getAppWidgetHost() {
return mAppWidgetHost;
}
-
+
static ApplicationInfo addShortcut(Context context, Intent data,
CellLayout.CellInfo cellInfo, boolean notify) {
+ final ApplicationInfo info = infoFromShortcutIntent(context, data);
+ LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
+
+ return info;
+ }
+
+ private static ApplicationInfo infoFromShortcutIntent(Context context, Intent data) {
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
@@ -676,7 +907,7 @@
Drawable icon = null;
boolean filtered = false;
boolean customIcon = false;
- Intent.ShortcutIconResource iconResource = null;
+ ShortcutIconResource iconResource = null;
if (bitmap != null) {
icon = new FastBitmapDrawable(Utilities.createBitmapThumbnail(bitmap, context));
@@ -684,9 +915,9 @@
customIcon = true;
} else {
Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
- if (extra != null && extra instanceof Intent.ShortcutIconResource) {
+ if (extra != null && extra instanceof ShortcutIconResource) {
try {
- iconResource = (Intent.ShortcutIconResource) extra;
+ iconResource = (ShortcutIconResource) extra;
final PackageManager packageManager = context.getPackageManager();
Resources resources = packageManager.getResourcesForApplication(
iconResource.packageName);
@@ -710,8 +941,6 @@
info.customIcon = customIcon;
info.iconResource = iconResource;
- LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
- cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
return info;
}
@@ -722,6 +951,11 @@
// Close the menu
if (Intent.ACTION_MAIN.equals(intent.getAction())) {
getWindow().closeAllPanels();
+
+ // Set this flag so that onResume knows to close the search dialog if it's open,
+ // because this was a new intent (thus a press of 'home' or some such) rather than
+ // for example onResume being called when the user pressed the 'back' button.
+ mIsNewIntent = true;
try {
dismissDialog(DIALOG_CREATE_SHORTCUT);
@@ -739,15 +973,23 @@
// An exception is thrown if the dialog is not visible, which is fine
}
- // If we are already in front we go back to the default screen,
- // otherwise we don't
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) !=
Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) {
- if (!mWorkspace.isDefaultScreenShowing()) {
- mWorkspace.moveToDefaultScreen();
+
+ if (mGesturesPanel != null && mDragLayer.getWindowVisibility() == View.VISIBLE &&
+ (mDragLayer.hasWindowFocus() ||
+ (mGesturesWindow != null && mGesturesWindow.isShowing()))) {
+
+ SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+
+ if (!searchManager.isVisible()) {
+ onHomeKeyPressed();
+ }
}
closeDrawer();
- View v = getWindow().peekDecorView();
+
+ final View v = getWindow().peekDecorView();
if (v != null && v.getWindowToken() != null) {
InputMethodManager imm = (InputMethodManager)getSystemService(
INPUT_METHOD_SERVICE);
@@ -759,6 +1001,91 @@
}
}
+ private void onHomeKeyPressed() {
+ if (mGesturesWindow == null || !mGesturesWindow.isShowing()) {
+ showGesturesPanel();
+ } else {
+ hideGesturesPanel();
+ }
+ }
+
+ private void showGesturesPanel() {
+ showGesturesPanel(true);
+ }
+
+ private void showGesturesPanel(boolean animate) {
+ resetGesturesPrompt();
+
+ mGesturesAdd.setEnabled(false);
+ mGesturesAdd.setAlpha(128);
+
+ mGesturesOverlay.clear(false);
+
+ PopupWindow window;
+ if (mGesturesWindow == null) {
+ mGesturesWindow = new PopupWindow(this);
+ window = mGesturesWindow;
+ window.setFocusable(true);
+ window.setTouchable(true);
+ window.setBackgroundDrawable(null);
+ window.setContentView(mGesturesPanel);
+ } else {
+ window = mGesturesWindow;
+ }
+ window.setAnimationStyle(animate ? com.android.internal.R.style.Animation_SlidingCard : 0);
+
+ final int[] xy = new int[2];
+ final DragLayer dragLayer = mDragLayer;
+ dragLayer.getLocationOnScreen(xy);
+
+ window.setWidth(dragLayer.getWidth());
+ window.setHeight(dragLayer.getHeight() - 1);
+ window.showAtLocation(dragLayer, Gravity.TOP | Gravity.LEFT, xy[0], xy[1] + 1);
+ }
+
+ private void resetGesturesPrompt() {
+ mGesturesAction.intent = null;
+ final TextView prompt = (TextView) mGesturesPrompt.getCurrentView();
+ prompt.setText(R.string.gestures_instructions);
+ prompt.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ prompt.setClickable(false);
+ }
+
+ private void resetGesturesNextPrompt() {
+ mGesturesAction.intent = null;
+ setGesturesNextPrompt(null, getString(R.string.gestures_instructions));
+ mGesturesPrompt.getNextView().setClickable(false);
+ }
+
+ private void setGesturesNextPrompt(Drawable icon, CharSequence title) {
+ final TextView prompt = (TextView) mGesturesPrompt.getNextView();
+ prompt.setText(title);
+ prompt.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+ prompt.setClickable(true);
+ mGesturesPrompt.showNext();
+ }
+
+ private void setGesturesPrompt(Drawable icon, CharSequence title) {
+ final TextView prompt = (TextView) mGesturesPrompt.getCurrentView();
+ prompt.setText(title);
+ prompt.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+ prompt.setClickable(true);
+ }
+
+ void hideGesturesPanel() {
+ hideGesturesPanel(true);
+ }
+
+ void hideGesturesPanel(boolean animate) {
+ if (mGesturesWindow != null) {
+ final PopupWindow popupWindow = mGesturesWindow;
+ popupWindow.setAnimationStyle(animate ?
+ com.android.internal.R.style.Animation_SlidingCard : 0);
+ popupWindow.update();
+ popupWindow.dismiss();
+ }
+ }
+
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// Do not call super here
@@ -782,9 +1109,13 @@
super.onSaveInstanceState(outState);
}
- if (mDrawer.isOpened()) {
+ final boolean isConfigurationChange = getChangingConfigurations() != 0;
+
+ // When the drawer is opened and we are saving the state because of a
+ // configuration change
+ if (mDrawer.isOpened() && isConfigurationChange) {
outState.putBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, true);
- }
+ }
if (mAddItemCellInfo != null && mAddItemCellInfo.valid && mWaitingForResult) {
final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
@@ -805,6 +1136,21 @@
outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
}
+
+ if (mCurrentGesture != null && mWaitingForResult) {
+ outState.putParcelable(RUNTIME_STATE_PENDING_GESTURE, mCurrentGesture);
+ }
+
+ if (mGesturesWindow != null && mGesturesWindow.isShowing()) {
+ outState.putBoolean(RUNTIME_STATE_GESTURES_PANEL, true);
+
+ if (mCurrentGesture == null || !mWaitingForResult) {
+ final Gesture gesture = mGesturesOverlay.getGesture();
+ if (gesture != null) {
+ outState.putParcelable(RUNTIME_STATE_GESTURES_PANEL_GESTURE, gesture);
+ }
+ }
+ }
}
@Override
@@ -812,7 +1158,7 @@
mDestroyed = true;
super.onDestroy();
-
+
try {
mAppWidgetHost.stopListening();
} catch (NullPointerException ex) {
@@ -821,30 +1167,89 @@
TextKeyListener.getInstance().release();
+ hideGesturesPanel(false);
mAllAppsGrid.clearTextFilter();
mAllAppsGrid.setAdapter(null);
sModel.unbind();
sModel.abortLoaders();
getContentResolver().unregisterContentObserver(mObserver);
- getContentResolver().unregisterContentObserver(mAppWidgetResetObserver);
unregisterReceiver(mApplicationsReceiver);
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
- mWaitingForResult = true;
+ if (requestCode >= 0) mWaitingForResult = true;
super.startActivityForResult(intent, requestCode);
}
@Override
- public void startSearch(String initialQuery, boolean selectInitialQuery,
+ public void startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
+
+ closeDrawer(false);
+
+ // Slide the search widget to the top, if it's on the current screen,
+ // otherwise show the search dialog immediately.
+ Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+ if (searchWidget == null) {
+ showSearchDialog(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ } else {
+ searchWidget.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ // show the currently typed text in the search widget while sliding
+ searchWidget.setQuery(getTypedText());
+ }
+ }
+
+ /**
+ * Show the search dialog immediately, without changing the search widget.
+ *
+ * @see Activity#startSearch(String, boolean, android.os.Bundle, boolean)
+ */
+ void showSearchDialog(String initialQuery, boolean selectInitialQuery,
+ Bundle appSearchData, boolean globalSearch) {
+
+ if (initialQuery == null) {
+ // Use any text typed in the launcher as the initial query
+ initialQuery = getTypedText();
+ clearTypedText();
+ }
if (appSearchData == null) {
appSearchData = new Bundle();
appSearchData.putString(SearchManager.SOURCE, "launcher-search");
}
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+
+ final Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+ if (searchWidget != null) {
+ // This gets called when the user leaves the search dialog to go back to
+ // the Launcher.
+ searchManager.setOnCancelListener(new SearchManager.OnCancelListener() {
+ public void onCancel() {
+ searchManager.setOnCancelListener(null);
+ stopSearch();
+ }
+ });
+ }
+
+ searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+ appSearchData, globalSearch);
+ }
+
+ /**
+ * Cancel search dialog if it is open.
+ */
+ void stopSearch() {
+ // Close search dialog
+ SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ searchManager.stopSearch();
+ // Restore search widget to its normal position
+ Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+ if (searchWidget != null) {
+ searchWidget.stopSearch(false);
+ }
}
@Override
@@ -865,9 +1270,14 @@
.setIcon(com.android.internal.R.drawable.ic_menu_notifications)
.setAlphabeticShortcut('N');
+ final Intent gestures = new Intent(this, GesturesActivity.class);
+ menu.add(0, MENU_GESTURES, 0, R.string.menu_gestures)
+ .setIcon(com.android.internal.R.drawable.ic_menu_compose).setAlphabeticShortcut('G')
+ .setIntent(gestures);
+
final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
- settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings)
.setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut('P')
@@ -906,14 +1316,15 @@
return super.onOptionsItemSelected(item);
}
+ /**
+ * Indicates that we want global search for this activity by setting the globalSearch
+ * argument for {@link #startSearch} to true.
+ */
+
@Override
public boolean onSearchRequested() {
- if (mWorkspace.snapToSearch()) {
- closeDrawer(true); // search widget: get drawer out of the way
- return true;
- } else {
- return super.onSearchRequested(); // no search widget: use system search UI
- }
+ startSearch(null, false, null, true);
+ return true;
}
private void addItems() {
@@ -925,7 +1336,7 @@
mWorkspace.removeShortcutsForPackage(packageName);
}
}
-
+
private void updateShortcutsForPackage(String packageName) {
if (packageName != null && packageName.length() > 0) {
mWorkspace.updateShortcutsForPackage(packageName);
@@ -958,41 +1369,43 @@
}
}
}
-
+
void addSearch() {
final Widget info = Widget.makeSearch();
final CellLayout.CellInfo cellInfo = mAddItemCellInfo;
-
+
final int[] xy = mCellCoordinates;
final int spanX = info.spanX;
final int spanY = info.spanY;
-
+
if (!findSlot(cellInfo, xy, spanX, spanY)) return;
-
+
sModel.addDesktopItem(info);
LauncherModel.addItemToDatabase(this, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
-
+
final View view = mInflater.inflate(info.layoutResource, null);
view.setTag(info);
-
+ Search search = (Search) view.findViewById(R.id.widget_search);
+ search.setLauncher(this);
+
mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY);
}
- void addShortcut(Intent intent) {
+ void processShortcut(Intent intent, int requestCodeApplication, int requestCodeShortcut) {
// Handle case where user selected "Applications"
String applicationName = getResources().getString(R.string.group_applications);
String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-
+
if (applicationName != null && applicationName.equals(shortcutName)) {
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
+
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
- startActivityForResult(pickIntent, REQUEST_PICK_APPLICATION);
+ startActivityForResult(pickIntent, requestCodeApplication);
} else {
- startActivityForResult(intent, REQUEST_CREATE_SHORTCUT);
+ startActivityForResult(intent, requestCodeShortcut);
}
}
@@ -1000,7 +1413,7 @@
// Handle case where user selected "Folder"
String folderName = getResources().getString(R.string.group_folder);
String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-
+
if (folderName != null && folderName.equals(shortcutName)) {
addFolder(!mDesktopLocked);
} else {
@@ -1028,7 +1441,7 @@
mWorkspace.addInCurrentScreen(newFolder,
cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst);
}
-
+
private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo,
boolean insertAtFirst) {
cellInfo.screen = mWorkspace.getCurrentScreen();
@@ -1158,7 +1571,6 @@
private void registerContentObservers() {
ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mObserver);
- resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI, true, mAppWidgetResetObserver);
}
@Override
@@ -1166,11 +1578,11 @@
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BACK:
- mWorkspace.dispatchKeyEvent(event);
+ mWorkspace.dispatchKeyEvent(event);
if (mDrawer.isOpened()) {
closeDrawer();
} else {
- closeFolder();
+ closeFolder();
}
return true;
case KeyEvent.KEYCODE_HOME:
@@ -1224,21 +1636,11 @@
sModel.loadUserItems(false, this, false, false);
}
- /**
- * When reset, we handle by calling {@link AppWidgetHost#startListening()}
- * to make sure our callbacks are set correctly.
- */
- private void onAppWidgetReset() {
- if (mAppWidgetHost != null) {
- mAppWidgetHost.startListening();
- }
- }
-
void onDesktopItemsLoaded() {
if (mDestroyed) return;
bindDesktopItems();
}
-
+
/**
* Refreshes the shortcuts shown on the workspace.
*/
@@ -1250,14 +1652,12 @@
return;
}
- mAllAppsGrid.setAdapter(drawerAdapter);
-
final Workspace workspace = mWorkspace;
int count = workspace.getChildCount();
for (int i = 0; i < count; i++) {
((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout();
}
-
+
if (DEBUG_USER_INTERFACE) {
android.widget.Button finishButton = new android.widget.Button(this);
finishButton.setText("Finish");
@@ -1269,13 +1669,13 @@
}
});
}
-
+
// Flag any old binder to terminate early
if (mBinder != null) {
mBinder.mTerminate = true;
}
-
- mBinder = new DesktopBinder(this, shortcuts, appWidgets);
+
+ mBinder = new DesktopBinder(this, shortcuts, appWidgets, drawerAdapter);
mBinder.startBindingItems();
}
@@ -1316,10 +1716,13 @@
final int screen = workspace.getCurrentScreen();
final View view = mInflater.inflate(R.layout.widget_search,
(ViewGroup) workspace.getChildAt(screen), false);
-
+
+ Search search = (Search) view.findViewById(R.id.widget_search);
+ search.setLauncher(this);
+
final Widget widget = (Widget) item;
view.setTag(widget);
-
+
workspace.addWidget(view, widget, !desktopLocked);
break;
}
@@ -1329,7 +1732,7 @@
if (end >= count) {
finishBindDesktopItems();
- binder.startBindingAppWidgetsWhenIdle();
+ binder.startBindingDrawer();
} else {
binder.obtainMessage(DesktopBinder.MESSAGE_BIND_ITEMS, i, count).sendToTarget();
}
@@ -1375,32 +1778,34 @@
mDesktopLocked = false;
mDrawer.unlock();
}
-
+
+ private void bindDrawer(Launcher.DesktopBinder binder,
+ ApplicationsAdapter drawerAdapter) {
+ mAllAppsGrid.setAdapter(drawerAdapter);
+ binder.startBindingAppWidgetsWhenIdle();
+ }
+
private void bindAppWidgets(Launcher.DesktopBinder binder,
LinkedList<LauncherAppWidgetInfo> appWidgets) {
-
+
final Workspace workspace = mWorkspace;
final boolean desktopLocked = mDesktopLocked;
if (!appWidgets.isEmpty()) {
final LauncherAppWidgetInfo item = appWidgets.removeFirst();
-
+
final int appWidgetId = item.appWidgetId;
- final AppWidgetProviderInfo appWidgetInfo =
- mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+ final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
-
- if (LOGD) {
- d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s",
- appWidgetId, appWidgetInfo));
- }
-
+
+ if (LOGD) d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s", appWidgetId, appWidgetInfo));
+
item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
item.hostView.setTag(item);
-
+
workspace.addInScreen(item.hostView, item.screen, item.cellX,
item.cellY, item.spanX, item.spanY, !desktopLocked);
-
+
workspace.requestLayout();
}
@@ -1434,6 +1839,7 @@
}
void startActivitySafely(Intent intent) {
+ mHideGesturesPanel = true;
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
@@ -1441,7 +1847,7 @@
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
+ e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
". Make sure to create a MAIN intent-filter for the corresponding activity " +
"or use the exported attribute for this activity.", e);
}
@@ -1560,6 +1966,10 @@
return sModel;
}
+ static GestureLibrary getGestureLibrary() {
+ return sLibrary;
+ }
+
void closeAllApplications() {
mDrawer.close();
}
@@ -1611,7 +2021,7 @@
EditText input = (EditText) dialog.findViewById(R.id.folder_name);
final CharSequence text = mFolderInfo.title;
input.setText(text);
- input.setSelection(0, text.length());
+ input.setSelection(0, text.length());
break;
}
}
@@ -1628,6 +2038,26 @@
showDialog(DIALOG_CREATE_SHORTCUT);
}
+ private void pickShortcut(int requestCode, int title) {
+ Bundle bundle = new Bundle();
+
+ ArrayList<String> shortcutNames = new ArrayList<String>();
+ shortcutNames.add(getString(R.string.group_applications));
+ bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
+
+ ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>();
+ shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
+ R.drawable.ic_launcher_application));
+ bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
+
+ Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
+ pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT));
+ pickIntent.putExtra(Intent.EXTRA_TITLE, getText(title));
+ pickIntent.putExtras(bundle);
+
+ startActivityForResult(pickIntent, requestCode);
+ }
+
private class RenameFolder {
private EditText mInput;
@@ -1703,22 +2133,23 @@
* appropriate activity.
*/
private class CreateShortcut implements DialogInterface.OnClickListener,
- DialogInterface.OnCancelListener {
+ DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
private AddAdapter mAdapter;
Dialog createDialog() {
mWaitingForResult = true;
-
+
mAdapter = new AddAdapter(Launcher.this);
-
+
final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
builder.setTitle(getString(R.string.menu_item_add_item));
builder.setAdapter(mAdapter, this);
-
+
builder.setInverseBackgroundForced(true);
AlertDialog dialog = builder.create();
dialog.setOnCancelListener(this);
+ dialog.setOnDismissListener(this);
return dialog;
}
@@ -1728,6 +2159,10 @@
cleanup();
}
+ public void onDismiss(DialogInterface dialog) {
+ mWorkspace.unlock();
+ }
+
private void cleanup() {
mWorkspace.unlock();
dismissDialog(DIALOG_CREATE_SHORTCUT);
@@ -1739,36 +2174,17 @@
public void onClick(DialogInterface dialog, int which) {
Resources res = getResources();
cleanup();
-
+
switch (which) {
case AddAdapter.ITEM_SHORTCUT: {
// Insert extra item to handle picking application
- Bundle bundle = new Bundle();
-
- ArrayList<String> shortcutNames = new ArrayList<String>();
- shortcutNames.add(res.getString(R.string.group_applications));
- bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
-
- ArrayList<ShortcutIconResource> shortcutIcons =
- new ArrayList<ShortcutIconResource>();
- shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
- R.drawable.ic_launcher_application));
- bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
-
- Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
- pickIntent.putExtra(Intent.EXTRA_INTENT,
- new Intent(Intent.ACTION_CREATE_SHORTCUT));
- pickIntent.putExtra(Intent.EXTRA_TITLE,
- getText(R.string.title_select_shortcut));
- pickIntent.putExtras(bundle);
-
- startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);
+ pickShortcut(REQUEST_PICK_SHORTCUT, R.string.title_select_shortcut);
break;
}
-
+
case AddAdapter.ITEM_APPWIDGET: {
int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId();
-
+
Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
// add the search widget
@@ -1791,15 +2207,15 @@
startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
break;
}
-
+
case AddAdapter.ITEM_LIVE_FOLDER: {
// Insert extra item to handle inserting folder
Bundle bundle = new Bundle();
-
+
ArrayList<String> shortcutNames = new ArrayList<String>();
shortcutNames.add(res.getString(R.string.group_folder));
bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
-
+
ArrayList<ShortcutIconResource> shortcutIcons =
new ArrayList<ShortcutIconResource>();
shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
@@ -1812,7 +2228,7 @@
pickIntent.putExtra(Intent.EXTRA_TITLE,
getText(R.string.title_select_live_folder));
pickIntent.putExtras(bundle);
-
+
startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER);
break;
}
@@ -1891,21 +2307,6 @@
}
/**
- * Receives notifications when the {@link AppWidgetHost} has been reset,
- * usually only when the {@link LauncherProvider} database is first created.
- */
- private class AppWidgetResetObserver extends ContentObserver {
- public AppWidgetResetObserver() {
- super(new Handler());
- }
-
- @Override
- public void onChange(boolean selfChange) {
- onAppWidgetReset();
- }
- }
-
- /**
* Receives intents from other applications to change the wallpaper.
*/
private static class WallpaperIntentReceiver extends BroadcastReceiver {
@@ -1996,27 +2397,31 @@
private static class DesktopBinder extends Handler implements MessageQueue.IdleHandler {
static final int MESSAGE_BIND_ITEMS = 0x1;
static final int MESSAGE_BIND_APPWIDGETS = 0x2;
-
+ static final int MESSAGE_BIND_DRAWER = 0x3;
+
// Number of items to bind in every pass
static final int ITEMS_COUNT = 6;
private final ArrayList<ItemInfo> mShortcuts;
private final LinkedList<LauncherAppWidgetInfo> mAppWidgets;
+ private final ApplicationsAdapter mDrawerAdapter;
private final WeakReference<Launcher> mLauncher;
-
- public volatile boolean mTerminate = false;
+
+ public boolean mTerminate = false;
DesktopBinder(Launcher launcher, ArrayList<ItemInfo> shortcuts,
- ArrayList<LauncherAppWidgetInfo> appWidgets) {
+ ArrayList<LauncherAppWidgetInfo> appWidgets,
+ ApplicationsAdapter drawerAdapter) {
mLauncher = new WeakReference<Launcher>(launcher);
mShortcuts = shortcuts;
-
+ mDrawerAdapter = drawerAdapter;
+
// Sort widgets so active workspace is bound first
final int currentScreen = launcher.mWorkspace.getCurrentScreen();
final int size = appWidgets.size();
mAppWidgets = new LinkedList<LauncherAppWidgetInfo>();
-
+
for (int i = 0; i < size; i++) {
LauncherAppWidgetInfo appWidgetInfo = appWidgets.get(i);
if (appWidgetInfo.screen == currentScreen) {
@@ -2026,17 +2431,21 @@
}
}
}
-
+
public void startBindingItems() {
obtainMessage(MESSAGE_BIND_ITEMS, 0, mShortcuts.size()).sendToTarget();
}
-
+
+ public void startBindingDrawer() {
+ obtainMessage(MESSAGE_BIND_DRAWER).sendToTarget();
+ }
+
public void startBindingAppWidgetsWhenIdle() {
// Ask for notification when message queue becomes idle
final MessageQueue messageQueue = Looper.myQueue();
messageQueue.addIdleHandler(this);
}
-
+
public boolean queueIdle() {
// Queue is idle, so start binding items
startBindingAppWidgets();
@@ -2053,12 +2462,16 @@
if (launcher == null || mTerminate) {
return;
}
-
+
switch (msg.what) {
case MESSAGE_BIND_ITEMS: {
launcher.bindItems(this, mShortcuts, msg.arg1, msg.arg2);
break;
}
+ case MESSAGE_BIND_DRAWER: {
+ launcher.bindDrawer(this, mDrawerAdapter);
+ break;
+ }
case MESSAGE_BIND_APPWIDGETS: {
launcher.bindAppWidgets(this, mAppWidgets);
break;
@@ -2066,4 +2479,160 @@
}
}
}
+
+ private class GesturesProcessor implements GestureOverlayView.OnGestureListener,
+ GestureOverlayView.OnGesturePerformedListener {
+
+ private final GestureMatcher mMatcher = new GestureMatcher();
+
+ GesturesProcessor() {
+ // TODO: Maybe the load should happen on a background thread?
+ sLibrary.load();
+ }
+
+ public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (!CONFIG_GESTURES_IMMEDIATE_MODE) {
+ overlay.removeCallbacks(mMatcher);
+ resetGesturesNextPrompt();
+ }
+
+ mGesturesAdd.setAlpha(128);
+ mGesturesAdd.setEnabled(false);
+ }
+
+ public void onGesture(GestureOverlayView overlay, MotionEvent event) {
+ }
+
+ public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
+ }
+
+ public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
+ if (CONFIG_GESTURES_IMMEDIATE_MODE) {
+ mMatcher.gesture = overlay.getGesture();
+ if (mMatcher.gesture.getLength() < GesturesConstants.LENGTH_THRESHOLD) {
+ overlay.clear(false);
+ if (mGesturesAction.intent != null) {
+ mGesturesAction.intent = null;
+ setGesturesNextPrompt(null, getString(R.string.gestures_unknown));
+ }
+ } else {
+ mMatcher.run();
+ }
+ } else {
+ overlay.removeCallbacks(mMatcher);
+
+ mMatcher.gesture = overlay.getGesture();
+ if (mMatcher.gesture.getLength() < GesturesConstants.LENGTH_THRESHOLD) {
+ overlay.clear(false);
+ if (mGesturesAction.intent != null) {
+ mGesturesAction.intent = null;
+ setGesturesNextPrompt(null, getString(R.string.gestures_unknown));
+ }
+ } else {
+ overlay.postDelayed(mMatcher, GesturesConstants.MATCH_DELAY);
+ }
+ }
+ }
+
+ void matchGesture(Gesture gesture) {
+ matchGesture(gesture, true);
+ }
+
+ void matchGesture(Gesture gesture, boolean animate) {
+ mGesturesAdd.setAlpha(255);
+ mGesturesAdd.setEnabled(true);
+
+ if (gesture != null) {
+ final ArrayList<Prediction> predictions = sLibrary.recognize(gesture);
+
+ if (DEBUG_GESTURES) {
+ for (Prediction p : predictions) {
+ d(LOG_TAG, String.format("name=%s, score=%f", p.name, p.score));
+ }
+ }
+
+ boolean match = false;
+ if (predictions.size() > 0) {
+ final Prediction prediction = predictions.get(0);
+ if (prediction.score > GesturesConstants.PREDICTION_THRESHOLD) {
+ match = true;
+
+ ApplicationInfo info = sModel.queryGesture(Launcher.this, prediction.name);
+ if (info != null) {
+ updatePrompt(info, animate);
+ }
+ }
+ }
+
+ if (!match){
+ mGesturesAction.intent = null;
+ if (animate) {
+ setGesturesNextPrompt(null, getString(R.string.gestures_unknown));
+ } else {
+ setGesturesPrompt(null, getString(R.string.gestures_unknown));
+ }
+ }
+ }
+ }
+
+ private void updatePrompt(ApplicationInfo info) {
+ updatePrompt(info, true);
+ }
+
+ private void updatePrompt(ApplicationInfo info, boolean animate) {
+ if (mGesturesAction.intent != null &&
+ info.intent.toUri(0).equals(mGesturesAction.intent.toUri(0)) &&
+ info.title.equals(((TextView) mGesturesPrompt.getCurrentView()).getText())) {
+ return;
+ }
+
+ if (animate) {
+ setGesturesNextPrompt(info.icon, info.title);
+ } else {
+ setGesturesPrompt(info.icon, info.title);
+ }
+
+ mGesturesAction.intent = info.intent;
+ }
+
+ public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (!CONFIG_GESTURES_IMMEDIATE_MODE) {
+ overlay.removeCallbacks(mMatcher);
+ }
+ }
+
+ void addGesture(String name, Gesture gesture) {
+ sLibrary.addGesture(name, gesture);
+ // TODO: On a background thread?
+ sLibrary.save();
+ }
+
+ void update(ApplicationInfo info, Gesture gesture) {
+ mGesturesOverlay.setGesture(gesture);
+ updatePrompt(info);
+ }
+
+ class GestureMatcher implements Runnable {
+ Gesture gesture;
+
+ public void run() {
+ if (gesture != null) {
+ matchGesture(gesture);
+ }
+ }
+ }
+ }
+
+ private class GesturesAction implements View.OnClickListener {
+ Intent intent;
+
+ public void onClick(View v) {
+ if (intent != null) {
+ startActivitySafely(intent);
+ }
+ }
+ }
}
+
diff --git a/src/com/android/launcher/LauncherAppWidgetHostView.java b/src/com/android/launcher/LauncherAppWidgetHostView.java
index 1e21a19..da5b3a0 100644
--- a/src/com/android/launcher/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher/LauncherAppWidgetHostView.java
@@ -98,4 +98,14 @@
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
}
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ mHasPerformedLongPress = false;
+ if (mPendingCheckForLongPress != null) {
+ removeCallbacks(mPendingCheckForLongPress);
+ }
+ }
}
diff --git a/src/com/android/launcher/LauncherModel.java b/src/com/android/launcher/LauncherModel.java
index da41f14..591463b 100644
--- a/src/com/android/launcher/LauncherModel.java
+++ b/src/com/android/launcher/LauncherModel.java
@@ -116,7 +116,7 @@
if (localeChanged) {
dropApplicationCache();
- }
+ }
if (mApplicationsAdapter == null || isLaunching || localeChanged) {
mApplications = new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
@@ -126,7 +126,7 @@
mApplicationsLoaded = false;
if (!isLaunching) {
- startApplicationsLoader(launcher);
+ startApplicationsLoader(launcher, false);
return false;
}
@@ -148,19 +148,19 @@
}
}
- private synchronized void startApplicationsLoader(Launcher launcher) {
+ private synchronized void startApplicationsLoader(Launcher launcher, boolean isLaunching) {
if (DEBUG_LOADERS) d(LOG_TAG, " --> starting applications loader");
stopAndWaitForApplicationsLoader();
- mApplicationsLoader = new ApplicationsLoader(launcher);
+ mApplicationsLoader = new ApplicationsLoader(launcher, isLaunching);
mApplicationsLoaderThread = new Thread(mApplicationsLoader, "Applications Loader");
mApplicationsLoaderThread.start();
}
synchronized void addPackage(Launcher launcher, String packageName) {
if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
- startApplicationsLoader(launcher);
+ startApplicationsLoader(launcher, false);
return;
}
@@ -174,7 +174,7 @@
for (ResolveInfo info : matches) {
adapter.setNotifyOnChange(false);
- adapter.add(makeAndCacheApplicationInfo(packageManager, cache, info));
+ adapter.add(makeAndCacheApplicationInfo(packageManager, cache, info, launcher));
}
adapter.sort(new ApplicationInfoComparator());
@@ -186,7 +186,7 @@
synchronized void removePackage(Launcher launcher, String packageName) {
if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
dropApplicationCache(); // TODO: this could be optimized
- startApplicationsLoader(launcher);
+ startApplicationsLoader(launcher, false);
return;
}
@@ -221,7 +221,7 @@
synchronized void updatePackage(Launcher launcher, String packageName) {
if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
- startApplicationsLoader(launcher);
+ startApplicationsLoader(launcher, false);
return;
}
@@ -239,11 +239,13 @@
final ApplicationInfo applicationInfo = findIntent(adapter,
info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
if (applicationInfo != null) {
- updateAndCacheApplicationInfo(packageManager, info, applicationInfo);
+ updateAndCacheApplicationInfo(packageManager, info, applicationInfo, launcher);
changed = true;
}
}
+ if (syncLocked(launcher, packageName)) changed = true;
+
if (changed) {
adapter.sort(new ApplicationInfoComparator());
adapter.notifyDataSetChanged();
@@ -252,9 +254,9 @@
}
private void updateAndCacheApplicationInfo(PackageManager packageManager, ResolveInfo info,
- ApplicationInfo applicationInfo) {
+ ApplicationInfo applicationInfo, Context context) {
- updateApplicationInfoTitleAndIcon(packageManager, info, applicationInfo);
+ updateApplicationInfoTitleAndIcon(packageManager, info, applicationInfo, context);
ComponentName componentName = new ComponentName(
info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
@@ -263,31 +265,38 @@
synchronized void syncPackage(Launcher launcher, String packageName) {
if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
- startApplicationsLoader(launcher);
+ startApplicationsLoader(launcher, false);
return;
}
if (packageName != null && packageName.length() > 0) {
- final PackageManager packageManager = launcher.getPackageManager();
- final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName);
-
- if (matches.size() > 0) {
+ if (syncLocked(launcher, packageName)) {
final ApplicationsAdapter adapter = mApplicationsAdapter;
-
- // Find disabled activities and remove them from the adapter
- boolean removed = removeDisabledActivities(packageName, matches, adapter);
- // Find enable activities and add them to the adapter
- // Also updates existing activities with new labels/icons
- boolean added = addEnabledAndUpdateActivities(matches, adapter, launcher);
-
- if (added || removed) {
- adapter.sort(new ApplicationInfoComparator());
- adapter.notifyDataSetChanged();
- }
+ adapter.sort(new ApplicationInfoComparator());
+ adapter.notifyDataSetChanged();
}
}
}
+ private boolean syncLocked(Launcher launcher, String packageName) {
+ final PackageManager packageManager = launcher.getPackageManager();
+ final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName);
+
+ if (matches.size() > 0) {
+ final ApplicationsAdapter adapter = mApplicationsAdapter;
+
+ // Find disabled activities and remove them from the adapter
+ boolean removed = removeDisabledActivities(packageName, matches, adapter);
+ // Find enable activities and add them to the adapter
+ // Also updates existing activities with new labels/icons
+ boolean added = addEnabledAndUpdateActivities(matches, adapter, launcher);
+
+ return added || removed;
+ }
+
+ return false;
+ }
+
private static List<ResolveInfo> findActivitiesForPackage(PackageManager packageManager,
String packageName) {
@@ -326,10 +335,11 @@
info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
if (applicationInfo == null) {
toAdd.add(makeAndCacheApplicationInfo(launcher.getPackageManager(),
- mAppInfoCache, info));
+ mAppInfoCache, info, launcher));
changed = true;
} else {
- updateAndCacheApplicationInfo(launcher.getPackageManager(), info, applicationInfo);
+ updateAndCacheApplicationInfo(
+ launcher.getPackageManager(), info, applicationInfo, launcher);
changed = true;
}
}
@@ -419,7 +429,8 @@
}
private static ApplicationInfo makeAndCacheApplicationInfo(PackageManager manager,
- HashMap<ComponentName, ApplicationInfo> appInfoCache, ResolveInfo info) {
+ HashMap<ComponentName, ApplicationInfo> appInfoCache, ResolveInfo info,
+ Context context) {
ComponentName componentName = new ComponentName(
info.activityInfo.applicationInfo.packageName,
@@ -430,7 +441,7 @@
application = new ApplicationInfo();
application.container = ItemInfo.NO_ID;
- updateApplicationInfoTitleAndIcon(manager, info, application);
+ updateApplicationInfoTitleAndIcon(manager, info, application, context);
application.setActivity(componentName,
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
@@ -442,14 +453,15 @@
}
private static void updateApplicationInfoTitleAndIcon(PackageManager manager, ResolveInfo info,
- ApplicationInfo application) {
+ ApplicationInfo application, Context context) {
application.title = info.loadLabel(manager);
if (application.title == null) {
application.title = info.activityInfo.name;
}
- application.icon = info.activityInfo.loadIcon(manager);
+ application.icon =
+ Utilities.createIconThumbnail(info.activityInfo.loadIcon(manager), context);
application.filtered = false;
}
@@ -458,8 +470,10 @@
private volatile boolean mStopped;
private volatile boolean mRunning;
+ private final boolean mIsLaunching;
- ApplicationsLoader(Launcher launcher) {
+ ApplicationsLoader(Launcher launcher, boolean isLaunching) {
+ mIsLaunching = isLaunching;
mLauncher = new WeakReference<Launcher>(launcher);
}
@@ -474,7 +488,10 @@
public void run() {
mRunning = true;
- android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ // Elevate priority when Home launches for the first time to avoid
+ // starving at boot time. Staring at a blank home is not cool.
+ android.os.Process.setThreadPriority(mIsLaunching ? Process.THREAD_PRIORITY_DEFAULT :
+ Process.THREAD_PRIORITY_BACKGROUND);
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
@@ -495,7 +512,7 @@
for (int i = 0; i < count && !mStopped; i++) {
ResolveInfo info = apps.get(i);
ApplicationInfo application =
- makeAndCacheApplicationInfo(manager, appInfoCache, info);
+ makeAndCacheApplicationInfo(manager, appInfoCache, info, launcher);
if (action.add(application) && !mStopped) {
launcher.runOnUiThread(action);
@@ -557,7 +574,7 @@
}
}
- private static class ApplicationInfoComparator implements Comparator<ApplicationInfo> {
+ static class ApplicationInfoComparator implements Comparator<ApplicationInfo> {
public final int compare(ApplicationInfo a, ApplicationInfo b) {
return sCollator.compare(a.title.toString(), b.title.toString());
}
@@ -577,7 +594,7 @@
if (isLaunching && isDesktopLoaded()) {
if (DEBUG_LOADERS) d(LOG_TAG, " --> items loaded, return");
- if (loadApplications) startApplicationsLoader(launcher);
+ if (loadApplications) startApplicationsLoader(launcher, true);
// We have already loaded our data from the DB
launcher.onDesktopItemsLoaded();
return;
@@ -604,18 +621,19 @@
if (DEBUG_LOADERS) d(LOG_TAG, " --> starting workspace loader");
mDesktopItemsLoaded = false;
- mDesktopItemsLoader = new DesktopItemsLoader(launcher, localeChanged, loadApplications);
+ mDesktopItemsLoader = new DesktopItemsLoader(launcher, localeChanged, loadApplications,
+ isLaunching);
mDesktopLoaderThread = new Thread(mDesktopItemsLoader, "Desktop Items Loader");
mDesktopLoaderThread.start();
}
private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) {
final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI,
- new String[] { LauncherSettings.Favorites.ID, LauncherSettings.Favorites.TITLE,
+ new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE,
LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE },
null, null, null);
- final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
@@ -632,7 +650,7 @@
final String intentUri = c.getString(intentIndex);
if (intentUri != null) {
- final Intent shortcut = Intent.getIntent(intentUri);
+ final Intent shortcut = Intent.parseUri(intentUri, 0);
if (Intent.ACTION_MAIN.equals(shortcut.getAction())) {
final ComponentName name = shortcut.getComponent();
if (name != null) {
@@ -685,9 +703,12 @@
private final WeakReference<Launcher> mLauncher;
private final boolean mLocaleChanged;
private final boolean mLoadApplications;
+ private final boolean mIsLaunching;
- DesktopItemsLoader(Launcher launcher, boolean localeChanged, boolean loadApplications) {
+ DesktopItemsLoader(Launcher launcher, boolean localeChanged, boolean loadApplications,
+ boolean isLaunching) {
mLoadApplications = loadApplications;
+ mIsLaunching = isLaunching;
mLauncher = new WeakReference<Launcher>(launcher);
mLocaleChanged = localeChanged;
}
@@ -703,6 +724,8 @@
public void run() {
mRunning = true;
+ android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+
final Launcher launcher = mLauncher.get();
final ContentResolver contentResolver = launcher.getContentResolver();
final PackageManager manager = launcher.getPackageManager();
@@ -722,7 +745,7 @@
LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
try {
- final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
@@ -759,13 +782,13 @@
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
intentDescription = c.getString(intentIndex);
try {
- intent = Intent.getIntent(intentDescription);
+ intent = Intent.parseUri(intentDescription, 0);
} catch (java.net.URISyntaxException e) {
continue;
}
if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- info = getApplicationInfo(manager, intent);
+ info = getApplicationInfo(manager, intent, launcher);
} else {
info = getApplicationInfoShortcut(c, launcher, iconTypeIndex,
iconPackageIndex, iconResourceIndex, iconIndex);
@@ -829,7 +852,7 @@
intent = null;
if (intentDescription != null) {
try {
- intent = Intent.getIntent(intentDescription);
+ intent = Intent.parseUri(intentDescription, 0);
} catch (java.net.URISyntaxException e) {
// Ignore, a live folder might not have a base intent
}
@@ -909,7 +932,7 @@
launcher.onDesktopItemsLoaded();
}
});
- if (mLoadApplications) startApplicationsLoader(launcher);
+ if (mLoadApplications) startApplicationsLoader(launcher, mIsLaunching);
}
if (!mStopped) {
@@ -1073,7 +1096,7 @@
ArrayList<ItemInfo> getDesktopItems() {
return mDesktopItems;
}
-
+
/**
* @return The current list of desktop items
*/
@@ -1089,7 +1112,7 @@
// TODO: write to DB; also check that folder has been added to folders list
mDesktopItems.add(info);
}
-
+
/**
* Remove an item from the desktop
* @param info
@@ -1105,7 +1128,7 @@
void addDesktopAppWidget(LauncherAppWidgetInfo info) {
mDesktopAppWidgets.add(info);
}
-
+
/**
* Remove a widget from the desktop
*/
@@ -1116,16 +1139,17 @@
/**
* Make an ApplicationInfo object for an application
*/
- private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent) {
+ private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent,
+ Context context) {
final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
if (resolveInfo == null) {
return null;
}
-
+
final ApplicationInfo info = new ApplicationInfo();
final ActivityInfo activityInfo = resolveInfo.activityInfo;
- info.icon = activityInfo.loadIcon(manager);
+ info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context);
if (info.title == null || info.title.length() == 0) {
info.title = activityInfo.loadLabel(manager);
}
@@ -1135,11 +1159,11 @@
info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
return info;
}
-
+
/**
* Make an ApplicationInfo object for a sortcut
*/
- private ApplicationInfo getApplicationInfoShortcut(Cursor c, Launcher launcher,
+ private ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context,
int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) {
final ApplicationInfo info = new ApplicationInfo();
@@ -1150,11 +1174,11 @@
case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
String packageName = c.getString(iconPackageIndex);
String resourceName = c.getString(iconResourceIndex);
- PackageManager packageManager = launcher.getPackageManager();
+ PackageManager packageManager = context.getPackageManager();
try {
Resources resources = packageManager.getResourcesForApplication(packageName);
final int id = resources.getIdentifier(resourceName, null, null);
- info.icon = resources.getDrawable(id);
+ info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context);
} catch (Exception e) {
info.icon = packageManager.getDefaultActivityIcon();
}
@@ -1165,14 +1189,19 @@
break;
case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
byte[] data = c.getBlob(iconIndex);
- Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- info.icon = new FastBitmapDrawable(
- Utilities.createBitmapThumbnail(bitmap, launcher));
+ try {
+ Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+ info.icon = new FastBitmapDrawable(
+ Utilities.createBitmapThumbnail(bitmap, context));
+ } catch (Exception e) {
+ packageManager = context.getPackageManager();
+ info.icon = packageManager.getDefaultActivityIcon();
+ }
info.filtered = true;
info.customIcon = true;
break;
default:
- info.icon = launcher.getPackageManager().getDefaultActivityIcon();
+ info.icon = context.getPackageManager().getDefaultActivityIcon();
info.customIcon = false;
break;
}
@@ -1186,7 +1215,7 @@
//noinspection SuspiciousMethodCalls
folder.contents.remove(info);
}
-
+
/**
* Removes a UserFolder from the in-memory list of folders. Does not change the DB.
* @param userFolderInfo
@@ -1194,7 +1223,7 @@
void removeUserFolder(UserFolderInfo userFolderInfo) {
mFolders.remove(userFolderInfo.id);
}
-
+
/**
* Adds an item to the DB if it was not created previously, or move it to a new
* <container, screen, cellX, cellY>
@@ -1209,7 +1238,7 @@
moveItemInDatabase(context, item, container, screen, cellX, cellY);
}
}
-
+
/**
* Move an item in the DB to a new <container, screen, cellX, cellY>
*/
@@ -1219,7 +1248,7 @@
item.screen = screen;
item.cellX = cellX;
item.cellY = cellY;
-
+
final ContentValues values = new ContentValues();
final ContentResolver cr = context.getContentResolver();
@@ -1239,7 +1268,7 @@
final ContentResolver cr = context.getContentResolver();
Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
new String[] { "title", "intent" }, "title=? and intent=?",
- new String[] { title, intent.toURI() }, null);
+ new String[] { title, intent.toUri(0) }, null);
boolean result = false;
try {
result = c.moveToFirst();
@@ -1302,12 +1331,12 @@
item.screen = screen;
item.cellX = cellX;
item.cellY = cellY;
-
+
final ContentValues values = new ContentValues();
final ContentResolver cr = context.getContentResolver();
-
+
item.onAddToDatabase(values);
-
+
Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
@@ -1317,6 +1346,26 @@
}
/**
+ * Add an item to the database in a specified container. Sets the container, screen, cellX and
+ * cellY fields of the item. Also assigns an ID to the item.
+ */
+ static boolean addGestureToDatabase(Context context, ItemInfo item, boolean notify) {
+ final ContentValues values = new ContentValues();
+ final ContentResolver cr = context.getContentResolver();
+
+ item.onAddToDatabase(values);
+
+ Uri result = cr.insert(notify ? LauncherSettings.Gestures.CONTENT_URI :
+ LauncherSettings.Gestures.CONTENT_URI_NO_NOTIFICATION, values);
+
+ if (result != null) {
+ item.id = Integer.parseInt(result.getPathSegments().get(1));
+ }
+
+ return result != null;
+ }
+
+ /**
* Update an item to the database in a specified container.
*/
static void updateItemInDatabase(Context context, ItemInfo item) {
@@ -1327,7 +1376,7 @@
cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
}
-
+
/**
* Removes the specified item from the database
* @param context
@@ -1350,4 +1399,84 @@
cr.delete(LauncherSettings.Favorites.CONTENT_URI,
LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
}
+
+ static void deleteGestureFromDatabase(Context context, ItemInfo item) {
+ final ContentResolver cr = context.getContentResolver();
+
+ cr.delete(LauncherSettings.Gestures.getContentUri(item.id, false), null, null);
+ }
+
+ static void updateGestureInDatabase(Context context, ItemInfo item) {
+ final ContentValues values = new ContentValues();
+ final ContentResolver cr = context.getContentResolver();
+
+ item.onAddToDatabase(values);
+
+ cr.update(LauncherSettings.Gestures.getContentUri(item.id, false), values, null, null);
+ }
+
+
+ ApplicationInfo queryGesture(Context context, String id) {
+ final ContentResolver contentResolver = context.getContentResolver();
+ final PackageManager manager = context.getPackageManager();
+ final Cursor c = contentResolver.query(
+ LauncherSettings.Gestures.CONTENT_URI, null, LauncherSettings.Gestures._ID + "=?",
+ new String[] { id }, null);
+
+ ApplicationInfo info = null;
+
+ try {
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures._ID);
+ final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.INTENT);
+ final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.TITLE);
+ final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_TYPE);
+ final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON);
+ final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_PACKAGE);
+ final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_RESOURCE);
+ final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ITEM_TYPE);
+
+ String intentDescription;
+ Intent intent;
+
+ if (c.moveToNext()) {
+ int itemType = c.getInt(itemTypeIndex);
+
+ switch (itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ intentDescription = c.getString(intentIndex);
+ try {
+ intent = Intent.parseUri(intentDescription, 0);
+ } catch (java.net.URISyntaxException e) {
+ return null;
+ }
+
+ if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ info = getApplicationInfo(manager, intent, context);
+ } else {
+ info = getApplicationInfoShortcut(c, context, iconTypeIndex,
+ iconPackageIndex, iconResourceIndex, iconIndex);
+ }
+
+ if (info == null) {
+ info = new ApplicationInfo();
+ info.icon = manager.getDefaultActivityIcon();
+ }
+
+ info.isGesture = true;
+ info.title = c.getString(titleIndex);
+ info.intent = intent;
+ info.id = c.getLong(idIndex);
+
+ break;
+ }
+ }
+ } catch (Exception e) {
+ w(LOG_TAG, "Could not load gesture with name " + id);
+ } finally {
+ c.close();
+ }
+
+ return info;
+ }
}
diff --git a/src/com/android/launcher/LauncherProvider.java b/src/com/android/launcher/LauncherProvider.java
index a27b746..fc38cdd 100644
--- a/src/com/android/launcher/LauncherProvider.java
+++ b/src/com/android/launcher/LauncherProvider.java
@@ -55,7 +55,7 @@
private static final String DATABASE_NAME = "launcher.db";
- private static final int DATABASE_VERSION = 3;
+ private static final int DATABASE_VERSION = 4;
static final String AUTHORITY = "com.android.launcher.settings";
@@ -63,10 +63,11 @@
static final String EXTRA_BIND_TARGETS = "com.android.launcher.settings.bindtargets";
static final String TABLE_FAVORITES = "favorites";
+ static final String TABLE_GESTURES = "gestures";
static final String PARAMETER_NOTIFY = "notify";
/**
- * {@link Uri} triggered at any registered {@link ContentObserver} when
+ * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
* {@link AppWidgetHost#deleteHost()} is called during database creation.
* Use this to recall {@link AppWidgetHost#startListening()} if needed.
*/
@@ -99,7 +100,7 @@
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(args.table);
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
@@ -220,6 +221,17 @@
"displayMode INTEGER" +
");");
+ db.execSQL("CREATE TABLE gestures (" +
+ "_id INTEGER PRIMARY KEY," +
+ "title TEXT," +
+ "intent TEXT," +
+ "itemType INTEGER," +
+ "iconType INTEGER," +
+ "iconPackage TEXT," +
+ "iconResource TEXT," +
+ "icon BLOB" +
+ ");");
+
// Database was just created, so wipe any previous widgets
if (mAppWidgetHost != null) {
mAppWidgetHost.deleteHost();
@@ -270,7 +282,7 @@
}
private int copyFromCursor(SQLiteDatabase db, Cursor c) {
- final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
@@ -289,7 +301,7 @@
int i = 0;
while (c.moveToNext()) {
ContentValues values = new ContentValues(c.getColumnCount());
- values.put(LauncherSettings.Favorites.ID, c.getLong(idIndex));
+ values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
@@ -352,10 +364,34 @@
convertWidgets(db);
}
}
+
+ if (version < 4) {
+ db.beginTransaction();
+ try {
+ db.execSQL("CREATE TABLE gestures (" +
+ "_id INTEGER PRIMARY KEY," +
+ "title TEXT," +
+ "intent TEXT," +
+ "itemType INTEGER," +
+ "iconType INTEGER," +
+ "iconPackage TEXT," +
+ "iconResource TEXT," +
+ "icon BLOB" +
+ ");");
+ db.setTransactionSuccessful();
+ version = 4;
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(LOG_TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+ }
if (version != DATABASE_VERSION) {
Log.w(LOG_TAG, "Destroying all old data.");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_GESTURES);
onCreate(db);
}
}
@@ -527,7 +563,7 @@
intent.setComponent(cn);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- values.put(Favorites.INTENT, intent.toURI());
+ values.put(Favorites.INTENT, intent.toUri(0));
values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
values.put(Favorites.SPANX, 1);
diff --git a/src/com/android/launcher/LauncherSettings.java b/src/com/android/launcher/LauncherSettings.java
index 60ea0df..8cc2559 100644
--- a/src/com/android/launcher/LauncherSettings.java
+++ b/src/com/android/launcher/LauncherSettings.java
@@ -23,11 +23,109 @@
* Settings related utilities.
*/
class LauncherSettings {
+ static interface BaseLauncherColumns extends BaseColumns {
+ /**
+ * Descriptive name of the gesture that can be displayed to the user.
+ * <P>Type: TEXT</P>
+ */
+ static final String TITLE = "title";
+
+ /**
+ * The Intent URL of the gesture, describing what it points to. This
+ * value is given to {@link android.content.Intent#parseUri(String, int)} to create
+ * an Intent that can be launched.
+ * <P>Type: TEXT</P>
+ */
+ static final String INTENT = "intent";
+
+ /**
+ * The type of the gesture
+ *
+ * <P>Type: INTEGER</P>
+ */
+ static final String ITEM_TYPE = "itemType";
+
+ /**
+ * The gesture is an application
+ */
+ static final int ITEM_TYPE_APPLICATION = 0;
+
+ /**
+ * The gesture is an application created shortcut
+ */
+ static final int ITEM_TYPE_SHORTCUT = 1;
+
+ /**
+ * The icon type.
+ * <P>Type: INTEGER</P>
+ */
+ static final String ICON_TYPE = "iconType";
+
+ /**
+ * The icon is a resource identified by a package name and an integer id.
+ */
+ static final int ICON_TYPE_RESOURCE = 0;
+
+ /**
+ * The icon is a bitmap.
+ */
+ static final int ICON_TYPE_BITMAP = 1;
+
+ /**
+ * The icon package name, if icon type is ICON_TYPE_RESOURCE.
+ * <P>Type: TEXT</P>
+ */
+ static final String ICON_PACKAGE = "iconPackage";
+
+ /**
+ * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
+ * <P>Type: TEXT</P>
+ */
+ static final String ICON_RESOURCE = "iconResource";
+
+ /**
+ * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
+ * <P>Type: BLOB</P>
+ */
+ static final String ICON = "icon";
+ }
+
+ static final class Gestures implements BaseLauncherColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ static final Uri CONTENT_URI = Uri.parse("content://" +
+ LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_GESTURES +
+ "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+
+ /**
+ * The content:// style URL for this table. When this Uri is used, no notification is
+ * sent if the content changes.
+ */
+ static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" +
+ LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_GESTURES +
+ "?" + LauncherProvider.PARAMETER_NOTIFY + "=false");
+
+ /**
+ * The content:// style URL for a given row, identified by its id.
+ *
+ * @param id The row id.
+ * @param notify True to send a notification is the content changes.
+ *
+ * @return The unique content URL for the specified row.
+ */
+ static Uri getContentUri(long id, boolean notify) {
+ return Uri.parse("content://" + LauncherProvider.AUTHORITY +
+ "/" + LauncherProvider.TABLE_GESTURES + "/" + id + "?" +
+ LauncherProvider.PARAMETER_NOTIFY + "=" + notify);
+ }
+ }
+
/**
* Favorites. When changing these values, be sure to update
* {@link com.android.settings.LauncherAppWidgetBinder} as needed.
*/
- static final class Favorites implements BaseColumns {
+ static final class Favorites implements BaseLauncherColumns {
/**
* The content:// style URL for this table
*/
@@ -58,26 +156,6 @@
}
/**
- * The row ID.
- * <p>Type: INTEGER</p>
- */
- static final String ID = "_id";
-
- /**
- * Descriptive name of the favorite that can be displayed to the user.
- * <P>Type: TEXT</P>
- */
- static final String TITLE = "title";
-
- /**
- * The Intent URL of the favorite, describing what it points to. This
- * value is given to {@link android.content.Intent#getIntent} to create
- * an Intent that can be launched.
- * <P>Type: TEXT</P>
- */
- static final String INTENT = "intent";
-
- /**
* The container holding the favorite
* <P>Type: INTEGER</P>
*/
@@ -121,23 +199,6 @@
static final String SPANY = "spanY";
/**
- * The type of the favorite
- *
- * <P>Type: INTEGER</P>
- */
- static final String ITEM_TYPE = "itemType";
-
- /**
- * The favorite is an application
- */
- static final int ITEM_TYPE_APPLICATION = 0;
-
- /**
- * The favorite is an application created shortcut
- */
- static final int ITEM_TYPE_SHORTCUT = 1;
-
- /**
* The favorite is a user created folder
*/
static final int ITEM_TYPE_USER_FOLDER = 2;
@@ -180,43 +241,10 @@
* value is 1, it is an application-created shortcut.
* <P>Type: INTEGER</P>
*/
+ @Deprecated
static final String IS_SHORTCUT = "isShortcut";
/**
- * The icon type.
- * <P>Type: INTEGER</P>
- */
- static final String ICON_TYPE = "iconType";
-
- /**
- * The icon is a resource identified by a package name and an integer id.
- */
- static final int ICON_TYPE_RESOURCE = 0;
-
- /**
- * The icon is a bitmap.
- */
- static final int ICON_TYPE_BITMAP = 1;
-
- /**
- * The icon package name, if icon type is ICON_TYPE_RESOURCE.
- * <P>Type: TEXT</P>
- */
- static final String ICON_PACKAGE = "iconPackage";
-
- /**
- * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
- * <P>Type: TEXT</P>
- */
- static final String ICON_RESOURCE = "iconResource";
-
- /**
- * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
- * <P>Type: BLOB</P>
- */
- static final String ICON = "icon";
-
- /**
* The URI associated with the favorite. It is used, for instance, by
* live folders to find the content provider.
* <P>Type: TEXT</P>
diff --git a/src/com/android/launcher/LiveFolderAdapter.java b/src/com/android/launcher/LiveFolderAdapter.java
index 4a5c077..c405041 100644
--- a/src/com/android/launcher/LiveFolderAdapter.java
+++ b/src/com/android/launcher/LiveFolderAdapter.java
@@ -116,7 +116,7 @@
if (holder.intentIndex != -1) {
try {
- holder.intent = Intent.getIntent(cursor.getString(holder.intentIndex));
+ holder.intent = Intent.parseUri(cursor.getString(holder.intentIndex), 0);
} catch (URISyntaxException e) {
// Ignore
}
diff --git a/src/com/android/launcher/LiveFolderIcon.java b/src/com/android/launcher/LiveFolderIcon.java
index 33cb0b7..14a4ee6 100644
--- a/src/com/android/launcher/LiveFolderIcon.java
+++ b/src/com/android/launcher/LiveFolderIcon.java
@@ -41,8 +41,8 @@
final Resources resources = launcher.getResources();
Drawable d = folderInfo.icon;
if (d == null) {
- resources.getDrawable(R.drawable.ic_launcher_folder);
- d = Utilities.createIconThumbnail(d, launcher);
+ d = Utilities.createIconThumbnail(
+ resources.getDrawable(R.drawable.ic_launcher_folder), launcher);
folderInfo.filtered = true;
}
icon.setCompoundDrawablesWithIntrinsicBounds(null, d, null, null);
diff --git a/src/com/android/launcher/LiveFolderInfo.java b/src/com/android/launcher/LiveFolderInfo.java
index 2432cc3..ec865a4 100644
--- a/src/com/android/launcher/LiveFolderInfo.java
+++ b/src/com/android/launcher/LiveFolderInfo.java
@@ -63,7 +63,7 @@
values.put(LauncherSettings.Favorites.TITLE, title.toString());
values.put(LauncherSettings.Favorites.URI, uri.toString());
if (baseIntent != null) {
- values.put(LauncherSettings.Favorites.INTENT, baseIntent.toURI());
+ values.put(LauncherSettings.Favorites.INTENT, baseIntent.toUri(0));
}
values.put(LauncherSettings.Favorites.ICON_TYPE, LauncherSettings.Favorites.ICON_TYPE_RESOURCE);
values.put(LauncherSettings.Favorites.DISPLAY_MODE, displayMode);
diff --git a/src/com/android/launcher/Search.java b/src/com/android/launcher/Search.java
index 71ab7ef..96c0022 100644
--- a/src/com/android/launcher/Search.java
+++ b/src/com/android/launcher/Search.java
@@ -16,71 +16,59 @@
package com.android.launcher;
-import android.app.ISearchManager;
-import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
+import android.content.res.Configuration;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.server.search.SearchableInfo;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLongClickListener;
-import android.widget.AdapterView;
-import android.widget.AutoCompleteTextView;
-import android.widget.CursorAdapter;
-import android.widget.Filter;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.inputmethod.InputMethodManager;
import android.widget.ImageButton;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
-public class Search extends LinearLayout implements OnClickListener, OnKeyListener,
- OnLongClickListener, TextWatcher, OnItemClickListener, OnItemSelectedListener {
+public class Search extends LinearLayout
+ implements OnClickListener, OnKeyListener, OnLongClickListener {
+
+ // Speed at which the widget slides up/down, in pixels/ms.
+ private static final float ANIMATION_VELOCITY = 1.0f;
private final String TAG = "SearchWidget";
- private AutoCompleteTextView mSearchText;
- private ImageButton mGoButton;
+ private Launcher mLauncher;
+
+ private TextView mSearchText;
private ImageButton mVoiceButton;
- private OnLongClickListener mLongClickListener;
-
- // Support for suggestions
- private SuggestionsAdapter mSuggestionsAdapter;
- private SearchableInfo mSearchable;
- private String mSuggestionAction = null;
- private Uri mSuggestionData = null;
- private String mSuggestionQuery = null;
- private int mItemSelected = -1;
-
+
+ /** The animation that morphs the search widget to the search dialog. */
+ private Animation mMorphAnimation;
+
+ /** The animation that morphs the search widget back to its normal position. */
+ private Animation mUnmorphAnimation;
+
+ // These four are passed to Launcher.startSearch() when the search widget
+ // has finished morphing. They are instance variables to make it possible to update
+ // them while the widget is morphing.
+ private String mInitialQuery;
+ private boolean mSelectInitialQuery;
+ private Bundle mAppSearchData;
+ private boolean mGlobalSearch;
+
// For voice searching
private Intent mVoiceSearchIntent;
- private Rect mTempRect = new Rect();
- private boolean mRestoreFocus = false;
-
/**
* Used to inflate the Workspace from XML.
*
@@ -89,293 +77,235 @@
*/
public Search(Context context, AttributeSet attrs) {
super(context, attrs);
+
+ Interpolator interpolator = new AccelerateDecelerateInterpolator();
+
+ mMorphAnimation = new ToParentOriginAnimation();
+ // no need to apply transformation before the animation starts,
+ // since the gadget is already in its normal place.
+ mMorphAnimation.setFillBefore(false);
+ // stay in the top position after the animation finishes
+ mMorphAnimation.setFillAfter(true);
+ mMorphAnimation.setInterpolator(interpolator);
+ mMorphAnimation.setAnimationListener(new Animation.AnimationListener() {
+ // The amount of time before the animation ends to show the search dialog.
+ private static final long TIME_BEFORE_ANIMATION_END = 80;
+
+ // The runnable which we'll pass to our handler to show the search dialog.
+ private final Runnable mShowSearchDialogRunnable = new Runnable() {
+ public void run() {
+ showSearchDialog();
+ }
+ };
+
+ public void onAnimationEnd(Animation animation) { }
+ public void onAnimationRepeat(Animation animation) { }
+ public void onAnimationStart(Animation animation) {
+ // Make the search dialog show up ideally *just* as the animation reaches
+ // the top, to aid the illusion that the widget becomes the search dialog.
+ // Otherwise, there is a short delay when the widget reaches the top before
+ // the search dialog shows. We do this roughly 80ms before the animation ends.
+ getHandler().postDelayed(
+ mShowSearchDialogRunnable,
+ Math.max(mMorphAnimation.getDuration() - TIME_BEFORE_ANIMATION_END, 0));
+ }
+ });
+
+ mUnmorphAnimation = new FromParentOriginAnimation();
+ // stay in the top position until the animation starts
+ mUnmorphAnimation.setFillBefore(true);
+ // no need to apply transformation after the animation finishes,
+ // since the gadget is now back in its normal place.
+ mUnmorphAnimation.setFillAfter(false);
+ mUnmorphAnimation.setInterpolator(interpolator);
+ mUnmorphAnimation.setAnimationListener(new Animation.AnimationListener(){
+ public void onAnimationEnd(Animation animation) {
+ clearAnimation();
+ }
+ public void onAnimationRepeat(Animation animation) { }
+ public void onAnimationStart(Animation animation) { }
+ });
mVoiceSearchIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
mVoiceSearchIntent.putExtra(android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL,
android.speech.RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
}
-
+
/**
- * Implements OnClickListener (for button)
+ * Implements OnClickListener.
*/
public void onClick(View v) {
- if (v == mGoButton) {
- query();
- } else if (v == mVoiceButton) {
- try {
- getContext().startActivity(mVoiceSearchIntent);
- } catch (ActivityNotFoundException ex) {
- // Should not happen, since we check the availability of
- // voice search before showing the button. But just in case...
- Log.w(TAG, "Could not find voice search activity");
+ if (v == mVoiceButton) {
+ startVoiceSearch();
+ } else {
+ mLauncher.onSearchRequested();
+ }
+ }
+
+ private void startVoiceSearch() {
+ try {
+ getContext().startActivity(mVoiceSearchIntent);
+ } catch (ActivityNotFoundException ex) {
+ // Should not happen, since we check the availability of
+ // voice search before showing the button. But just in case...
+ Log.w(TAG, "Could not find voice search activity");
+ }
+ }
+
+ /**
+ * Sets the query text. The query field is not editable, instead we forward
+ * the key events to the launcher, which keeps track of the text,
+ * calls setQuery() to show it, and gives it to the search dialog.
+ */
+ public void setQuery(String query) {
+ mSearchText.setText(query, TextView.BufferType.NORMAL);
+ }
+
+ /**
+ * Morph the search gadget to the search dialog.
+ * See {@link Activity.startSearch()} for the arguments.
+ */
+ public void startSearch(String initialQuery, boolean selectInitialQuery,
+ Bundle appSearchData, boolean globalSearch) {
+ mInitialQuery = initialQuery;
+ mSelectInitialQuery = selectInitialQuery;
+ mAppSearchData = appSearchData;
+ mGlobalSearch = globalSearch;
+
+ if (isAtTop()) {
+ showSearchDialog();
+ } else {
+ // Call up the keyboard before we actually call the search dialog so that it
+ // (hopefully) animates in at about the same time as the widget animation, and
+ // so that it becomes available as soon as possible. Only do this if a hard
+ // keyboard is not currently available.
+ if (getContext().getResources().getConfiguration().hardKeyboardHidden ==
+ Configuration.HARDKEYBOARDHIDDEN_YES) {
+ // Make sure the text field is not focusable, so it's not responsible for
+ // causing the whole view to shift up to accommodate the keyboard.
+ mSearchText.setFocusable(false);
+
+ InputMethodManager inputManager = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputManager.showSoftInputUnchecked(0, null);
+ }
+
+ // Start the animation, unless it has already started.
+ if (getAnimation() != mMorphAnimation) {
+ mMorphAnimation.setDuration(getAnimationDuration());
+ startAnimation(mMorphAnimation);
}
}
}
- private void query() {
- String query = mSearchText.getText().toString();
- if (TextUtils.getTrimmedLength(mSearchText.getText()) == 0) {
- return;
+ /**
+ * Shows the system search dialog immediately, without any animation.
+ */
+ private void showSearchDialog() {
+ mLauncher.showSearchDialog(
+ mInitialQuery, mSelectInitialQuery, mAppSearchData, mGlobalSearch);
+ }
+
+ /**
+ * Restore the search gadget to its normal position.
+ *
+ * @param animate Whether to animate the movement of the gadget.
+ */
+ public void stopSearch(boolean animate) {
+ setQuery("");
+
+ // Set the search field back to focusable after making it unfocusable in
+ // startSearch, so that the home screen doesn't try to shift around when the
+ // keyboard comes up.
+ mSearchText.setFocusable(true);
+ // Only restore if we are not already restored.
+ if (getAnimation() == mMorphAnimation) {
+ if (animate && !isAtTop()) {
+ mUnmorphAnimation.setDuration(getAnimationDuration());
+ startAnimation(mUnmorphAnimation);
+ } else {
+ clearAnimation();
+ }
}
- Bundle appData = new Bundle();
- appData.putString(SearchManager.SOURCE, "launcher-widget");
- sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, 0, null, mSearchable);
- clearQuery();
+ }
+
+ private boolean isAtTop() {
+ return getTop() == 0;
+ }
+
+ private int getAnimationDuration() {
+ return (int) (getTop() / ANIMATION_VELOCITY);
+ }
+
+ /**
+ * Modify clearAnimation() to invalidate the parent. This works around
+ * an issue where the region where the end of the animation placed the view
+ * was not redrawn after clearing the animation.
+ */
+ @Override
+ public void clearAnimation() {
+ Animation animation = getAnimation();
+ if (animation != null) {
+ super.clearAnimation();
+ if (animation.hasEnded()
+ && animation.getFillAfter()
+ && animation.willChangeBounds()) {
+ ((View) getParent()).invalidate();
+ } else {
+ invalidate();
+ }
+ }
}
- /**
- * Assemble a search intent and send it.
- *
- * This is copied from SearchDialog.
- *
- * @param action The intent to send, typically Intent.ACTION_SEARCH
- * @param data The data for the intent
- * @param query The user text entered (so far)
- * @param appData The app data bundle (if supplied)
- * @param actionKey If the intent was triggered by an action key, e.g. KEYCODE_CALL, it will
- * be sent here. Pass KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
- * @param actionMsg If the intent was triggered by an action key, e.g. KEYCODE_CALL, the
- * corresponding tag message will be sent here. Pass null for no actionKey message.
- * @param si Reference to the current SearchableInfo. Passed here so it can be used even after
- * we've called dismiss(), which attempts to null mSearchable.
- */
- private void sendLaunchIntent(final String action, final Uri data, final String query,
- final Bundle appData, int actionKey, final String actionMsg, final SearchableInfo si) {
- Intent launcher = new Intent(action);
- launcher.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- if (query != null) {
- launcher.putExtra(SearchManager.QUERY, query);
- }
-
- if (data != null) {
- launcher.setData(data);
- }
-
- if (appData != null) {
- launcher.putExtra(SearchManager.APP_DATA, appData);
- }
-
- // add launch info (action key, etc.)
- if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
- launcher.putExtra(SearchManager.ACTION_KEY, actionKey);
- launcher.putExtra(SearchManager.ACTION_MSG, actionMsg);
- }
-
- // attempt to enforce security requirement (no 3rd-party intents)
- if (si != null) {
- launcher.setComponent(si.mSearchActivity);
- }
-
- getContext().startActivity(launcher);
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- if (!hasWindowFocus && hasFocus()) {
- mRestoreFocus = true;
- }
-
- super.onWindowFocusChanged(hasWindowFocus);
-
- if (hasWindowFocus && mRestoreFocus) {
- if (isInTouchMode()) {
- final AutoCompleteTextView searchText = mSearchText;
- searchText.setSelectAllOnFocus(false);
- searchText.requestFocusFromTouch();
- searchText.setSelectAllOnFocus(true);
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (!event.isSystem() &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
+ (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
+ // Forward key events to Launcher, which will forward text
+ // to search dialog
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ return mLauncher.onKeyDown(keyCode, event);
+ case KeyEvent.ACTION_MULTIPLE:
+ return mLauncher.onKeyMultiple(keyCode, event.getRepeatCount(), event);
+ case KeyEvent.ACTION_UP:
+ return mLauncher.onKeyUp(keyCode, event);
}
- mRestoreFocus = false;
}
- }
-
- /**
- * Implements TextWatcher (for EditText)
- */
- public void beforeTextChanged(CharSequence s, int start, int before, int after) {
- }
-
- /**
- * Implements TextWatcher (for EditText)
- */
- public void onTextChanged(CharSequence s, int start, int before, int after) {
- // enable the button if we have one or more non-space characters
- boolean enabled = TextUtils.getTrimmedLength(mSearchText.getText()) != 0;
- mGoButton.setEnabled(enabled);
- mGoButton.setFocusable(enabled);
- }
-
- /**
- * Implements TextWatcher (for EditText)
- */
- public void afterTextChanged(Editable s) {
- }
-
- /**
- * Implements OnKeyListener (for EditText and for button)
- *
- * This plays some games with state in order to "soften" the strength of suggestions
- * presented. Suggestions should not be used unless the user specifically navigates to them
- * (or clicks them, in which case it's obvious). This is not the way that AutoCompleteTextBox
- * normally works.
- */
- public final boolean onKey(View v, int keyCode, KeyEvent event) {
- if (v == mSearchText) {
- boolean searchTrigger = (keyCode == KeyEvent.KEYCODE_ENTER ||
- keyCode == KeyEvent.KEYCODE_SEARCH ||
- keyCode == KeyEvent.KEYCODE_DPAD_CENTER);
- if (event.getAction() == KeyEvent.ACTION_UP) {
-// Log.d(TAG, "onKey() ACTION_UP isPopupShowing:" + mSearchText.isPopupShowing());
- if (!mSearchText.isPopupShowing()) {
- if (searchTrigger) {
- query();
- return true;
- }
- }
- } else {
-// Log.d(TAG, "onKey() ACTION_DOWN isPopupShowing:" + mSearchText.isPopupShowing() +
-// " mItemSelected="+ mItemSelected);
- if (searchTrigger && mItemSelected < 0) {
- query();
- return true;
- }
- }
- } else if (v == mGoButton || v == mVoiceButton) {
- boolean handled = false;
- if (!event.isSystem() &&
- (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
- if (mSearchText.requestFocus()) {
- handled = mSearchText.dispatchKeyEvent(event);
- }
- }
- return handled;
- }
-
return false;
}
-
- @Override
- public void setOnLongClickListener(OnLongClickListener l) {
- super.setOnLongClickListener(l);
- mLongClickListener = l;
- }
-
+
/**
- * Implements OnLongClickListener (for button)
+ * Implements OnLongClickListener to pass long clicks on child views
+ * to the widget. This makes it possible to pick up the widget by long
+ * clicking on the text field or a button.
*/
public boolean onLongClick(View v) {
- // Pretend that a long press on a child view is a long press on the search widget
- if (mLongClickListener != null) {
- return mLongClickListener.onLongClick(this);
- }
- return false;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- // Request focus unless the user tapped on the voice search button
- final int x = (int) ev.getX();
- final int y = (int) ev.getY();
- final Rect frame = mTempRect;
- mVoiceButton.getHitRect(frame);
- if (!frame.contains(x, y)) {
- requestFocusFromTouch();
- }
- return super.onInterceptTouchEvent(ev);
- }
-
- /**
- * In order to keep things simple, the external trigger will clear the query just before
- * focusing, so as to give you a fresh query. This way we eliminate any sources of
- * accidental query launching.
- */
- public void clearQuery() {
- mSearchText.setText(null);
+ return performLongClick();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mSearchText = (AutoCompleteTextView) findViewById(R.id.input);
- // TODO: This can be confusing when the user taps the text field to give the focus
- // (it is not necessary but I ran into this issue several times myself)
- // mTitleInput.setOnClickListener(this);
- mSearchText.setOnKeyListener(this);
- mSearchText.addTextChangedListener(this);
-
- mGoButton = (ImageButton) findViewById(R.id.search_go_btn);
+ mSearchText = (TextView) findViewById(R.id.search_src_text);
mVoiceButton = (ImageButton) findViewById(R.id.search_voice_btn);
- mGoButton.setOnClickListener(this);
+
+ mSearchText.setOnKeyListener(this);
+
+ mSearchText.setOnClickListener(this);
mVoiceButton.setOnClickListener(this);
- mGoButton.setOnKeyListener(this);
- mVoiceButton.setOnKeyListener(this);
-
+ setOnClickListener(this);
+
mSearchText.setOnLongClickListener(this);
- mGoButton.setOnLongClickListener(this);
mVoiceButton.setOnLongClickListener(this);
-
- // disable the button since we start out w/empty input
- mGoButton.setEnabled(false);
- mGoButton.setFocusable(false);
-
- configureSearchableInfo();
- configureSuggestions();
+
configureVoiceSearchButton();
}
-
- /**
- * Cache of popup padding value after read from {@link Resources}.
- */
- private static float mPaddingInset = -1;
-
- /**
- * When our size is changed, pass down adjusted width and offset values to
- * correctly center the {@link AutoCompleteTextView} popup and include our
- * padding.
- */
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (changed) {
- if (mPaddingInset == -1) {
- mPaddingInset = getResources().getDimension(R.dimen.search_widget_inset);
- }
-
- // Fill entire width of widget, minus padding inset
- float paddedWidth = getWidth() - (mPaddingInset * 2);
- float paddedOffset = -(mSearchText.getLeft() - mPaddingInset);
-
- mSearchText.setDropDownWidth((int) paddedWidth);
- mSearchText.setDropDownHorizontalOffset((int) paddedOffset);
- }
- }
-
- /**
- * Read the searchable info from the search manager
- */
- private void configureSearchableInfo() {
- ISearchManager sms;
- SearchableInfo searchable;
- sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE));
- try {
- // TODO null isn't the published use of this API, but it works when global=true
- // TODO better implementation: defer all of this, let Home set it up
- searchable = sms.getSearchableInfo(null, true);
- } catch (RemoteException e) {
- searchable = null;
- }
- if (searchable == null) {
- // no suggestions so just get out (no need to continue)
- return;
- }
- mSearchable = searchable;
- }
-
+
/**
* If appropriate & available, configure voice search
*
@@ -384,346 +314,45 @@
* voice search.
*/
private void configureVoiceSearchButton() {
- boolean voiceSearchVisible = false;
- if (mSearchable.getVoiceSearchEnabled() && mSearchable.getVoiceSearchLaunchWebSearch()) {
- // Enable the voice search button if there is an activity that can handle it
- PackageManager pm = getContext().getPackageManager();
- ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
- PackageManager.MATCH_DEFAULT_ONLY);
- voiceSearchVisible = ri != null;
- }
-
+ // Enable the voice search button if there is an activity that can handle it
+ PackageManager pm = getContext().getPackageManager();
+ ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ boolean voiceSearchVisible = ri != null;
+
// finally, set visible state of voice search button, as appropriate
mVoiceButton.setVisibility(voiceSearchVisible ? View.VISIBLE : View.GONE);
}
-
- /** The rest of the class deals with providing search suggestions */
-
+
/**
- * Set up the suggestions provider mechanism
+ * Sets the {@link Launcher} that this gadget will call on to display the search dialog.
*/
- private void configureSuggestions() {
- // get SearchableInfo
-
- mSearchText.setOnItemClickListener(this);
- mSearchText.setOnItemSelectedListener(this);
-
- // attach the suggestions adapter
- mSuggestionsAdapter = new SuggestionsAdapter(mContext,
- com.android.internal.R.layout.search_dropdown_item_2line, null,
- SuggestionsAdapter.TWO_LINE_FROM, SuggestionsAdapter.TWO_LINE_TO, mSearchable);
- mSearchText.setAdapter(mSuggestionsAdapter);
+ public void setLauncher(Launcher launcher) {
+ mLauncher = launcher;
}
-
- /**
- * Remove internal cursor references when detaching from window which
- * prevents {@link Context} leaks.
- */
- @Override
- public void onDetachedFromWindow() {
- if (mSuggestionsAdapter != null) {
- mSuggestionsAdapter.changeCursor(null);
- mSuggestionsAdapter = null;
- }
- }
-
- /**
- * Implements OnItemClickListener
- */
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-// Log.d(TAG, "onItemClick() position " + position);
- launchSuggestion(mSuggestionsAdapter, position);
- }
-
+
/**
- * Implements OnItemSelectedListener
+ * Moves the view to the top left corner of its parent.
*/
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-// Log.d(TAG, "onItemSelected() position " + position);
- mItemSelected = position;
- }
-
- /**
- * Implements OnItemSelectedListener
- */
- public void onNothingSelected(AdapterView<?> parent) {
-// Log.d(TAG, "onNothingSelected()");
- mItemSelected = -1;
- }
-
- /**
- * Code to launch a suggestion query.
- *
- * This is copied from SearchDialog.
- *
- * @param ca The CursorAdapter containing the suggestions
- * @param position The suggestion we'll be launching from
- *
- * @return Returns true if a successful launch, false if could not (e.g. bad position)
- */
- private boolean launchSuggestion(CursorAdapter ca, int position) {
- if (ca != null) {
- Cursor c = ca.getCursor();
- if ((c != null) && c.moveToPosition(position)) {
- setupSuggestionIntent(c, mSearchable);
-
- SearchableInfo si = mSearchable;
- String suggestionAction = mSuggestionAction;
- Uri suggestionData = mSuggestionData;
- String suggestionQuery = mSuggestionQuery;
- sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, null,
- KeyEvent.KEYCODE_UNKNOWN, null, si);
- clearQuery();
- return true;
- }
- }
- return false;
- }
-
- /**
- * When a particular suggestion has been selected, perform the various lookups required
- * to use the suggestion. This includes checking the cursor for suggestion-specific data,
- * and/or falling back to the XML for defaults; It also creates REST style Uri data when
- * the suggestion includes a data id.
- *
- * NOTE: Return values are in member variables mSuggestionAction, mSuggestionData and
- * mSuggestionQuery.
- *
- * This is copied from SearchDialog.
- *
- * @param c The suggestions cursor, moved to the row of the user's selection
- * @param si The searchable activity's info record
- */
- void setupSuggestionIntent(Cursor c, SearchableInfo si) {
- try {
- // use specific action if supplied, or default action if supplied, or fixed default
- mSuggestionAction = null;
- int column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
- if (column >= 0) {
- final String action = c.getString(column);
- if (action != null) {
- mSuggestionAction = action;
- }
- }
- if (mSuggestionAction == null) {
- mSuggestionAction = si.getSuggestIntentAction();
- }
- if (mSuggestionAction == null) {
- mSuggestionAction = Intent.ACTION_SEARCH;
- }
-
- // use specific data if supplied, or default data if supplied
- String data = null;
- column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
- if (column >= 0) {
- final String rowData = c.getString(column);
- if (rowData != null) {
- data = rowData;
- }
- }
- if (data == null) {
- data = si.getSuggestIntentData();
- }
-
- // then, if an ID was provided, append it.
- if (data != null) {
- column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
- if (column >= 0) {
- final String id = c.getString(column);
- if (id != null) {
- data = data + "/" + Uri.encode(id);
- }
- }
- }
- mSuggestionData = (data == null) ? null : Uri.parse(data);
-
- mSuggestionQuery = null;
- column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
- if (column >= 0) {
- final String query = c.getString(column);
- if (query != null) {
- mSuggestionQuery = query;
- }
- }
- } catch (RuntimeException e ) {
- int rowNum;
- try { // be really paranoid now
- rowNum = c.getPosition();
- } catch (RuntimeException e2 ) {
- rowNum = -1;
- }
- Log.w(TAG, "Search Suggestions cursor at row " + rowNum +
- " returned exception" + e.toString());
- }
- }
-
- SearchAutoCompleteTextView getSearchInputField() {
- return (SearchAutoCompleteTextView) mSearchText;
- }
-
- /**
- * This class provides the filtering-based interface to suggestions providers.
- * It is hardwired in a couple of places to support GoogleSearch - for example, it supports
- * two-line suggestions, but it does not support icons.
- */
- private static class SuggestionsAdapter extends SimpleCursorAdapter {
- public final static String[] TWO_LINE_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1,
- SearchManager.SUGGEST_COLUMN_TEXT_2 };
- public final static int[] TWO_LINE_TO = {com.android.internal.R.id.text1,
- com.android.internal.R.id.text2};
-
- private final String TAG = "SuggestionsAdapter";
-
- Filter mFilter;
- SearchableInfo mSearchable;
- private Resources mProviderResources;
- String[] mFromStrings;
-
- public SuggestionsAdapter(Context context, int layout, Cursor c,
- String[] from, int[] to, SearchableInfo searchable) {
- super(context, layout, c, from, to);
- mFromStrings = from;
- mSearchable = searchable;
-
- // set up provider resources (gives us icons, etc.)
- Context activityContext = mSearchable.getActivityContext(mContext);
- Context providerContext = mSearchable.getProviderContext(mContext, activityContext);
- mProviderResources = providerContext.getResources();
- }
-
- /**
- * Use the search suggestions provider to obtain a live cursor. This will be called
- * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
- * The results will be processed in the UI thread and changeCursor() will be called.
- */
+ private class ToParentOriginAnimation extends Animation {
@Override
- public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
- String query = (constraint == null) ? "" : constraint.toString();
- return getSuggestions(mSearchable, query);
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float dx = -getLeft() * interpolatedTime;
+ float dy = -getTop() * interpolatedTime;
+ t.getMatrix().setTranslate(dx, dy);
}
-
- /**
- * Overriding this allows us to write the selected query back into the box.
- * NOTE: This is a vastly simplified version of SearchDialog.jamQuery() and does
- * not universally support the search API. But it is sufficient for Google Search.
- */
- @Override
- public CharSequence convertToString(Cursor cursor) {
- CharSequence result = null;
- if (cursor != null) {
- int column = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
- if (column >= 0) {
- final String query = cursor.getString(column);
- if (query != null) {
- result = query;
- }
- }
- }
- return result;
- }
-
- /**
- * Get the query cursor for the search suggestions.
- *
- * TODO this is functionally identical to the version in SearchDialog.java. Perhaps it
- * could be hoisted into SearchableInfo or some other shared spot.
- *
- * @param query The search text entered (so far)
- * @return Returns a cursor with suggestions, or null if no suggestions
- */
- private Cursor getSuggestions(final SearchableInfo searchable, final String query) {
- Cursor cursor = null;
- if (searchable.getSuggestAuthority() != null) {
- try {
- StringBuilder uriStr = new StringBuilder("content://");
- uriStr.append(searchable.getSuggestAuthority());
-
- // if content path provided, insert it now
- final String contentPath = searchable.getSuggestPath();
- if (contentPath != null) {
- uriStr.append('/');
- uriStr.append(contentPath);
- }
-
- // append standard suggestion query path
- uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY);
-
- // inject query, either as selection args or inline
- String[] selArgs = null;
- if (searchable.getSuggestSelection() != null) { // use selection if provided
- selArgs = new String[] {query};
- } else {
- uriStr.append('/'); // no sel, use REST pattern
- uriStr.append(Uri.encode(query));
- }
-
- // finally, make the query
- cursor = mContext.getContentResolver().query(
- Uri.parse(uriStr.toString()), null,
- searchable.getSuggestSelection(), selArgs,
- null);
- } catch (RuntimeException e) {
- Log.w(TAG, "Search Suggestions query returned exception " + e.toString());
- cursor = null;
- }
- }
-
- return cursor;
- }
-
- /**
- * Overriding this allows us to affect the way that an icon is loaded. Specifically,
- * we can be more controlling about the resource path (and allow icons to come from other
- * packages).
- *
- * TODO: This is 100% identical to the version in SearchDialog.java
- *
- * @param v ImageView to receive an image
- * @param value the value retrieved from the cursor
- */
- @Override
- public void setViewImage(ImageView v, String value) {
- int resID;
- Drawable img = null;
-
- try {
- resID = Integer.parseInt(value);
- if (resID != 0) {
- img = mProviderResources.getDrawable(resID);
- }
- } catch (NumberFormatException nfe) {
- // img = null;
- } catch (NotFoundException e2) {
- // img = null;
- }
-
- // finally, set the image to whatever we've gotten
- v.setImageDrawable(img);
- }
-
- /**
- * This method is overridden purely to provide a bit of protection against
- * flaky content providers.
- *
- * TODO: This is 100% identical to the version in SearchDialog.java
- *
- * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- try {
- return super.getView(position, convertView, parent);
- } catch (RuntimeException e) {
- Log.w(TAG, "Search Suggestions cursor returned exception " + e.toString());
- // what can I return here?
- View v = newView(mContext, mCursor, parent);
- if (v != null) {
- TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1);
- tv.setText(e.toString());
- }
- return v;
- }
- }
-
}
+
+ /**
+ * Moves the view from the top left corner of its parent.
+ */
+ private class FromParentOriginAnimation extends Animation {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float dx = -getLeft() * (1.0f - interpolatedTime);
+ float dy = -getTop() * (1.0f - interpolatedTime);
+ t.getMatrix().setTranslate(dx, dy);
+ }
+ }
+
}
diff --git a/src/com/android/launcher/SearchAutoCompleteTextView.java b/src/com/android/launcher/SearchAutoCompleteTextView.java
deleted file mode 100644
index e25a8f1..0000000
--- a/src/com/android/launcher/SearchAutoCompleteTextView.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher;
-
-import android.widget.AutoCompleteTextView;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.Message;
-import android.util.AttributeSet;
-import android.graphics.Rect;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-import android.app.Activity;
-
-/**
- * This class is not for the faint of heart. Home works in the pan & scan
- * soft input mode. However, this mode gets rid of the soft keyboard on rotation,
- * which is a probelm when the Search widget has focus. This special class
- * changes Home's soft input method temporarily as long as the Search widget holds
- * the focus. This way, the soft keyboard remains after rotation.
- */
-public class SearchAutoCompleteTextView extends AutoCompleteTextView {
- private boolean mShowKeyboard;
-
- private Handler mLoseFocusHandler = new Handler() {
- public void handleMessage(Message msg) {
- if (msg.what == 1 && !hasFocus()) {
- // Hide the soft keyboard when the search widget loses the focus
- InputMethodManager.peekInstance().hideSoftInputFromWindow(getWindowToken(), 0);
- }
- }
- };
-
- public SearchAutoCompleteTextView(Context context) {
- super(context);
- }
-
- public SearchAutoCompleteTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SearchAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-
- final WindowManager.LayoutParams lp = ((Activity) getContext()).getWindow().getAttributes();
- if (gainFocus) {
- lp.softInputMode =
- (lp.softInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) |
- WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
- } else {
- //noinspection PointlessBitwiseExpression
- lp.softInputMode =
- (lp.softInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) |
- WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
- // If we don't immediately gain focus, we want to hide the IME.
- mLoseFocusHandler.sendEmptyMessage(1);
- }
-
- if (getWindowToken() != null) {
- final WindowManager manager = (WindowManager)
- getContext().getSystemService(Context.WINDOW_SERVICE);
- manager.updateViewLayout(getRootView(), lp);
-
- if (mShowKeyboard) {
- if (getContext().getResources().getConfiguration().hardKeyboardHidden ==
- Configuration.HARDKEYBOARDHIDDEN_YES) {
- InputMethodManager inputManager = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- inputManager.showSoftInput(this, 0);
- }
- mShowKeyboard = false;
- }
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- // See Workspace#focusOnSearch()
- setFocusableInTouchMode(false);
- }
-
- void showKeyboardOnNextFocus() {
- mShowKeyboard = true;
- }
-}
diff --git a/src/com/android/launcher/UninstallShortcutReceiver.java b/src/com/android/launcher/UninstallShortcutReceiver.java
index e490f9c..9fd6298 100644
--- a/src/com/android/launcher/UninstallShortcutReceiver.java
+++ b/src/com/android/launcher/UninstallShortcutReceiver.java
@@ -22,11 +22,19 @@
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
+import android.widget.Toast;
import java.net.URISyntaxException;
public class UninstallShortcutReceiver extends BroadcastReceiver {
+ private static final String ACTION_UNINSTALL_SHORTCUT =
+ "com.android.launcher.action.UNINSTALL_SHORTCUT";
+
public void onReceive(Context context, Intent data) {
+ if (!ACTION_UNINSTALL_SHORTCUT.equals(data.getAction())) {
+ return;
+ }
+
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
@@ -34,7 +42,7 @@
if (intent != null && name != null) {
final ContentResolver cr = context.getContentResolver();
Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
- new String[] { LauncherSettings.Favorites.ID, LauncherSettings.Favorites.INTENT },
+ new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT },
LauncherSettings.Favorites.TITLE + "=?", new String[] { name }, null);
final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
@@ -45,7 +53,7 @@
try {
while (c.moveToNext()) {
try {
- if (intent.filterEquals(Intent.getIntent(c.getString(intentIndex)))) {
+ if (intent.filterEquals(Intent.parseUri(c.getString(intentIndex), 0))) {
final long id = c.getLong(idIndex);
final Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
cr.delete(uri, null, null);
@@ -62,7 +70,11 @@
c.close();
}
- if (changed) cr.notifyChange(LauncherSettings.Favorites.CONTENT_URI, null);
+ if (changed) {
+ cr.notifyChange(LauncherSettings.Favorites.CONTENT_URI, null);
+ Toast.makeText(context, context.getString(R.string.shortcut_uninstalled, name),
+ Toast.LENGTH_SHORT).show();
+ }
}
}
}
diff --git a/src/com/android/launcher/UserFolder.java b/src/com/android/launcher/UserFolder.java
index 1044e96..6cdfed9 100644
--- a/src/com/android/launcher/UserFolder.java
+++ b/src/com/android/launcher/UserFolder.java
@@ -1,6 +1,7 @@
package com.android.launcher;
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -33,6 +34,10 @@
return (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && item.container != mInfo.id;
}
+
+ public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+ return null;
+ }
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
final ApplicationInfo item = (ApplicationInfo) dragInfo;
diff --git a/src/com/android/launcher/Utilities.java b/src/com/android/launcher/Utilities.java
index cb8976c..33b084b 100644
--- a/src/com/android/launcher/Utilities.java
+++ b/src/com/android/launcher/Utilities.java
@@ -16,6 +16,7 @@
package com.android.launcher;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.graphics.Bitmap;
@@ -80,24 +81,35 @@
static Drawable createIconThumbnail(Drawable icon, Context context) {
if (sIconWidth == -1) {
final Resources resources = context.getResources();
- sIconWidth = sIconHeight = (int) resources.getDimension(
- android.R.dimen.app_icon_size);
+ sIconWidth = sIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size);
}
int width = sIconWidth;
int height = sIconHeight;
- final int iconWidth = icon.getIntrinsicWidth();
- final int iconHeight = icon.getIntrinsicHeight();
-
+ float scale = 1.0f;
if (icon instanceof PaintDrawable) {
PaintDrawable painter = (PaintDrawable) icon;
painter.setIntrinsicWidth(width);
painter.setIntrinsicHeight(height);
+ } else if (icon instanceof BitmapDrawable) {
+ float displayDensity = context.getResources().getDisplayMetrics().density;
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ float iconDensity = bitmap.getDensityScale();
+ scale = displayDensity / iconDensity;
+
+ // Scale the bitmap to the screen density size if it's not loaded at the same density.
+ if (scale != 1.0f) {
+ icon = bitmapDrawable = new BitmapDrawable(bitmap);
+ bitmapDrawable.setDensityScale(scale);
+ }
}
+ int iconWidth = icon.getIntrinsicWidth();
+ int iconHeight = icon.getIntrinsicHeight();
if (width > 0 && height > 0) {
- if (width < iconWidth || height < iconHeight) {
+ if (width < iconWidth || height < iconHeight || scale != 1.0f) {
final float ratio = (float) iconWidth / iconHeight;
if (iconWidth > iconHeight) {
diff --git a/src/com/android/launcher/WallpaperChooser.java b/src/com/android/launcher/WallpaperChooser.java
index 9ac922c..c88a02a 100644
--- a/src/com/android/launcher/WallpaperChooser.java
+++ b/src/com/android/launcher/WallpaperChooser.java
@@ -60,7 +60,7 @@
};
private static final Integer[] IMAGE_IDS = {
- com.android.internal.R.drawable.default_wallpaper,
+ R.drawable.wallpaper_lake,
R.drawable.wallpaper_sunset,
R.drawable.wallpaper_beach,
R.drawable.wallpaper_snow_leopard,
diff --git a/src/com/android/launcher/Workspace.java b/src/com/android/launcher/Workspace.java
index 359767a..f5dc633 100644
--- a/src/com/android/launcher/Workspace.java
+++ b/src/com/android/launcher/Workspace.java
@@ -48,7 +48,7 @@
*/
public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
private static final int INVALID_SCREEN = -1;
-
+
/**
* The velocity at which a fling gesture will cause us to snap to the next screen
*/
@@ -75,6 +75,11 @@
* CellInfo for the cell that is currently being dragged
*/
private CellLayout.CellInfo mDragInfo;
+
+ /**
+ * Target drop area calculated during last acceptDrop call.
+ */
+ private int[] mTargetCell = null;
private float mLastMotionX;
private float mLastMotionY;
@@ -88,13 +93,20 @@
private Launcher mLauncher;
private DragController mDragger;
-
+
+ /**
+ * Cache of vacant cells, used during drag events and invalidated as needed.
+ */
+ private CellLayout.CellInfo mVacantCache = null;
+
private int[] mTempCell = new int[2];
+ private int[] mTempEstimate = new int[2];
private boolean mAllowLongPress;
private boolean mLocked;
private int mTouchSlop;
+ private int mMaximumVelocity;
final Rect mDrawerBounds = new Rect();
final Rect mClipBounds = new Rect();
@@ -139,7 +151,9 @@
mPaint = new Paint();
mPaint.setDither(false);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
/**
@@ -261,6 +275,7 @@
* @param currentScreen
*/
void setCurrentScreen(int currentScreen) {
+ clearVacantCache();
mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
scrollTo(mCurrentScreen * getWidth(), 0);
invalidate();
@@ -334,6 +349,8 @@
throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
}
+ clearVacantCache();
+
final CellLayout group = (CellLayout) getChildAt(screen);
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (lp == null) {
@@ -363,11 +380,18 @@
CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
if (group != null) {
- return group.findAllVacantCells(occupied);
+ return group.findAllVacantCells(occupied, null);
}
return null;
}
+ private void clearVacantCache() {
+ if (mVacantCache != null) {
+ mVacantCache.clearVacantCells();
+ mVacantCache = null;
+ }
+ }
+
/**
* Returns the coordinate of a vacant cell for the current screen.
*/
@@ -446,6 +470,11 @@
}
@Override
+ public boolean isOpaque() {
+ return !mWallpaper.hasAlpha();
+ }
+
+ @Override
protected void dispatchDraw(Canvas canvas) {
boolean restore = false;
@@ -609,7 +638,7 @@
}
@Override
- public void addFocusables(ArrayList<View> views, int direction) {
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
if (mLauncher.isDrawerDown()) {
final Folder openFolder = getOpenFolder();
if (openFolder == null) {
@@ -787,7 +816,7 @@
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000);
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityX = (int) velocityTracker.getXVelocity();
if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
@@ -822,6 +851,9 @@
}
void snapToScreen(int whichScreen) {
+ if (!mScroller.isFinished()) return;
+
+ clearVacantCache();
enableChildrenCache();
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
@@ -890,7 +922,7 @@
}
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
- final CellLayout cellLayout = (CellLayout) getChildAt(mCurrentScreen);
+ final CellLayout cellLayout = getCurrentDropLayout();
if (source != this) {
onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
} else {
@@ -902,7 +934,9 @@
originalCellLayout.removeView(cell);
cellLayout.addView(cell);
}
- cellLayout.onDropChild(cell, x - xOffset, y - yOffset);
+ mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
+ mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
+ cellLayout.onDropChild(cell, mTargetCell);
final ItemInfo info = (ItemInfo)cell.getTag();
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
@@ -914,6 +948,7 @@
public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
Object dragInfo) {
+ clearVacantCache();
}
public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
@@ -922,6 +957,7 @@
public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
Object dragInfo) {
+ clearVacantCache();
}
private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
@@ -955,7 +991,8 @@
cellLayout.addView(view, insertAtFirst ? 0 : -1);
view.setOnLongClickListener(mLongClickListener);
- cellLayout.onDropChild(view, x, y);
+ mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
+ cellLayout.onDropChild(view, mTargetCell);
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
final LauncherModel model = Launcher.getModel();
@@ -963,18 +1000,82 @@
LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
}
-
- public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
- Object dragInfo) {
-
- final CellLayout.CellInfo cellInfo = mDragInfo;
- int cellHSpan = cellInfo == null ? 1 : cellInfo.spanX;
- int cellVSpan = cellInfo == null ? 1 : cellInfo.spanY;
-
- return ((CellLayout) getChildAt(mCurrentScreen)).acceptChildDrop(x - xOffset, y - yOffset,
- cellHSpan, cellVSpan, cellInfo == null ? null : cellInfo.cell);
+
+ /**
+ * Return the current {@link CellLayout}, correctly picking the destination
+ * screen while a scroll is in progress.
+ */
+ private CellLayout getCurrentDropLayout() {
+ int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
+ return (CellLayout) getChildAt(index);
}
+ /**
+ * {@inheritDoc}
+ */
+ public boolean acceptDrop(DragSource source, int x, int y,
+ int xOffset, int yOffset, Object dragInfo) {
+ final CellLayout layout = getCurrentDropLayout();
+ final CellLayout.CellInfo cellInfo = mDragInfo;
+ final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
+ final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
+
+ if (mVacantCache == null) {
+ final View ignoreView = cellInfo == null ? null : cellInfo.cell;
+ mVacantCache = layout.findAllVacantCells(null, ignoreView);
+ }
+
+ return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Rect estimateDropLocation(DragSource source, int x, int y,
+ int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+ final CellLayout layout = getCurrentDropLayout();
+
+ final CellLayout.CellInfo cellInfo = mDragInfo;
+ final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
+ final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
+ final View ignoreView = cellInfo == null ? null : cellInfo.cell;
+
+ final Rect location = recycle != null ? recycle : new Rect();
+
+ // Find drop cell and convert into rectangle
+ int[] dropCell = estimateDropCell(x - xOffset, y - yOffset,
+ spanX, spanY, ignoreView, layout, mTempCell);
+
+ if (dropCell == null) {
+ return null;
+ }
+
+ layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
+ location.left = mTempEstimate[0];
+ location.top = mTempEstimate[1];
+
+ layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
+ location.right = mTempEstimate[0];
+ location.bottom = mTempEstimate[1];
+
+ return location;
+ }
+
+ /**
+ * Calculate the nearest cell where the given object would be dropped.
+ */
+ private int[] estimateDropCell(int pixelX, int pixelY,
+ int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
+ // Create vacant cell cache if none exists
+ if (mVacantCache == null) {
+ mVacantCache = layout.findAllVacantCells(null, ignoreView);
+ }
+
+ // Find the best target drop location
+ return layout.findNearestVacantArea(pixelX, pixelY,
+ spanX, spanY, mVacantCache, recycle);
+ }
+
void setLauncher(Launcher launcher) {
mLauncher = launcher;
}
@@ -1002,12 +1103,14 @@
}
public void scrollLeft() {
+ clearVacantCache();
if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
snapToScreen(mCurrentScreen - 1);
}
}
public void scrollRight() {
+ clearVacantCache();
if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
mScroller.isFinished()) {
snapToScreen(mCurrentScreen + 1);
@@ -1027,7 +1130,7 @@
}
return result;
}
-
+
/**
* Find a search widget on the given screen
*/
@@ -1041,102 +1144,14 @@
}
return null;
}
-
+
/**
- * Focuses on the search widget on the specified screen,
- * if there is one. Also clears the current search selection so we don't
+ * Gets the first search widget on the current screen, if there is one.
+ * Returns <code>null</code> otherwise.
*/
- private boolean focusOnSearch(int screen) {
- CellLayout currentScreen = (CellLayout) getChildAt(screen);
- final Search searchWidget = findSearchWidget(currentScreen);
- if (searchWidget != null) {
- // This is necessary when focus on search is requested from the menu
- // If the workspace was not in touch mode before the menu is invoked
- // and the user clicks "Search" by touching the menu item, the following
- // happens:
- //
- // - We request focus from touch on the search widget
- // - The search widget gains focus
- // - The window focus comes back to Home's window
- // - The touch mode change is propagated to Home's window
- // - The search widget is not focusable in touch mode and ViewRoot
- // clears its focus
- //
- // Forcing focusable in touch mode ensures the search widget will
- // keep the focus no matter what happens.
- //
- // Note: the search input field disables focusable in touch mode
- // after the window gets the focus back, see SearchAutoCompleteTextView
- final SearchAutoCompleteTextView input = searchWidget.getSearchInputField();
- input.setFocusableInTouchMode(true);
- input.showKeyboardOnNextFocus();
-
- if (isInTouchMode()) {
- searchWidget.requestFocusFromTouch();
- } else {
- searchWidget.requestFocus();
- }
- searchWidget.clearQuery();
- return true;
- }
- return false;
- }
-
- /**
- * Snap to the nearest screen with a search widget and give it focus
- *
- * @return True if a search widget was found
- */
- public boolean snapToSearch() {
- // The screen we are searching
- int current = mCurrentScreen;
-
- // first position scanned so far
- int first = current;
-
- // last position scanned so far
- int last = current;
-
- // True if we should move down on the next iteration
- boolean next = false;
-
- // True when we have looked at the first item in the data
- boolean hitFirst;
-
- // True when we have looked at the last item in the data
- boolean hitLast;
-
- final int count = getChildCount();
-
- while (true) {
- if (focusOnSearch(current)) {
- return true;
- }
-
- hitLast = last == count - 1;
- hitFirst = first == 0;
-
- if (hitLast && hitFirst) {
- // Looked at everything
- break;
- }
-
- if (hitFirst || (next && !hitLast)) {
- // Either we hit the top, or we are trying to move down
- last++;
- current = last;
- // Try going up next time
- next = false;
- } else {
- // Either we hit the bottom, or we are trying to move up
- first--;
- current = first;
- // Try going down next time
- next = true;
- }
-
- }
- return false;
+ public Search findSearchWidgetOnCurrentScreen() {
+ CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
+ return findSearchWidget(currentScreen);
}
public Folder getFolderForTag(Object tag) {
@@ -1210,32 +1225,64 @@
final ArrayList<View> childrenToRemove = new ArrayList<View>();
final LauncherModel model = Launcher.getModel();
final int count = getChildCount();
+
for (int i = 0; i < count; i++) {
final CellLayout layout = (CellLayout) getChildAt(i);
int childCount = layout.getChildCount();
+
childrenToRemove.clear();
+
for (int j = 0; j < childCount; j++) {
final View view = layout.getChildAt(j);
Object tag = view.getTag();
+
if (tag instanceof ApplicationInfo) {
- ApplicationInfo info = (ApplicationInfo) tag;
+ final ApplicationInfo info = (ApplicationInfo) tag;
// We need to check for ACTION_MAIN otherwise getComponent() might
// return null for some shortcuts (for instance, for shortcuts to
// web pages.)
final Intent intent = info.intent;
final ComponentName name = intent.getComponent();
+
if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
name != null && packageName.equals(name.getPackageName())) {
model.removeDesktopItem(info);
LauncherModel.deleteItemFromDatabase(mLauncher, info);
childrenToRemove.add(view);
}
+ } else if (tag instanceof UserFolderInfo) {
+ final UserFolderInfo info = (UserFolderInfo) tag;
+ final ArrayList<ApplicationInfo> contents = info.contents;
+ final ArrayList<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>(1);
+ final int contentsCount = contents.size();
+ boolean removedFromFolder = false;
+
+ for (int k = 0; k < contentsCount; k++) {
+ final ApplicationInfo appInfo = contents.get(k);
+ final Intent intent = appInfo.intent;
+ final ComponentName name = intent.getComponent();
+
+ if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
+ name != null && packageName.equals(name.getPackageName())) {
+ toRemove.add(appInfo);
+ LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
+ removedFromFolder = true;
+ }
+ }
+
+ contents.removeAll(toRemove);
+ if (removedFromFolder) {
+ final Folder folder = getOpenFolder();
+ if (folder != null) folder.notifyDataSetChanged();
+ }
}
}
+
childCount = childrenToRemove.size();
for (int j = 0; j < childCount; j++) {
layout.removeViewInLayout(childrenToRemove.get(j));
}
+
if (childCount > 0) {
layout.requestLayout();
layout.invalidate();
@@ -1277,10 +1324,6 @@
}
}
- // TODO: remove widgets when appwidgetmanager tells us they're gone
-// void removeAppWidgetsForProvider() {
-// }
-
void moveToDefaultScreen() {
snapToScreen(mDefaultScreen);
getChildAt(mDefaultScreen).requestFocus();