diff options
173 files changed, 12041 insertions, 2863 deletions
@@ -14,6 +14,8 @@ # limitations under the License. # +ifneq ($(TARGET_SIMULATOR),true) + LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) @@ -21,7 +23,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_STATIC_JAVA_LIBRARIES := android-common -LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_SRC_FILES := $(call all-subdir-java-files) $(call all-renderscript-files-under, src) LOCAL_PACKAGE_NAME := Launcher2 LOCAL_CERTIFICATE := shared @@ -31,3 +33,5 @@ LOCAL_OVERRIDES_PACKAGES := Home LOCAL_PROGUARD_FLAG_FILES := proguard.flags include $(BUILD_PACKAGE) + +endif diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 84ee599..4aa5d68 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -66,7 +66,8 @@ android:name="com.android.launcher2.LauncherApplication" android:process="@string/process" android:label="@string/application_name" - android:icon="@drawable/ic_launcher_home"> + android:icon="@drawable/ic_launcher_home" + android:hardwareAccelerated="@bool/config_hardwareAccelerated"> <activity android:name="com.android.launcher2.Launcher" @@ -74,7 +75,6 @@ android:clearTaskOnLaunch="true" android:stateNotNeeded="true" android:theme="@style/Theme" - android:screenOrientation="nosensor" android:windowSoftInputMode="stateUnspecified|adjustPan"> <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/CleanSpec.mk b/CleanSpec.mk index b84e1b6..444dbd8 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -44,6 +44,8 @@ #$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates) + # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST -# ************************************************ +# ************************************************
\ No newline at end of file diff --git a/proguard.flags b/proguard.flags index 1187fd8..010a1db 100644 --- a/proguard.flags +++ b/proguard.flags @@ -2,6 +2,36 @@ public void previousScreen(android.view.View); public void nextScreen(android.view.View); public void launchHotSeat(android.view.View); + public void onClickSearchButton(android.view.View); + public void onClickConfigureButton(android.view.View); + public void onClickAllAppsButton(android.view.View); + public void onClickAppMarketButton(android.view.View); +} + +-keep class com.android.launcher2.CellLayout { + public float getBackgroundAlpha(); + public void setBackgroundAlpha(float); + public float getDimmableProgress(); + public void setDimmableProgress(float); + public float getHoverScale(); + public void setHoverScale(float); + public float getHoverAlpha(); + public void setHoverAlpha(float); +} + +-keep class com.android.launcher2.DimmableBubbleTextView { + public float getDimmableProgress(); + public void setDimmableProgress(float); +} + +-keep class com.android.launcher2.DimmableAppWidgetHostView { + public float getDimmableProgress(); + public void setDimmableProgress(float); +} + +-keep class com.android.launcher2.Workspace { + public float getBackgroundAlpha(); + public void setBackgroundAlpha(float); } -keep class com.android.launcher2.AllApps3D$Defines { @@ -10,4 +40,4 @@ -keep class com.android.launcher2.ClippedImageView { *; -} +}
\ No newline at end of file diff --git a/res/anim/all_apps_zoom_in.xml b/res/anim/all_apps_zoom_in.xml new file mode 100644 index 0000000..644d1cf --- /dev/null +++ b/res/anim/all_apps_zoom_in.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/decelerate_interpolator" + android:shareInterpolator="true"> + <translate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromXDelta="0%p" + android:toXDelta="0%p" + android:fromYDelta="-20%p" + android:toYDelta="0%p" + android:duration="500" /> + <scale + android:fromXScale="5.0" + android:toXScale="1.0" + android:fromYScale="5.0" + android:toYScale="1.0" + android:pivotX="50%" + android:pivotY="100%" + android:duration="500" /> +</set> diff --git a/res/anim/all_apps_zoom_out.xml b/res/anim/all_apps_zoom_out.xml new file mode 100644 index 0000000..23a8712 --- /dev/null +++ b/res/anim/all_apps_zoom_out.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/accelerate_interpolator" + android:shareInterpolator="true"> + <translate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromXDelta="0%p" + android:toXDelta="0%p" + android:fromYDelta="0%p" + android:toYDelta="-20%p" + android:duration="500" /> + <scale + android:fromXScale="1.0" + android:toXScale="5.0" + android:fromYScale="1.0" + android:toYScale="5.0" + android:pivotX="50%" + android:pivotY="100%" + android:duration="500" /> +</set> diff --git a/res/anim/home_customization_drawer_slide_down.xml b/res/anim/home_customization_drawer_slide_down.xml new file mode 100644 index 0000000..b3041c7 --- /dev/null +++ b/res/anim/home_customization_drawer_slide_down.xml @@ -0,0 +1,22 @@ +<?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. +--> +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromXDelta="0%p" + android:toXDelta="0%p" + android:fromYDelta="0%p" + android:toYDelta="100%p" + + android:duration="500" /> diff --git a/res/anim/home_customization_drawer_slide_up.xml b/res/anim/home_customization_drawer_slide_up.xml new file mode 100644 index 0000000..3df2320 --- /dev/null +++ b/res/anim/home_customization_drawer_slide_up.xml @@ -0,0 +1,22 @@ +<?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. +--> +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromXDelta="0%p" + android:toXDelta="0%p" + android:fromYDelta="100%p" + android:toYDelta="0%p" + + android:duration="500" /> diff --git a/res/anim/paged_view_click_feedback.xml b/res/anim/paged_view_click_feedback.xml new file mode 100644 index 0000000..786d974 --- /dev/null +++ b/res/anim/paged_view_click_feedback.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:fromAlpha="1.0" + android:toAlpha="0.65" + android:duration="100" + android:fillAfter="true" + android:repeatCount="1" + android:repeatMode="reverse" /> diff --git a/res/drawable-hdpi/add_button.png b/res/drawable-hdpi/add_button.png Binary files differnew file mode 100644 index 0000000..7e60d81 --- /dev/null +++ b/res/drawable-hdpi/add_button.png diff --git a/res/drawable-hdpi/default_widget_preview.9.png b/res/drawable-hdpi/default_widget_preview.9.png Binary files differnew file mode 100644 index 0000000..6596f07 --- /dev/null +++ b/res/drawable-hdpi/default_widget_preview.9.png diff --git a/res/drawable-hdpi/hotseat_browser_focused.png b/res/drawable-hdpi/hotseat_browser_focused.png Binary files differindex 25969ca..4ab51dd 100644 --- a/res/drawable-hdpi/hotseat_browser_focused.png +++ b/res/drawable-hdpi/hotseat_browser_focused.png diff --git a/res/drawable-hdpi/hotseat_browser_normal.png b/res/drawable-hdpi/hotseat_browser_normal.png Binary files differindex 0dbdbe7..77ae927 100644 --- a/res/drawable-hdpi/hotseat_browser_normal.png +++ b/res/drawable-hdpi/hotseat_browser_normal.png diff --git a/res/drawable-hdpi/hotseat_browser_pressed.png b/res/drawable-hdpi/hotseat_browser_pressed.png Binary files differindex 3b872bf..cfe963b 100644 --- a/res/drawable-hdpi/hotseat_browser_pressed.png +++ b/res/drawable-hdpi/hotseat_browser_pressed.png diff --git a/res/drawable-hdpi/hotseat_phone_focused.png b/res/drawable-hdpi/hotseat_phone_focused.png Binary files differindex 3172d7f..f81f0a8 100644 --- a/res/drawable-hdpi/hotseat_phone_focused.png +++ b/res/drawable-hdpi/hotseat_phone_focused.png diff --git a/res/drawable-hdpi/hotseat_phone_normal.png b/res/drawable-hdpi/hotseat_phone_normal.png Binary files differindex 61ef7e6..391802e 100644 --- a/res/drawable-hdpi/hotseat_phone_normal.png +++ b/res/drawable-hdpi/hotseat_phone_normal.png diff --git a/res/drawable-hdpi/hotseat_phone_pressed.png b/res/drawable-hdpi/hotseat_phone_pressed.png Binary files differindex 7bf8f48..a6c2baf 100644 --- a/res/drawable-hdpi/hotseat_phone_pressed.png +++ b/res/drawable-hdpi/hotseat_phone_pressed.png diff --git a/res/drawable-hdpi/pressed_application_background.9.png b/res/drawable-hdpi/pressed_application_background.9.png Binary files differnew file mode 100644 index 0000000..a6cbe94 --- /dev/null +++ b/res/drawable-hdpi/pressed_application_background.9.png diff --git a/res/drawable-hdpi/rounded_rect_green.9.png b/res/drawable-hdpi/rounded_rect_green.9.png Binary files differnew file mode 100644 index 0000000..a846cbd --- /dev/null +++ b/res/drawable-hdpi/rounded_rect_green.9.png diff --git a/res/drawable-hdpi/rounded_rect_red.9.png b/res/drawable-hdpi/rounded_rect_red.9.png Binary files differnew file mode 100644 index 0000000..21fd62c --- /dev/null +++ b/res/drawable-hdpi/rounded_rect_red.9.png diff --git a/res/drawable-mdpi/hotseat_browser_focused.png b/res/drawable-mdpi/hotseat_browser_focused.png Binary files differindex 6d9d886..5b85840 100644 --- a/res/drawable-mdpi/hotseat_browser_focused.png +++ b/res/drawable-mdpi/hotseat_browser_focused.png diff --git a/res/drawable-mdpi/hotseat_browser_normal.png b/res/drawable-mdpi/hotseat_browser_normal.png Binary files differindex 0916b2c..4a4a6e3 100644 --- a/res/drawable-mdpi/hotseat_browser_normal.png +++ b/res/drawable-mdpi/hotseat_browser_normal.png diff --git a/res/drawable-mdpi/hotseat_browser_pressed.png b/res/drawable-mdpi/hotseat_browser_pressed.png Binary files differindex 5c6a137..ed10c18 100644 --- a/res/drawable-mdpi/hotseat_browser_pressed.png +++ b/res/drawable-mdpi/hotseat_browser_pressed.png diff --git a/res/drawable-mdpi/hotseat_phone_focused.png b/res/drawable-mdpi/hotseat_phone_focused.png Binary files differindex d55b277..15ec1c6 100644 --- a/res/drawable-mdpi/hotseat_phone_focused.png +++ b/res/drawable-mdpi/hotseat_phone_focused.png diff --git a/res/drawable-mdpi/hotseat_phone_normal.png b/res/drawable-mdpi/hotseat_phone_normal.png Binary files differindex 17ecda5..7f20428 100644 --- a/res/drawable-mdpi/hotseat_phone_normal.png +++ b/res/drawable-mdpi/hotseat_phone_normal.png diff --git a/res/drawable-mdpi/hotseat_phone_pressed.png b/res/drawable-mdpi/hotseat_phone_pressed.png Binary files differindex cc18ee3..fcdf073 100644 --- a/res/drawable-mdpi/hotseat_phone_pressed.png +++ b/res/drawable-mdpi/hotseat_phone_pressed.png diff --git a/res/drawable-mdpi/tab_unselected_holo.9.png b/res/drawable-mdpi/tab_unselected_holo.9.png Binary files differnew file mode 100644 index 0000000..848f3f1 --- /dev/null +++ b/res/drawable-mdpi/tab_unselected_holo.9.png diff --git a/res/drawable-mdpi/trashcan.png b/res/drawable-mdpi/trashcan.png Binary files differindex 4355164..4f01d76 100644 --- a/res/drawable-mdpi/trashcan.png +++ b/res/drawable-mdpi/trashcan.png diff --git a/res/drawable-mdpi/trashcan_hover.png b/res/drawable-mdpi/trashcan_hover.png Binary files differindex 96f4e51..fb7474a 100644 --- a/res/drawable-mdpi/trashcan_hover.png +++ b/res/drawable-mdpi/trashcan_hover.png diff --git a/res/drawable-xlarge-mdpi/all_apps_button_focused.png b/res/drawable-xlarge-mdpi/all_apps_button_focused.png Binary files differnew file mode 100644 index 0000000..a9d5a3f --- /dev/null +++ b/res/drawable-xlarge-mdpi/all_apps_button_focused.png diff --git a/res/drawable-xlarge-mdpi/all_apps_button_normal.9.png b/res/drawable-xlarge-mdpi/all_apps_button_normal.9.png Binary files differnew file mode 100644 index 0000000..a423145 --- /dev/null +++ b/res/drawable-xlarge-mdpi/all_apps_button_normal.9.png diff --git a/res/drawable-xlarge-mdpi/all_apps_button_pressed.9.png b/res/drawable-xlarge-mdpi/all_apps_button_pressed.9.png Binary files differnew file mode 100644 index 0000000..5dbe77c --- /dev/null +++ b/res/drawable-xlarge-mdpi/all_apps_button_pressed.9.png diff --git a/res/drawable-xlarge-mdpi/app_market_generic.png b/res/drawable-xlarge-mdpi/app_market_generic.png Binary files differnew file mode 100644 index 0000000..0ceaeef --- /dev/null +++ b/res/drawable-xlarge-mdpi/app_market_generic.png diff --git a/res/drawable-xlarge-mdpi/configure_button_normal.png b/res/drawable-xlarge-mdpi/configure_button_normal.png Binary files differnew file mode 100644 index 0000000..9b37eff --- /dev/null +++ b/res/drawable-xlarge-mdpi/configure_button_normal.png diff --git a/res/drawable-xlarge-mdpi/configure_button_pressed.png b/res/drawable-xlarge-mdpi/configure_button_pressed.png Binary files differnew file mode 100644 index 0000000..5f38db6 --- /dev/null +++ b/res/drawable-xlarge-mdpi/configure_button_pressed.png diff --git a/res/drawable-xlarge-mdpi/glow_wallpaper.png b/res/drawable-xlarge-mdpi/glow_wallpaper.png Binary files differnew file mode 100644 index 0000000..eb29449 --- /dev/null +++ b/res/drawable-xlarge-mdpi/glow_wallpaper.png diff --git a/res/drawable-xlarge-mdpi/glow_wallpaper_small.png b/res/drawable-xlarge-mdpi/glow_wallpaper_small.png Binary files differnew file mode 100644 index 0000000..f83fc60 --- /dev/null +++ b/res/drawable-xlarge-mdpi/glow_wallpaper_small.png diff --git a/res/drawable-xlarge-mdpi/home_screen_bg.9.png b/res/drawable-xlarge-mdpi/home_screen_bg.9.png Binary files differnew file mode 100644 index 0000000..413e492 --- /dev/null +++ b/res/drawable-xlarge-mdpi/home_screen_bg.9.png diff --git a/res/drawable-xlarge-mdpi/home_screen_bg_hover.9.png b/res/drawable-xlarge-mdpi/home_screen_bg_hover.9.png Binary files differnew file mode 100644 index 0000000..73696bf --- /dev/null +++ b/res/drawable-xlarge-mdpi/home_screen_bg_hover.9.png diff --git a/res/drawable-xlarge-mdpi/ic_no_applications.png b/res/drawable-xlarge-mdpi/ic_no_applications.png Binary files differnew file mode 100644 index 0000000..a4f695a --- /dev/null +++ b/res/drawable-xlarge-mdpi/ic_no_applications.png diff --git a/res/drawable-xlarge-mdpi/info_button.png b/res/drawable-xlarge-mdpi/info_button.png Binary files differnew file mode 100644 index 0000000..253cdf7 --- /dev/null +++ b/res/drawable-xlarge-mdpi/info_button.png diff --git a/res/drawable-xlarge-mdpi/mini_home_screen_bg.9.png b/res/drawable-xlarge-mdpi/mini_home_screen_bg.9.png Binary files differnew file mode 100644 index 0000000..4602609 --- /dev/null +++ b/res/drawable-xlarge-mdpi/mini_home_screen_bg.9.png diff --git a/res/drawable-xlarge-mdpi/mini_home_screen_bg_accepts_drops.9.png b/res/drawable-xlarge-mdpi/mini_home_screen_bg_accepts_drops.9.png Binary files differnew file mode 100644 index 0000000..b1aa98b --- /dev/null +++ b/res/drawable-xlarge-mdpi/mini_home_screen_bg_accepts_drops.9.png diff --git a/res/drawable-xlarge-mdpi/mini_home_screen_bg_hover.9.png b/res/drawable-xlarge-mdpi/mini_home_screen_bg_hover.9.png Binary files differnew file mode 100644 index 0000000..a30e992 --- /dev/null +++ b/res/drawable-xlarge-mdpi/mini_home_screen_bg_hover.9.png diff --git a/res/drawable-xlarge-mdpi/rotate_button_normal.png b/res/drawable-xlarge-mdpi/rotate_button_normal.png Binary files differnew file mode 100644 index 0000000..fd014aa --- /dev/null +++ b/res/drawable-xlarge-mdpi/rotate_button_normal.png diff --git a/res/drawable-xlarge-mdpi/rotate_button_pressed.png b/res/drawable-xlarge-mdpi/rotate_button_pressed.png Binary files differnew file mode 100644 index 0000000..29fb8d9 --- /dev/null +++ b/res/drawable-xlarge-mdpi/rotate_button_pressed.png diff --git a/res/drawable-xlarge-mdpi/search_button_normal.png b/res/drawable-xlarge-mdpi/search_button_normal.png Binary files differnew file mode 100644 index 0000000..b172074 --- /dev/null +++ b/res/drawable-xlarge-mdpi/search_button_normal.png diff --git a/res/drawable-xlarge-mdpi/search_button_pressed.png b/res/drawable-xlarge-mdpi/search_button_pressed.png Binary files differnew file mode 100644 index 0000000..c2926c8 --- /dev/null +++ b/res/drawable-xlarge-mdpi/search_button_pressed.png diff --git a/res/drawable-xlarge-mdpi/trashcan.png b/res/drawable-xlarge-mdpi/trashcan.png Binary files differnew file mode 100644 index 0000000..839d4b8 --- /dev/null +++ b/res/drawable-xlarge-mdpi/trashcan.png diff --git a/res/drawable-xlarge-mdpi/trashcan_hover.png b/res/drawable-xlarge-mdpi/trashcan_hover.png Binary files differnew file mode 100644 index 0000000..cc2fde7 --- /dev/null +++ b/res/drawable-xlarge-mdpi/trashcan_hover.png diff --git a/res/drawable-xlarge/all_apps_bg_gradient.9.png b/res/drawable-xlarge/all_apps_bg_gradient.9.png Binary files differnew file mode 100644 index 0000000..8d88a7e --- /dev/null +++ b/res/drawable-xlarge/all_apps_bg_gradient.9.png diff --git a/res/drawable-xlarge/all_apps_button.xml b/res/drawable-xlarge/all_apps_button.xml new file mode 100644 index 0000000..46bc632 --- /dev/null +++ b/res/drawable-xlarge/all_apps_button.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:drawable="@drawable/all_apps_button_pressed" /> + <item android:drawable="@drawable/all_apps_button_normal" /> +</selector> diff --git a/res/drawable-xlarge/configure_button.xml b/res/drawable-xlarge/configure_button.xml new file mode 100644 index 0000000..ac87290 --- /dev/null +++ b/res/drawable-xlarge/configure_button.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:drawable="@drawable/configure_button_pressed" /> + <item android:drawable="@drawable/configure_button_normal" /> +</selector> diff --git a/res/drawable-xlarge/customize_bg_gradient.png b/res/drawable-xlarge/customize_bg_gradient.png Binary files differnew file mode 100644 index 0000000..fb3ec90 --- /dev/null +++ b/res/drawable-xlarge/customize_bg_gradient.png diff --git a/res/drawable-xlarge/rotate_button.xml b/res/drawable-xlarge/rotate_button.xml new file mode 100644 index 0000000..c29efa4 --- /dev/null +++ b/res/drawable-xlarge/rotate_button.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:drawable="@drawable/rotate_button_pressed" /> + <item android:drawable="@drawable/rotate_button_normal" /> +</selector> diff --git a/res/drawable-xlarge/search_button_generic.xml b/res/drawable-xlarge/search_button_generic.xml new file mode 100644 index 0000000..8f18e67 --- /dev/null +++ b/res/drawable-xlarge/search_button_generic.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:drawable="@drawable/search_button_pressed" /> + <item android:drawable="@drawable/search_button_normal" /> +</selector> diff --git a/res/drawable/default_widget_preview.9.png b/res/drawable/default_widget_preview.9.png Binary files differnew file mode 100644 index 0000000..5fedcc8 --- /dev/null +++ b/res/drawable/default_widget_preview.9.png diff --git a/res/drawable/gardening_crosshairs.png b/res/drawable/gardening_crosshairs.png Binary files differnew file mode 100644 index 0000000..cbaee78 --- /dev/null +++ b/res/drawable/gardening_crosshairs.png diff --git a/res/drawable/page_hover_left.9.png b/res/drawable/page_hover_left.9.png Binary files differnew file mode 100644 index 0000000..5d5e0c8 --- /dev/null +++ b/res/drawable/page_hover_left.9.png diff --git a/res/drawable/page_hover_right.9.png b/res/drawable/page_hover_right.9.png Binary files differnew file mode 100644 index 0000000..1545bfe --- /dev/null +++ b/res/drawable/page_hover_right.9.png diff --git a/res/drawable/rounded_rect_green.9.png b/res/drawable/rounded_rect_green.9.png Binary files differnew file mode 100644 index 0000000..5787c3d --- /dev/null +++ b/res/drawable/rounded_rect_green.9.png diff --git a/res/drawable/rounded_rect_red.9.png b/res/drawable/rounded_rect_red.9.png Binary files differnew file mode 100644 index 0000000..7a5fdee --- /dev/null +++ b/res/drawable/rounded_rect_red.9.png diff --git a/res/drawable/widget_divider.png b/res/drawable/widget_divider.png Binary files differnew file mode 100644 index 0000000..1b453c5 --- /dev/null +++ b/res/drawable/widget_divider.png diff --git a/res/layout-land/all_apps_2d.xml b/res/layout-land/all_apps_2d.xml index a253b93..b7fcd45 100644 --- a/res/layout-land/all_apps_2d.xml +++ b/res/layout-land/all_apps_2d.xml @@ -22,7 +22,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:padding="2dip" - > + android:background="#FF000000"> <GridView android:id="@+id/all_apps_2d_grid" android:tag="all_apps_2d_grid" diff --git a/res/layout-land/application.xml b/res/layout-land/application.xml index 6de5658..6e8c31e 100644 --- a/res/layout-land/application.xml +++ b/res/layout-land/application.xml @@ -15,4 +15,4 @@ --> <com.android.launcher2.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android" - style="@style/WorkspaceIcon.Landscape" /> + style="@style/WorkspaceIcon.Landscape" /> diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml index 1f13f1f..8d38a3d 100644 --- a/res/layout-land/launcher.xml +++ b/res/layout-land/launcher.xml @@ -24,14 +24,16 @@ <include layout="@layout/all_apps" /> - <!-- The workspace contains 3 screens of cells --> + <!-- The workspace contains 5 screens of cells --> <com.android.launcher2.Workspace android:id="@+id/workspace" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="horizontal" android:fadeScrollbars="true" - launcher:defaultScreen="2"> + launcher:defaultScreen="2" + launcher:cellCountX="4" + launcher:cellCountY="4"> <include android:id="@+id/cell1" layout="@layout/workspace_screen" /> <include android:id="@+id/cell2" layout="@layout/workspace_screen" /> diff --git a/res/layout-land/workspace_screen.xml b/res/layout-land/workspace_screen.xml index 315e68b..9323f58 100644 --- a/res/layout-land/workspace_screen.xml +++ b/res/layout-land/workspace_screen.xml @@ -24,9 +24,7 @@ launcher:cellWidth="@dimen/workspace_cell_width" launcher:cellHeight="@dimen/workspace_cell_height" - launcher:longAxisStartPadding="65dip" - launcher:longAxisEndPadding="65dip" - launcher:shortAxisStartPadding="0dip" - launcher:shortAxisEndPadding="0dip" - launcher:shortAxisCells="4" - launcher:longAxisCells="4" /> + launcher:xAxisStartPadding="65dip" + launcher:xAxisEndPadding="65dip" + launcher:yAxisStartPadding="0dip" + launcher:yAxisEndPadding="0dip"/> diff --git a/res/layout-port/all_apps_2d.xml b/res/layout-port/all_apps_2d.xml index 0607d62..081cba2 100644 --- a/res/layout-port/all_apps_2d.xml +++ b/res/layout-port/all_apps_2d.xml @@ -22,7 +22,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:padding="2dip" - > + android:background="#FF000000"> <GridView android:id="@+id/all_apps_2d_grid" android:tag="all_apps_2d_grid" diff --git a/res/layout-port/application.xml b/res/layout-port/application.xml index f904a66..32c1510 100644 --- a/res/layout-port/application.xml +++ b/res/layout-port/application.xml @@ -4,9 +4,9 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,4 +15,4 @@ --> <com.android.launcher2.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android" - style="@style/WorkspaceIcon.Portrait" /> + style="@style/WorkspaceIcon.Portrait" /> diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml index 8dc5092..c50dbca 100644 --- a/res/layout-port/launcher.xml +++ b/res/layout-port/launcher.xml @@ -24,12 +24,14 @@ <include layout="@layout/all_apps" /> - <!-- The workspace contains 3 screens of cells --> + <!-- The workspace contains 5 screens of cells --> <com.android.launcher2.Workspace android:id="@+id/workspace" android:layout_width="match_parent" android:layout_height="match_parent" - launcher:defaultScreen="2"> + launcher:defaultScreen="2" + launcher:cellCountX="4" + launcher:cellCountY="4"> <include android:id="@+id/cell1" layout="@layout/workspace_screen" /> <include android:id="@+id/cell2" layout="@layout/workspace_screen" /> diff --git a/res/layout-port/workspace_screen.xml b/res/layout-port/workspace_screen.xml index bd4a4bd..f400c40 100644 --- a/res/layout-port/workspace_screen.xml +++ b/res/layout-port/workspace_screen.xml @@ -24,9 +24,7 @@ launcher:cellWidth="@dimen/workspace_cell_width" launcher:cellHeight="@dimen/workspace_cell_height" - launcher:longAxisStartPadding="8dip" - launcher:longAxisEndPadding="78dip" - launcher:shortAxisStartPadding="0dip" - launcher:shortAxisEndPadding="0dip" - launcher:shortAxisCells="4" - launcher:longAxisCells="4" /> + launcher:yAxisStartPadding="8dip" + launcher:yAxisEndPadding="@dimen/button_bar_height" + launcher:xAxisStartPadding="0dip" + launcher:xAxisEndPadding="0dip" /> diff --git a/res/layout-xlarge-land/all_apps_tabbed.xml b/res/layout-xlarge-land/all_apps_tabbed.xml new file mode 100644 index 0000000..89a3104 --- /dev/null +++ b/res/layout-xlarge-land/all_apps_tabbed.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.launcher2.AllAppsTabbed + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"> + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TabWidget + android:id="@android:id/tabs" + android:layout_width="952dp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:background="@drawable/tab_unselected_holo" + android:tabStripEnabled="false" /> + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <com.android.launcher2.AllAppsPagedView + android:id="@+id/all_apps_paged_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + launcher:cellCountX="7" + launcher:cellCountY="5" + launcher:pageLayoutWidthGap="36dp" + launcher:pageLayoutHeightGap="6dp" + launcher:pageLayoutPaddingTop="15dp" + launcher:pageLayoutPaddingBottom="15dp" + launcher:pageLayoutPaddingLeft="40dp" + launcher:pageLayoutPaddingRight="40dp"> + </com.android.launcher2.AllAppsPagedView> + </FrameLayout> + </LinearLayout> +</com.android.launcher2.AllAppsTabbed> diff --git a/res/layout-xlarge-land/application.xml b/res/layout-xlarge-land/application.xml new file mode 100644 index 0000000..ed8fa8c --- /dev/null +++ b/res/layout-xlarge-land/application.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.launcher2.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android" + style="@style/WorkspaceIcon.Landscape" />
\ No newline at end of file diff --git a/res/layout-xlarge-land/customization_drawer.xml b/res/layout-xlarge-land/customization_drawer.xml new file mode 100644 index 0000000..9115259 --- /dev/null +++ b/res/layout-xlarge-land/customization_drawer.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.launcher2.CustomizePagedView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" + + android:layout_width="match_parent" + android:layout_height="match_parent" + launcher:widgetCellCountX="16" + launcher:cellCountX="8" + launcher:cellCountY="3" + launcher:pageLayoutWidthGap="32dp" + launcher:pageLayoutHeightGap="12dp" + launcher:pageLayoutPaddingTop="40dp" + launcher:pageLayoutPaddingBottom="25dp" + launcher:pageLayoutPaddingLeft="20dp" + launcher:pageLayoutPaddingRight="20dp" />
\ No newline at end of file diff --git a/res/layout-xlarge-land/launcher.xml b/res/layout-xlarge-land/launcher.xml new file mode 100644 index 0000000..e5ba393 --- /dev/null +++ b/res/layout-xlarge-land/launcher.xml @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.launcher2.DragLayer + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" + + android:id="@+id/drag_layer" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include + layout="@layout/all_apps_tabbed" + android:id="@+id/all_apps_view" + android:layout_width="match_parent" + android:layout_height="600dp" + android:layout_gravity="top"/> + + <!-- The workspace contains 5 screens of cells --> + <com.android.launcher2.Workspace + android:id="@+id/workspace" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="?android:attr/actionBarSize" + launcher:defaultScreen="2" + launcher:cellCountX="8" + launcher:cellCountY="7" + launcher:pageSpacing="32dp"> + + <include android:id="@+id/cell1" layout="@layout/workspace_screen" /> + <include android:id="@+id/cell2" layout="@layout/workspace_screen" /> + <include android:id="@+id/cell3" layout="@layout/workspace_screen" /> + <include android:id="@+id/cell4" layout="@layout/workspace_screen" /> + <include android:id="@+id/cell5" layout="@layout/workspace_screen" /> + </com.android.launcher2.Workspace> + + <RelativeLayout + android:id="@+id/all_apps_button_cluster" + android:layout_width="fill_parent" + android:layout_height="?android:attr/actionBarSize" + android:layout_gravity="top"> + + <ImageView + android:id="@+id/search_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="left" + android:layout_marginLeft="@dimen/toolbar_button_spacing" + + android:onClick="onClickSearchButton" + android:focusable="true" + android:clickable="true"/> + + <ImageView + android:id="@+id/configure_button" + android:src="@drawable/configure_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignParentRight="true" + android:layout_marginLeft="@dimen/toolbar_button_spacing" + android:layout_marginRight="@dimen/toolbar_button_spacing" + + android:onClick="onClickConfigureButton" + android:focusable="true" + android:clickable="true" /> + + <TextView + android:id="@+id/all_apps_button" + android:text="@string/all_apps_button_label" + android:background="@drawable/all_apps_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_toLeftOf="@id/configure_button" + android:layout_marginLeft="@dimen/toolbar_button_spacing" + android:layout_marginRight="@dimen/toolbar_button_spacing" + android:textSize="18dp" + + android:onClick="onClickAllAppsButton" + android:focusable="true" + android:clickable="true" /> + + <!-- The button to bring up the installed app market. + The icon for this button will be dynamically set. --> + <ImageView + android:id="@+id/market_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignLeft="@id/all_apps_button" + + android:onClick="onClickAppMarketButton" + android:focusable="false" + android:clickable="false" + android:visibility="gone"/> + + <com.android.launcher2.DeleteZone + android:id="@+id/delete_zone" + android:src="@drawable/delete_zone_selector" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignLeft="@id/all_apps_button" + + android:visibility="gone" + launcher:direction="horizontal" /> + + <com.android.launcher2.ApplicationInfoDropTarget + android:id="@+id/info_button" + android:src="@drawable/info_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignLeft="@id/configure_button" + + android:visibility="gone" + android:focusable="true" + android:clickable="true" /> + + </RelativeLayout> + + <TabHost + android:id="@android:id/tabhost" + android:layout_width="match_parent" + android:layout_height="460dp" + android:layout_gravity="bottom"> + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TabWidget + android:id="@android:id/tabs" + android:layout_width="952dp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:background="@drawable/tab_unselected_holo" + android:tabStripEnabled="false" /> + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="match_parent" + android:layout_height="match_parent"> + </FrameLayout> + </LinearLayout> + </TabHost> +</com.android.launcher2.DragLayer> diff --git a/res/layout-xlarge-land/workspace_screen.xml b/res/layout-xlarge-land/workspace_screen.xml new file mode 100644 index 0000000..64de05e --- /dev/null +++ b/res/layout-xlarge-land/workspace_screen.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.launcher2.CellLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" + + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:hapticFeedbackEnabled="false" + + launcher:cellWidth="@dimen/workspace_cell_width" + launcher:cellHeight="@dimen/workspace_cell_height" + launcher:widthGap="@dimen/workspace_width_gap" + launcher:heightGap="@dimen/workspace_height_gap" + launcher:yAxisStartPadding="25dip" + launcher:yAxisEndPadding="25dip" + launcher:xAxisStartPadding="40dip" + launcher:xAxisEndPadding="40dip" /> diff --git a/res/layout-xlarge-port/all_apps_tabbed.xml b/res/layout-xlarge-port/all_apps_tabbed.xml new file mode 100644 index 0000000..65e3729 --- /dev/null +++ b/res/layout-xlarge-port/all_apps_tabbed.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.launcher2.AllAppsTabbed + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"> + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TabWidget + android:id="@android:id/tabs" + android:layout_width="700dp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:background="@drawable/tab_unselected_holo" + android:tabStripEnabled="false" /> + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <com.android.launcher2.AllAppsPagedView + android:id="@+id/all_apps_paged_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + launcher:cellCountX="5" + launcher:cellCountY="7" + launcher:pageLayoutWidthGap="36dp" + launcher:pageLayoutHeightGap="36dp" + launcher:pageLayoutPaddingTop="25dp" + launcher:pageLayoutPaddingBottom="10dp" + launcher:pageLayoutPaddingLeft="20dp" + launcher:pageLayoutPaddingRight="20dp"> + </com.android.launcher2.AllAppsPagedView> + </FrameLayout> + </LinearLayout> +</com.android.launcher2.AllAppsTabbed> diff --git a/res/layout-xlarge-port/application.xml b/res/layout-xlarge-port/application.xml new file mode 100644 index 0000000..af7a8a4 --- /dev/null +++ b/res/layout-xlarge-port/application.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.launcher2.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android" + style="@style/WorkspaceIcon.Portrait" /> diff --git a/res/layout-xlarge-port/customization_drawer.xml b/res/layout-xlarge-port/customization_drawer.xml new file mode 100644 index 0000000..28127ea --- /dev/null +++ b/res/layout-xlarge-port/customization_drawer.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.launcher2.CustomizePagedView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" + + android:layout_width="match_parent" + android:layout_height="match_parent" + launcher:widgetCellCountX="10" + launcher:cellCountX="5" + launcher:cellCountY="3" + launcher:pageLayoutWidthGap="36dp" + launcher:pageLayoutHeightGap="12dp" + launcher:pageLayoutPaddingTop="40dp" + launcher:pageLayoutPaddingBottom="25dp" + launcher:pageLayoutPaddingLeft="20dp" + launcher:pageLayoutPaddingRight="20dp" /> diff --git a/res/layout-xlarge-port/launcher.xml b/res/layout-xlarge-port/launcher.xml new file mode 100644 index 0000000..13b69fe --- /dev/null +++ b/res/layout-xlarge-port/launcher.xml @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.launcher2.DragLayer + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" + + android:id="@+id/drag_layer" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include + layout="@layout/all_apps_tabbed" + android:id="@+id/all_apps_view" + android:layout_width="match_parent" + android:layout_height="1040dp" + android:layout_gravity="top"/> + + <!-- The workspace contains 5 screens of cells --> + <com.android.launcher2.Workspace + android:id="@+id/workspace" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="?android:attr/actionBarSize" + launcher:defaultScreen="2" + launcher:cellCountX="8" + launcher:cellCountY="7" + launcher:pageSpacing="64dp"> + + <include android:id="@+id/cell1" layout="@layout/workspace_screen" /> + <include android:id="@+id/cell2" layout="@layout/workspace_screen" /> + <include android:id="@+id/cell3" layout="@layout/workspace_screen" /> + <include android:id="@+id/cell4" layout="@layout/workspace_screen" /> + <include android:id="@+id/cell5" layout="@layout/workspace_screen" /> + </com.android.launcher2.Workspace> + + <RelativeLayout + android:id="@+id/all_apps_button_cluster" + android:layout_width="fill_parent" + android:layout_height="?android:attr/actionBarSize" + android:layout_gravity="top"> + + <ImageView + android:id="@+id/search_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="left" + android:layout_marginLeft="@dimen/toolbar_button_spacing" + + android:onClick="onClickSearchButton" + android:focusable="true" + android:clickable="true"/> + + <ImageView + android:id="@+id/configure_button" + android:src="@drawable/configure_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignParentRight="true" + android:layout_marginLeft="@dimen/toolbar_button_spacing" + android:layout_marginRight="@dimen/toolbar_button_spacing" + + android:onClick="onClickConfigureButton" + android:focusable="true" + android:clickable="true" /> + + <TextView + android:id="@+id/all_apps_button" + android:text="@string/all_apps_button_label" + android:background="@drawable/all_apps_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_toLeftOf="@id/configure_button" + android:layout_marginLeft="@dimen/toolbar_button_spacing" + android:layout_marginRight="@dimen/toolbar_button_spacing" + android:textSize="18dp" + + android:onClick="onClickAllAppsButton" + android:focusable="true" + android:clickable="true" /> + + <!-- The button to bring up the installed app market. + The icon for this button will be dynamically set. --> + <ImageView + android:id="@+id/market_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignLeft="@id/all_apps_button" + + android:onClick="onClickAppMarketButton" + android:focusable="false" + android:clickable="false" + android:visibility="gone"/> + + <com.android.launcher2.DeleteZone + android:id="@+id/delete_zone" + android:src="@drawable/delete_zone_selector" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignLeft="@id/all_apps_button" + + android:visibility="gone" + launcher:direction="horizontal" /> + + <com.android.launcher2.ApplicationInfoDropTarget + android:id="@+id/info_button" + android:src="@drawable/info_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignLeft="@id/configure_button" + + android:visibility="gone" + android:focusable="true" + android:clickable="true" /> + + </RelativeLayout> + + <TabHost + android:id="@android:id/tabhost" + android:layout_width="match_parent" + android:layout_height="800dp" + android:layout_gravity="bottom"> + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TabWidget + android:id="@android:id/tabs" + android:layout_width="700dp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:background="@drawable/tab_unselected_holo" + android:tabStripEnabled="false" /> + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="match_parent" + android:layout_height="650dp"> + </FrameLayout> + </LinearLayout> + </TabHost> +</com.android.launcher2.DragLayer> diff --git a/res/layout-xlarge-port/workspace_screen.xml b/res/layout-xlarge-port/workspace_screen.xml new file mode 100644 index 0000000..406441d --- /dev/null +++ b/res/layout-xlarge-port/workspace_screen.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.launcher2.CellLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" + + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:hapticFeedbackEnabled="false" + + launcher:cellWidth="@dimen/workspace_cell_width" + launcher:cellHeight="@dimen/workspace_cell_height" + launcher:widthGap="@dimen/workspace_width_gap" + launcher:heightGap="@dimen/workspace_height_gap" + launcher:yAxisStartPadding="25dip" + launcher:yAxisEndPadding="25dip" + launcher:xAxisStartPadding="15dip" + launcher:xAxisEndPadding="15dip"/> diff --git a/res/layout-xlarge/all_apps_no_items_placeholder.xml b/res/layout-xlarge/all_apps_no_items_placeholder.xml new file mode 100644 index 0000000..f5ecdec --- /dev/null +++ b/res/layout-xlarge/all_apps_no_items_placeholder.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + + android:id="@+id/no_items_icon" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:paddingTop="2dip" + + android:textColor="#FFFFFFFF" + android:textSize="15sp" + android:shadowColor="#FF000000" + android:shadowDx="0.0" + android:shadowDy="1.0" + android:shadowRadius="1.0" + android:drawableLeft="@drawable/ic_no_applications" + android:drawablePadding="0dip" + + android:maxLines="2" + android:fadingEdge="horizontal" /> diff --git a/res/layout-xlarge/all_apps_paged_view_application.xml b/res/layout-xlarge/all_apps_paged_view_application.xml new file mode 100644 index 0000000..7458222 --- /dev/null +++ b/res/layout-xlarge/all_apps_paged_view_application.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.launcher2.PagedViewIcon + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" + + launcher:blurColor="#FF6B8CF0" + launcher:outlineColor="#FF8CD2FF" + launcher:checkedBlurColor="#FFBBE83C" + launcher:checkedOutlineColor="#FF8CD2FF" + + android:id="@+id/application_icon" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:singleLine="false" + android:maxLines="1" + + style="@style/WorkspaceIcon.Portrait" /> diff --git a/res/layout-xlarge/customize_paged_view_item.xml b/res/layout-xlarge/customize_paged_view_item.xml new file mode 100644 index 0000000..bbc04d7 --- /dev/null +++ b/res/layout-xlarge/customize_paged_view_item.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<com.android.launcher2.PagedViewIcon + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" + + launcher:blurColor="#FF6B8CF0" + launcher:outlineColor="#FF8CD2FF" + launcher:checkedBlurColor="#FFBBE83C" + launcher:checkedOutlineColor="#FF8CD2FF" + + android:id="@+id/customize_icon" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:singleLine="false" + android:maxLines="1" + + style="@style/WorkspaceIcon.Landscape" /> diff --git a/res/layout-xlarge/customize_paged_view_widget.xml b/res/layout-xlarge/customize_paged_view_widget.xml new file mode 100644 index 0000000..fab2a9b --- /dev/null +++ b/res/layout-xlarge/customize_paged_view_widget.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.launcher2.PagedViewWidgetIcon + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" + + android:layout_width="wrap_content" + android:layout_height="365dp" + android:paddingLeft="12.5dp" + android:paddingRight="12.5dp" + android:paddingBottom="50dp" + android:gravity="top" + android:orientation="vertical" + + launcher:checkedBlurColor="#FFDAFF71" + launcher:checkedOutlineColor="#FFCFFF9C"> + + <!-- The icon of the widget. --> + <ImageView + android:id="@+id/widget_preview" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1.0" + android:scaleType="fitStart" /> + + <!-- The divider image. --> + <ImageView + android:id="@+id/divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="10dp" + android:paddingBottom="10dp" + android:src="@drawable/widget_divider" /> + + <!-- The name of the widget. --> + <TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/widget_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="left" + + android:textColor="#FFFFFFFF" + android:textSize="14sp" + android:shadowColor="#FF000000" + android:shadowDx="0.0" + android:shadowDy="1.0" + android:shadowRadius="1.0" + + android:maxLines="2" + android:fadingEdge="horizontal" /> + + <!-- The original dimensions of the widget (can't be the same text as above due to different + style. --> + <TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/widget_dims" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="left" + + android:textColor="#FF999999" + android:textSize="14sp" + android:shadowColor="#99000000" + android:shadowDx="0.0" + android:shadowDy="1.0" + android:shadowRadius="1.0" /> +</com.android.launcher2.PagedViewWidgetIcon> diff --git a/res/layout-xlarge/external_widget_drop_list_item.xml b/res/layout-xlarge/external_widget_drop_list_item.xml new file mode 100644 index 0000000..84f40ed --- /dev/null +++ b/res/layout-xlarge/external_widget_drop_list_item.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + + android:layout_width="match_parent" + android:layout_height="64dp"> + <ImageView + android:id="@+id/provider_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="20dp" + android:maxWidth="32dp" + android:maxHeight="32dp" + android:scaleType="fitCenter" + android:src="@drawable/ic_launcher_application" /> + <TextView + android:id="@+id/provider" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginLeft="5dp" + android:gravity="center_vertical" + android:textSize="18sp" /> +</LinearLayout> diff --git a/res/layout/home_customization_drawer_item.xml b/res/layout/home_customization_drawer_item.xml new file mode 100644 index 0000000..4d61dbf --- /dev/null +++ b/res/layout/home_customization_drawer_item.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="200dip" + android:layout_height="200dip" + android:padding="20dip" + android:orientation="vertical" + android:gravity="center_horizontal|center_vertical|clip_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="#ffffffff" />
\ No newline at end of file diff --git a/res/layout/home_customization_drawer_widget.xml b/res/layout/home_customization_drawer_widget.xml new file mode 100644 index 0000000..1581308 --- /dev/null +++ b/res/layout/home_customization_drawer_widget.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="600dip" + android:layout_height="match_parent" + android:padding="20dip" + android:orientation="vertical" + android:gravity="center_horizontal|center_vertical|clip_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="#ffffffff" />
\ No newline at end of file diff --git a/res/raw/allapps.rs b/res/raw/allapps.rs deleted file mode 100644 index af5abd3..0000000 --- a/res/raw/allapps.rs +++ /dev/null @@ -1,427 +0,0 @@ -#pragma version(1) -#pragma stateVertex(PV) -#pragma stateFragment(PFTexNearest) -#pragma stateStore(PSIcons) - -#define PI 3.14159f - -int g_SpecialHWWar; - -// Attraction to center values from page edge to page center. -float g_AttractionTable[9]; -float g_FrictionTable[9]; -float g_PhysicsTableSize; - -float g_PosPage; -float g_PosVelocity; -float g_LastPositionX; -int g_LastTouchDown; -float g_DT; -int g_LastTime; -int g_PosMax; -float g_Zoom; -float g_Animation; -float g_OldPosPage; -float g_OldPosVelocity; -float g_OldZoom; -float g_MoveToTotalTime; -float g_MoveToTime; -float g_MoveToOldPos; - -int g_Cols; -int g_Rows; - -// Drawing constants, should be parameters ====== -#define VIEW_ANGLE 1.28700222f - -int g_DrawLastFrame; -int lastFrame(int draw) { - // We draw one extra frame to work around the last frame post bug. - // We also need to track if we drew the last frame to deal with large DT - // in the physics. - int ret = g_DrawLastFrame | draw; - g_DrawLastFrame = draw; - return ret; // should return draw instead. -} - -void updateReadback() { - if ((g_OldPosPage != g_PosPage) || - (g_OldPosVelocity != g_PosVelocity) || - (g_OldZoom != g_Zoom)) { - - g_OldPosPage = g_PosPage; - g_OldPosVelocity = g_PosVelocity; - g_OldZoom = g_Zoom; - - int i[3]; - i[0] = g_PosPage * (1 << 16); - i[1] = g_PosVelocity * (1 << 16); - i[2] = g_OldZoom * (1 << 16); - sendToClient(&i[0], 1, 12, 1); - } -} - -void setColor(float r, float g, float b, float a) { - if (g_SpecialHWWar) { - color(0, 0, 0, 0.001f); - } else { - color(r, g, b, a); - } -} - -void init() { - g_AttractionTable[0] = 20.0f; - g_AttractionTable[1] = 20.0f; - g_AttractionTable[2] = 20.0f; - g_AttractionTable[3] = 10.0f; - g_AttractionTable[4] = -10.0f; - g_AttractionTable[5] = -20.0f; - g_AttractionTable[6] = -20.0f; - g_AttractionTable[7] = -20.0f; - g_AttractionTable[8] = -20.0f; // dup 7 to avoid a clamp later - g_FrictionTable[0] = 10.0f; - g_FrictionTable[1] = 10.0f; - g_FrictionTable[2] = 11.0f; - g_FrictionTable[3] = 15.0f; - g_FrictionTable[4] = 15.0f; - g_FrictionTable[5] = 11.0f; - g_FrictionTable[6] = 10.0f; - g_FrictionTable[7] = 10.0f; - g_FrictionTable[8] = 10.0f; // dup 7 to avoid a clamp later - g_PhysicsTableSize = 7; - - g_PosVelocity = 0; - g_PosPage = 0; - g_LastTouchDown = 0; - g_LastPositionX = 0; - g_Zoom = 0; - g_Animation = 1.f; - g_SpecialHWWar = 1; - g_MoveToTime = 0; - g_MoveToOldPos = 0; - g_MoveToTotalTime = 0.2f; // Duration of scrolling 1 line -} - -void resetHWWar() { - g_SpecialHWWar = 1; -} - -void move() { - if (g_LastTouchDown) { - float dx = -(state->newPositionX - g_LastPositionX); - g_PosVelocity = 0; - g_PosPage += dx * 5.2f; - - float pmin = -0.49f; - float pmax = g_PosMax + 0.49f; - g_PosPage = clampf(g_PosPage, pmin, pmax); - } - g_LastTouchDown = state->newTouchDown; - g_LastPositionX = state->newPositionX; - g_MoveToTime = 0; - //debugF("Move P", g_PosPage); -} - -void moveTo() { - g_MoveToTime = g_MoveToTotalTime; - g_PosVelocity = 0; - g_MoveToOldPos = g_PosPage; - - // debugF("======= moveTo", state->targetPos); -} - -void setZoom() { - g_Zoom = state->zoomTarget; - g_DrawLastFrame = 1; - updateReadback(); -} - -void fling() { - g_LastTouchDown = 0; - g_PosVelocity = -state->flingVelocity * 4; - float av = fabsf(g_PosVelocity); - float minVel = 3.5f; - - minVel *= 1.f - (fabsf(fracf(g_PosPage + 0.5f) - 0.5f) * 0.45f); - - if (av < minVel && av > 0.2f) { - if (g_PosVelocity > 0) { - g_PosVelocity = minVel; - } else { - g_PosVelocity = -minVel; - } - } - - if (g_PosPage <= 0) { - g_PosVelocity = maxf(0, g_PosVelocity); - } - if (g_PosPage > g_PosMax) { - g_PosVelocity = minf(0, g_PosVelocity); - } -} - -float -modf(float x, float y) -{ - return x-(y*floorf(x/y)); -} - - -/* - * Interpolates values in the range 0..1 to a curve that eases in - * and out. - */ -float -getInterpolation(float input) { - return (cosf((input + 1) * PI) / 2.0f) + 0.5f; -} - - -void updatePos() { - if (g_LastTouchDown) { - return; - } - - float tablePosNorm = fracf(g_PosPage + 0.5f); - float tablePosF = tablePosNorm * g_PhysicsTableSize; - int tablePosI = tablePosF; - float tablePosFrac = tablePosF - tablePosI; - float accel = lerpf(g_AttractionTable[tablePosI], - g_AttractionTable[tablePosI + 1], - tablePosFrac) * g_DT; - float friction = lerpf(g_FrictionTable[tablePosI], - g_FrictionTable[tablePosI + 1], - tablePosFrac) * g_DT; - - if (g_MoveToTime) { - // New position is old posiition + (total distance) * (interpolated time) - g_PosPage = g_MoveToOldPos + (state->targetPos - g_MoveToOldPos) * getInterpolation((g_MoveToTotalTime - g_MoveToTime) / g_MoveToTotalTime); - g_MoveToTime -= g_DT; - if (g_MoveToTime <= 0) { - g_MoveToTime = 0; - g_PosPage = state->targetPos; - } - return; - } - - // If our velocity is low OR acceleration is opposing it, apply it. - if (fabsf(g_PosVelocity) < 4.0f || (g_PosVelocity * accel) < 0) { - g_PosVelocity += accel; - } - //debugF("g_PosPage", g_PosPage); - //debugF(" g_PosVelocity", g_PosVelocity); - //debugF(" friction", friction); - //debugF(" accel", accel); - - // Normal physics - if (g_PosVelocity > 0) { - g_PosVelocity -= friction; - g_PosVelocity = maxf(g_PosVelocity, 0); - } else { - g_PosVelocity += friction; - g_PosVelocity = minf(g_PosVelocity, 0); - } - - if ((friction > fabsf(g_PosVelocity)) && (friction > fabsf(accel))) { - // Special get back to center and overcome friction physics. - float t = tablePosNorm - 0.5f; - if (fabsf(t) < (friction * g_DT)) { - // really close, just snap - g_PosPage = roundf(g_PosPage); - g_PosVelocity = 0; - } else { - if (t > 0) { - g_PosVelocity = -friction; - } else { - g_PosVelocity = friction; - } - } - } - - // Check for out of boundry conditions. - if (g_PosPage < 0 && g_PosVelocity < 0) { - float damp = 1.0 + (g_PosPage * 4); - damp = clampf(damp, 0.f, 0.9f); - g_PosVelocity *= damp; - } - if (g_PosPage > g_PosMax && g_PosVelocity > 0) { - float damp = 1.0 - ((g_PosPage - g_PosMax) * 4); - damp = clampf(damp, 0.f, 0.9f); - g_PosVelocity *= damp; - } - - g_PosPage += g_PosVelocity * g_DT; - g_PosPage = clampf(g_PosPage, -0.49, g_PosMax + 0.49); -} - - -void -draw_home_button() -{ - setColor(1.0f, 1.0f, 1.0f, 1.0f); - bindTexture(NAMED_PFTexNearest, 0, state->homeButtonId); - - float w = getWidth(); - float h = getHeight(); - - float x; - float y; - if (getWidth() > getHeight()) { - x = w - (params->homeButtonTextureWidth * (1 - g_Animation)) + 20; - y = (h - params->homeButtonTextureHeight) * 0.5f; - } else { - x = (w - params->homeButtonTextureWidth) / 2; - y = -g_Animation * params->homeButtonTextureHeight; - y -= 30; // move the house to the edge of the screen as it doesn't fill the texture. - } - - drawSpriteScreenspace(x, y, 0, params->homeButtonTextureWidth, params->homeButtonTextureHeight); -} - -void drawFrontGrid(float rowOffset, float p) -{ - float h = getHeight(); - float w = getWidth(); - - int intRowOffset = rowOffset; - float rowFrac = rowOffset - intRowOffset; - float colWidth = 120.f;//getWidth() / 4; - float rowHeight = colWidth + 25.f; - float yoff = 0.5f * h + 1.5f * rowHeight; - - int row, col; - int colCount = 4; - if (w > h) { - colCount = 6; - rowHeight -= 12.f; - yoff = 0.47f * h + 1.0f * rowHeight; - } - - int iconNum = (intRowOffset - 5) * colCount; - - - bindProgramVertex(NAMED_PVCurve); - - vpConstants->Position.z = p; - - setColor(1.0f, 1.0f, 1.0f, 1.0f); - for (row = -5; row < 15; row++) { - float y = yoff - ((-rowFrac + row) * rowHeight); - - for (col=0; col < colCount; col++) { - if (iconNum >= state->iconCount) { - return; - } - - if (iconNum >= 0) { - float x = colWidth * col + (colWidth / 2); - vpConstants->Position.x = x + 0.2f; - - if (state->selectedIconIndex == iconNum && !p) { - bindProgramFragment(NAMED_PFTexNearest); - bindTexture(NAMED_PFTexNearest, 0, state->selectedIconTexture); - vpConstants->ImgSize.x = SELECTION_TEXTURE_WIDTH_PX; - vpConstants->ImgSize.y = SELECTION_TEXTURE_HEIGHT_PX; - vpConstants->Position.y = y - (SELECTION_TEXTURE_HEIGHT_PX - ICON_TEXTURE_HEIGHT_PX) * 0.5f; - drawSimpleMesh(NAMED_SMCell); - } - - bindProgramFragment(NAMED_PFTexMip); - vpConstants->ImgSize.x = ICON_TEXTURE_WIDTH_PX; - vpConstants->ImgSize.y = ICON_TEXTURE_HEIGHT_PX; - vpConstants->Position.y = y - 0.2f; - bindTexture(NAMED_PFTexMip, 0, loadI32(ALLOC_ICON_IDS, iconNum)); - drawSimpleMesh(NAMED_SMCell); - - bindProgramFragment(NAMED_PFTexMipAlpha); - vpConstants->ImgSize.x = 120.f; - vpConstants->ImgSize.y = 64.f; - vpConstants->Position.y = y - 64.f - 0.2f; - bindTexture(NAMED_PFTexMipAlpha, 0, loadI32(ALLOC_LABEL_IDS, iconNum)); - drawSimpleMesh(NAMED_SMCell); - } - iconNum++; - } - } -} - - -int -main(int launchID) -{ - // Compute dt in seconds. - int newTime = uptimeMillis(); - g_DT = (newTime - g_LastTime) / 1000.f; - g_LastTime = newTime; - - if (!g_DrawLastFrame) { - // If we stopped rendering we cannot use DT. - // assume 30fps in this case. - g_DT = 0.033f; - } - // physics may break if DT is large. - g_DT = minf(g_DT, 0.2f); - - if (g_Zoom != state->zoomTarget) { - float dz = g_DT * 1.7f; - if (state->zoomTarget < 0.5f) { - dz = -dz; - } - if (fabsf(g_Zoom - state->zoomTarget) < fabsf(dz)) { - g_Zoom = state->zoomTarget; - } else { - g_Zoom += dz; - } - updateReadback(); - } - g_Animation = powf(1-g_Zoom, 3); - - // Set clear value to dim the background based on the zoom position. - if ((g_Zoom < 0.001f) && (state->zoomTarget < 0.001f) && !g_SpecialHWWar) { - pfClearColor(0.0f, 0.0f, 0.0f, 0.0f); - // When we're zoomed out and not tracking motion events, reset the pos to 0. - if (!g_LastTouchDown) { - g_PosPage = 0; - } - return lastFrame(0); - } else { - pfClearColor(0.0f, 0.0f, 0.0f, g_Zoom); - } - - // icons & labels - int iconCount = state->iconCount; - if (getWidth() > getHeight()) { - g_Cols = 6; - g_Rows = 3; - } else { - g_Cols = 4; - g_Rows = 4; - } - g_PosMax = ((iconCount + (g_Cols-1)) / g_Cols) - g_Rows; - if (g_PosMax < 0) g_PosMax = 0; - - updatePos(); - updateReadback(); - - //debugF(" draw g_PosPage", g_PosPage); - - // Draw the icons ======================================== - drawFrontGrid(g_PosPage, g_Animation); - - bindProgramFragment(NAMED_PFTexNearest); - draw_home_button(); - - // This is a WAR to do a rendering pass without drawing during init to - // force the driver to preload and compile its shaders. - // Without this the first animation does not appear due to the time it - // takes to init the driver state. - if (g_SpecialHWWar) { - g_SpecialHWWar = 0; - return 1; - } - - // Bug workaround where the last frame is not always displayed - // So we keep rendering until the bug is fixed. - return lastFrame((g_PosVelocity != 0) || fracf(g_PosPage) || g_Zoom != state->zoomTarget || (g_MoveToTime != 0)); -} - diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 0f28387..e938b73 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Tapety"</string> <string name="activity_not_found" msgid="3571057450431950427">"Aplikace není v telefonu nainstalována."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Konfigurace..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgety"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Složky"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Další"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Tapety"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Zástupci aplikací"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Toto bude karta Tapety"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Vše"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Aplikace"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Hry"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Stažené"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Název složky"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Přejmenovat složku"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Vyberte zástupce"</string> <string name="title_select_application" msgid="8031072293115454221">"Výběr aplikace"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Vybrat složku"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Všechny aplikace"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Aplikace"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Plocha"</string> <string name="menu_add" msgid="3065046628354640854">"Přidat"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Spravovat aplikace"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Oznámení"</string> <string name="menu_gestures" msgid="514678675575912237">"Gesta"</string> <string name="menu_settings" msgid="6233960148378443661">"Nastavení"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Odinstalovat aplikaci"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Podrobnosti o aplikaci"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"Vybrána 1 aplikace"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"Vybrán 1 widget"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"Vybrána 1 složka"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"Vybrán 1 zástupce"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"instalovat zástupce"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Povoluje aplikaci přidat zástupce bez zásahu uživatele."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"odinstalovat zástupce"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"zápis nastavení a odkazů plochy"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Povoluje aplikaci změnit nastavení a odkazy plochy."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Problém s načtením widgetu"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Toto je systémová aplikace a nelze ji odinstalovat."</string> </resources> diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index d23e046..576abce 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Tapeter"</string> <string name="activity_not_found" msgid="3571057450431950427">"Programmet er ikke installeret på din telefon."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Konfigurer ..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgets"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Mapper"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Flere"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Tapeter"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Programgenvej"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Dette er fanen for tapeter"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Alle"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Programmer"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Spil"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Downloadet"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Mappenavn"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Omdøb mappe"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Vælg genvej"</string> <string name="title_select_application" msgid="8031072293115454221">"Vælg program"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Vælg mappe"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Alle programmer"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Programmer"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Start"</string> <string name="menu_add" msgid="3065046628354640854">"Tilføj"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Administrer programmer"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Meddelelser"</string> <string name="menu_gestures" msgid="514678675575912237">"Gestus"</string> <string name="menu_settings" msgid="6233960148378443661">"Indstillinger"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Afinstaller program"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Programoplysninger"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1 program er valgt"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1 widget er valgt"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 mappe er valgt"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 genvej er valgt"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"installer genveje"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Tillader, at et program tilføjer genveje uden brugerindgriben."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"afinstaller genveje"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"skriv indstillinger og genveje for Start"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Tillader, at et program ændrer indstillingerne og genvejene i Start."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Der er problemer med indlæsning af widget"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Dette er et systemprogram, som ikke kan afinstalleres."</string> </resources> diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 5a4186b..575db07 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Hintergrundbilder"</string> <string name="activity_not_found" msgid="3571057450431950427">"Anwendung ist nicht auf dem Telefon installiert."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Konfigurieren..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgets"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Ordner"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Mehr"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Hintergründe"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"App-Verknüpfungen"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Dies ist der Tab \"Hintergründe\""</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Alle"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Apps"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Spiele"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Heruntergeladen"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Ordnername"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Ordner umbenennen"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Tastenkürzel auswählen"</string> <string name="title_select_application" msgid="8031072293115454221">"Anwendung auswählen"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Ordner auswählen"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Alle Anwendungen"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Anwendungen"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Startseite"</string> <string name="menu_add" msgid="3065046628354640854">"Hinzufügen"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Apps verwalten"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Benachrichtigungen"</string> <string name="menu_gestures" msgid="514678675575912237">"Bewegungen"</string> <string name="menu_settings" msgid="6233960148378443661">"Einstellungen"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Anwendung deinstallieren"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Anwendungsdetails"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1 Anwendung ausgewählt"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1 Widget ausgewählt"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 Ordner ausgewählt"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 Verknüpfung ausgewählt"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"Verknüpfungen installieren"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Ermöglicht einer Anwendung das Hinzufügen von Verknüpfungen ohne Eingriff des Nutzers."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"Verknüpfungen deinstallieren"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"Einstellungen und Shortcuts für Startseite schreiben"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Ermöglicht einer Anwendung, die Einstellungen und Shortcuts auf der Startseite zu ändern."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Problem beim Laden des Widgets"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Dies ist eine Systemanwendung, die nicht deinstalliert werden kann."</string> </resources> diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 1e27440..0937c5b 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Ταπετσαρίες"</string> <string name="activity_not_found" msgid="3571057450431950427">"Η εφαρμογή δεν έχει εγκατασταθεί στο τηλέφωνό σας."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Διαμόρφωση..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Γραφικά στοιχεία"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Φάκελοι"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Περισσότερα"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Ταπετσαρίες"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Συντομεύσεις Εφαρμογών"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Αυτή θα είναι η καρτέλα ταπετσαριών"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Κάθε ηλικία"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Εφαρμογές"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Παιχνίδια"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Η λήψη ολοκληρώθηκε"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Όνομα φακέλου"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Μετονομασία φακέλου"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Επιλογή συντόμευσης"</string> <string name="title_select_application" msgid="8031072293115454221">"Επιλέξτε εφαρμογή"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Επιλογή φακέλου"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Όλες οι εφαρμογές"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Εφαρμογές"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Αρχική σελίδα"</string> <string name="menu_add" msgid="3065046628354640854">"Προσθήκη"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Διαχείριση εφαρμογών"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Ειδοποιήσεις"</string> <string name="menu_gestures" msgid="514678675575912237">"Χειρονομίες"</string> <string name="menu_settings" msgid="6233960148378443661">"Ρυθμίσεις"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Κατάργηση εγκατάστασης εφαρμογής"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Λεπτομέρειες εφαρμογής"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"Επιλέχθηκε 1 εφαρμογή"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"Επιλέχθηκε 1 γραφικό στοιχείο"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"Επιλέχθηκε 1 φάκελος"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"Επιλέχθηκε 1 συντόμευση"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"εγκατάσταση συντομεύσεων"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Επιτρέπει σε μια εφαρμογή την προσθήκη συντομεύσεων χωρίς την παρέμβαση του χρήστη."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"κατάργηση εγκατάστασης συντομεύσεων"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"εγγραφή ρυθμίσεων και συντομεύσεων αρχικής οθόνης"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Επιτρέπει σε μια εφαρμογή την αλλαγή των ρυθμίσεων και των συντομεύσεων στην αρχική οθόνη."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Παρουσιάστηκε πρόβλημα στη φόρτωση του γραφικού στοιχείου"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Αυτή είναι εφαρμογή του συστήματος και δεν είναι δυνατή η κατάργηση της εγκατάστασής της."</string> </resources> diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 7c26851..d718936 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -27,6 +27,18 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Papeles tapiz"</string> <string name="activity_not_found" msgid="3571057450431950427">"La aplicación no está instalada en tu computadora."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Configurar..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgets"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Carpetas"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Más"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Papeles tapiz"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Accesos directos a aplicaciones"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Ésta será la pestaña para los papeles tapiz"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Todos"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Google Apps"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Juegos"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Descargado"</string> + <string name="all_apps_no_games" msgid="5293893733372793696">"No"</string> + <string name="all_apps_no_downloads" msgid="6145042636084482299">"No Google Apps"</string> <string name="rename_folder_label" msgid="5646236631298452787">"Nombre de carpeta"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Cambiar nombre de carpeta"</string> <string name="rename_action" msgid="6016003384693240896">"Aceptar"</string> @@ -48,7 +60,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Seleccionar acceso directo"</string> <string name="title_select_application" msgid="8031072293115454221">"Seleccionar aplicación"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Seleccionar carpeta"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Todas las aplicaciones"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Google Apps"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Página principal"</string> <string name="menu_add" msgid="3065046628354640854">"Agregar"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Administrar aplicaciones"</string> @@ -57,6 +69,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Notificaciones"</string> <string name="menu_gestures" msgid="514678675575912237">"Gestos"</string> <string name="menu_settings" msgid="6233960148378443661">"Configuración"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Desinstalar la aplicación"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Detalles de la aplicación"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"Se seleccionó 1 aplicación"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"Se seleccionó 1 widget"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"Se seleccionó 1 carpeta"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"Se seleccionó 1 acceso directo"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"instalar accesos directos"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Permite a una aplicación agregar accesos directos sin intervención del usuario."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"desinstalar papel tapiz"</string> @@ -66,4 +84,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"escribir configuración y accesos directos de la página principal"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Permite a una aplicación cambiar la configuración y los accesos directos de la página principal."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Problema al cargar el widget"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Esta es una aplicación de sistema y no puede desinstalarse."</string> </resources> diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 49f3891..ab0485a 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Fondos de pantalla"</string> <string name="activity_not_found" msgid="3571057450431950427">"La aplicación no está instalada en el teléfono."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Configurar..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgets"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Carpetas"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Más"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Fondos de pantalla"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Accesos directos de aplicaciones"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Será la carpeta de fondos de pantalla."</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Todas las aplicaciones"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Aplicaciones"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Juegos"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Descargadas"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Nombre de carpeta"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Cambiar nombre de carpeta"</string> <string name="rename_action" msgid="6016003384693240896">"Aceptar"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Seleccionar acceso directo"</string> <string name="title_select_application" msgid="8031072293115454221">"Seleccionar aplicación"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Seleccionar carpeta"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Todas las aplicaciones"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Aplicaciones"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Inicio"</string> <string name="menu_add" msgid="3065046628354640854">"Añadir"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Administrar aplicaciones"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Notificaciones"</string> <string name="menu_gestures" msgid="514678675575912237">"Gestos"</string> <string name="menu_settings" msgid="6233960148378443661">"Ajustes"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Desinstalar aplicación"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Información de la aplicación"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"Se ha seleccionado una aplicación."</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"Se ha seleccionado un widget."</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"Se ha seleccionado una carpeta."</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"Se ha seleccionado un acceso directo."</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"instalar accesos directos"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Permite que una aplicación añada accesos directos sin intervención del usuario."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"desinstalar accesos directos"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"escribir información de accesos directos y de configuración del escritorio"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Permite que una aplicación modifique la configuración y los accesos directos de la página de inicio."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Problema al cargar el widget"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Se trata de una aplicación del sistema y no se puede desinstalar."</string> </resources> diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 29c4c58..35ee775 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Fonds d\'écran"</string> <string name="activity_not_found" msgid="3571057450431950427">"L\'application n\'est pas installée sur votre téléphone."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Configurer..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgets"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Dossiers"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Plus"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Fonds d\'écran"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Raccourcis des applications"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Onglet des fonds d\'écran"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Toutes"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Applications"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Jeux"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Téléchargées"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Nom du dossier"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Renommer le dossier"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Sélectionner un raccourci"</string> <string name="title_select_application" msgid="8031072293115454221">"Sélection d\'une application"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Sélectionner le dossier"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Toutes les applications"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Applications"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Page d\'accueil"</string> <string name="menu_add" msgid="3065046628354640854">"Ajouter"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Gérer les applications"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Notifications"</string> <string name="menu_gestures" msgid="514678675575912237">"Gestes"</string> <string name="menu_settings" msgid="6233960148378443661">"Paramètres"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Désinstaller l\'application"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Infos sur l\'application"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1 application sélectionnée"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1 widget sélectionné"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 dossier sélectionné"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 raccourci sélectionné"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"installer des raccourcis"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"désinstaller les raccourcis"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"Enregistrer les paramètres de la page d\'accueil et des raccourcis"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Permet à une application de modifier les paramètres et les raccourcis de la page d\'accueil."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Problème lors du chargement du widget"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Il s\'agit d\'une application système que vous ne pouvez pas désinstaller."</string> </resources> diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 9f8db44..7705763 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Sfondi"</string> <string name="activity_not_found" msgid="3571057450431950427">"Applicazione non installata sul telefono."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Configura..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widget"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Cartelle"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Altro"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Sfondi"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Scorciatoie applicazioni"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Questa sarà la scheda degli sfondi"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Tutte"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Applicazioni"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Giochi"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Scaricate"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Nome cartella"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Rinomina cartella"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Seleziona scorciatoia"</string> <string name="title_select_application" msgid="8031072293115454221">"Seleziona applicazione"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Seleziona cartella"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Tutte le applicazioni"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Applicazioni"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Home"</string> <string name="menu_add" msgid="3065046628354640854">"Aggiungi"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Gestisci applicazioni"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Notifiche"</string> <string name="menu_gestures" msgid="514678675575912237">"Gesti"</string> <string name="menu_settings" msgid="6233960148378443661">"Impostazioni"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Disinstalla applicazione"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Dettagli applicazione"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1 applicazione selezionata"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1 widget selezionato"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 cartella selezionata"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 scorciatoia selezionata"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"aggiungere scorciatoie"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Consente a un\'applicazione di aggiungere scorciatoie automaticamente."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"eliminare scorciatoie"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"creare impostazioni e scorciatoie in Home"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Consente a un\'applicazione di modificare le impostazioni e le scorciatoie in Home."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Errore durante il caricamento del widget"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Questa è un\'applicazione di sistema e non può essere disinstallata."</string> </resources> diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index b401b8b..2a7a218 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"壁紙"</string> <string name="activity_not_found" msgid="3571057450431950427">"アプリケーションがインストールされていません。"</string> <string name="configure_wallpaper" msgid="2820186271419674623">"設定..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"ウィジェット"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"フォルダ"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"その他"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"壁紙"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"アプリのショートカット"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"ここが壁紙タブになります"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"すべて"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"アプリ"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"ゲーム"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"ダウンロード済み"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"フォルダ名"</string> <string name="rename_folder_title" msgid="4544573104191526550">"フォルダ名を変更"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"ショートカットを選択"</string> <string name="title_select_application" msgid="8031072293115454221">"アプリを選択"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"フォルダの選択"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"すべてのアプリケーション"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"アプリ"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"ホーム"</string> <string name="menu_add" msgid="3065046628354640854">"追加"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"アプリの管理"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"通知"</string> <string name="menu_gestures" msgid="514678675575912237">"ジェスチャー"</string> <string name="menu_settings" msgid="6233960148378443661">"設定"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"アプリケーションのアンインストール"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"アプリケーションの詳細"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1つのアプリケーションが選択されています"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1つのウィジェットが選択されています"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1つのフォルダが選択されています"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1つのショートカットが選択されています"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"ショートカットのインストール"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"ユーザー操作なしでショートカットの追加をアプリケーションに許可します。"</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"ショートカットのアンインストール"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"ホームの設定とショートカットの書き込み"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"ホームの設定とショートカットの変更をアプリケーションに許可します。"</string> <string name="gadget_error_text" msgid="8359351016167075858">"ウィジェットを表示できません"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"このシステムアプリケーションはアンインストールできません。"</string> </resources> diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 8787589..b23450d 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"배경화면"</string> <string name="activity_not_found" msgid="3571057450431950427">"휴대전화에 설치되어 있지 않은 애플리케이션입니다."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"구성..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"위젯"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"폴더"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"더보기"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"배경화면"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"앱 바로가기"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"배경화면 탭이 됩니다."</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"모두"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"애플리케이션"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"게임"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"다운로드앱"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"폴더 이름"</string> <string name="rename_folder_title" msgid="4544573104191526550">"폴더 이름 바꾸기"</string> <string name="rename_action" msgid="6016003384693240896">"확인"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"바로가기 선택"</string> <string name="title_select_application" msgid="8031072293115454221">"애플리케이션 선택"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"폴더 선택"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"모든 애플리케이션"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"애플리케이션"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"홈"</string> <string name="menu_add" msgid="3065046628354640854">"추가"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"애플리케이션 관리"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"알림"</string> <string name="menu_gestures" msgid="514678675575912237">"동작"</string> <string name="menu_settings" msgid="6233960148378443661">"설정"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"애플리케이션 제거"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"애플리케이션 세부정보"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1개 애플리케이션 선택"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1개 위젯 선택"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1개 폴더 선택"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1개 바로가기 선택"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"바로가기 설치"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"애플리케이션이 사용자의 작업 없이 바로가기를 추가할 수 있도록 합니다."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"바로가기 제거"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"홈 설정 및 바로가기 쓰기"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"애플리케이션이 홈에 있는 설정 및 바로가기를 변경할 수 있도록 합니다."</string> <string name="gadget_error_text" msgid="8359351016167075858">"위젯을 로드하는 중 문제가 발생했습니다."</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"시스템 애플리케이션은 제거할 수 없습니다."</string> </resources> diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 201d2d0..5026744 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Bakgrunner"</string> <string name="activity_not_found" msgid="3571057450431950427">"Applikasjonen er ikke installert."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Konfigurer"</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Moduler"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Mapper"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Mer"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Bakgrunner"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Programsnarveier"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Dette er nå bakgrunnsfanen"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Alle"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Programmer"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Spill"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Nedlastet"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Mappenavn"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Gi nytt navn til mappe"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Velg snarvei"</string> <string name="title_select_application" msgid="8031072293115454221">"Velg program"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Velg mappe"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Alle programmer"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Programmer"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Startsiden"</string> <string name="menu_add" msgid="3065046628354640854">"Legg til"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Administrer programmer"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Varslinger"</string> <string name="menu_gestures" msgid="514678675575912237">"Bevegelser"</string> <string name="menu_settings" msgid="6233960148378443661">"Innstillinger"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Avinstaller program"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Søknadsopplysninger"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1 program valgt"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1 modul valgt"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 mappe valgt"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 snarvei valgt"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"installere snarveier"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Lar applikasjonen legge til snarveier uten å involvere brukeren."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"avinstallere snarveier"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"skrive skrivebordsinnstillinger og -snarveier"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Lar applikasjonen endre innstillinger og snarveier på skrivebordet."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Problem under lasting av gadget"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Dette er et systemprogram og kan ikke avinstalleres."</string> </resources> diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 389e893..6562529 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Achtergronden"</string> <string name="activity_not_found" msgid="3571057450431950427">"Deze toepassing is niet geïnstalleerd op uw telefoon."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Configureren..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgets"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Mappen"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Meer"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Achtergronden"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Toepassingssnelkoppelingen"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Dit wordt het tabblad \'Achtergronden\'"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Alle"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Toepassingen"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Games"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Gedownload"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Mapnaam"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Naam van map wijzigen"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Snelkoppeling selecteren"</string> <string name="title_select_application" msgid="8031072293115454221">"Toepassing selecteren"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Map selecteren"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Alle toepassingen"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Toepassingen"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Startpagina"</string> <string name="menu_add" msgid="3065046628354640854">"Toevoegen"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Toepassingen beheren"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Meldingen"</string> <string name="menu_gestures" msgid="514678675575912237">"Gebaren"</string> <string name="menu_settings" msgid="6233960148378443661">"Instellingen"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Toepassing verwijderen"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Toepassingsdetails"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1 toepassing geselecteerd"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1 widget geselecteerd"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 map geselecteerd"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 snelkoppeling geselecteerd"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"snelkoppelingen installeren"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Een toepassing toestaan om snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"snelkoppelingen verwijderen"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"instellingen en snelkoppelingen voor de startpagina schrijven"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Hiermee kan een toepassing de instellingen en snelkoppelingen op de startpagina wijzigen."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Probleem bij het laden van widget"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Dit is een systeemtoepassing die niet kan worden verwijderd."</string> </resources> diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 019a016..f2ac81d 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Tapety"</string> <string name="activity_not_found" msgid="3571057450431950427">"Aplikacja nie jest zainstalowana w telefonie."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Konfiguruj..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widżety"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Foldery"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Więcej"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Tapety"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Skróty do aplikacji"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"To będzie karta tapet"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Wszystkie"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Aplikacje"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Gry"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Pobrane"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Nazwa folderu"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Zmień nazwę folderu"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Wybierz skrót"</string> <string name="title_select_application" msgid="8031072293115454221">"Wybierz aplikację"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Wybierz folder"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Wszystkie aplikacje"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Aplikacje"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Ekran główny"</string> <string name="menu_add" msgid="3065046628354640854">"Dodaj"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Zarządzaj aplikacjami"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Powiadomienia"</string> <string name="menu_gestures" msgid="514678675575912237">"Gesty"</string> <string name="menu_settings" msgid="6233960148378443661">"Ustawienia"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Odinstaluj aplikację"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Szczegóły aplikacji"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"Zaznaczono 1 aplikację"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"Zaznaczono 1 widżet"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"Zaznaczono 1 folder"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"Zaznaczono 1 skrót"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"zainstaluj skróty"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Umożliwia aplikacji dodawanie skrótów bez interwencji użytkownika."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"odinstaluj skróty"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"zapisywanie ustawień i skrótów strony głównej"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Umożliwia aplikacji zmianę ustawień i skrótów strony głównej."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Problem podczas ładowania widżetu"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"To jest aplikacja systemowa i nie można jej odinstalować."</string> </resources> diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 73bf895..a3c432d 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Imagens de fundo"</string> <string name="activity_not_found" msgid="3571057450431950427">"A aplicação não está instalada no telefone."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Configurar..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgets"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Pastas"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Mais"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Imagens de fundo"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Atalhos de aplicações"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Isto será o separador de imagens de fundo"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Todas"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Aplicações"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Jogos"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Transferidas"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Nome da pasta"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Mudar o nome da pasta"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Seleccione o atalho"</string> <string name="title_select_application" msgid="8031072293115454221">"Seleccionar aplicação"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Seleccione a pasta"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Todas as aplicações"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Aplicações"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Página inicial"</string> <string name="menu_add" msgid="3065046628354640854">"Adicionar"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Gerir aplicações"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Notificações"</string> <string name="menu_gestures" msgid="514678675575912237">"Gestos"</string> <string name="menu_settings" msgid="6233960148378443661">"Definições"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Desinstalar aplicação"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Detalhes da aplicação"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1 aplicação seleccionada"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1 widget seleccionado"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 pasta seleccionada"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 atalho seleccionado"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"instalar atalhos"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Permite que uma aplicação adicione atalhos sem a intervenção do utilizador."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"desinstalar atalhos"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"escrever definições e atalhos do ecrã principal"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Permite que uma aplicação altere as definições e os atalhos do ecrã principal."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Erro ao carregar o widget"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"É uma aplicação de sistema e não pode ser desinstalada."</string> </resources> diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index c9cc6a9..3941c4f 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Papéis de parede"</string> <string name="activity_not_found" msgid="3571057450431950427">"O aplicativo não está instalado no seu telefone."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Configurar..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgets"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Pastas"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Mais"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Papéis de parede"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Atalhos de aplicativo"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Esta será a guia de papéis de parede"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Todos"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Aplicativos"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Jogos"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Download concluído"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Nome da pasta"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Renomear pasta"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Selecionar atalho"</string> <string name="title_select_application" msgid="8031072293115454221">"Selecionar aplicativo"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Selecionar pasta"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Todos os aplicativos"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Aplicativos"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Página inicial"</string> <string name="menu_add" msgid="3065046628354640854">"Adicionar"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Gerenciar aplicativos"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Notificações"</string> <string name="menu_gestures" msgid="514678675575912237">"Gestos"</string> <string name="menu_settings" msgid="6233960148378443661">"Configurações"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Desinstalar aplicativo"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Detalhes do aplicativo"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"Um aplicativo selecionado"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"Um widget selecionado"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"Uma pasta selecionada"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"Um atalho selecionado"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"instalar atalhos"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Permite que um aplicativo adicione os atalhos sem a intervenção do usuário."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"desinstalar atalhos"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"gravar configurações e atalhos da Página inicial"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Permite que um aplicativo altere as configurações e atalhos na Página inicial."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Problema ao carregar o widget"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Este é um aplicativo do sistema e não pode ser desinstalado."</string> </resources> diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml new file mode 100644 index 0000000..10cbc4a --- /dev/null +++ b/res/values-rm/strings.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/* +* Copyright (C) 2008 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="application_name" msgid="8424725141379931883">"Lantschader"</string> + <string name="uid_name" msgid="3371120195364560632">"Applicaziuns da basa dad Android"</string> + <string name="folder_name" msgid="4588446541914685904">"Ordinatur"</string> + <string name="chooser_wallpaper" msgid="5988031014201479733">"Tscherner in fund davos"</string> + <string name="wallpaper_instructions" msgid="4215640646180727542">"Definir in fund davos"</string> + <string name="pick_wallpaper" msgid="5630222540525626723">"Maletgs da fund davos"</string> + <string name="activity_not_found" msgid="3571057450431950427">"L\'applicaziun n\'è betg installada sin quest telefonin."</string> + <string name="configure_wallpaper" msgid="2820186271419674623">"Configurar…"</string> + <!-- no translation found for widgets_tab_label (9145860100000983599) --> + <skip /> + <!-- no translation found for folders_tab_label (1145293785541489736) --> + <skip /> + <!-- no translation found for shortcuts_tab_label (8640731503933155644) --> + <skip /> + <!-- no translation found for wallpapers_tab_label (1617804870364119879) --> + <skip /> + <!-- no translation found for applications_tab_label (9046797126882613707) --> + <skip /> + <!-- no translation found for wallpapers_temp_tab_text (1660218201190495279) --> + <skip /> + <!-- no translation found for all_apps_tab_all (2942727589595027258) --> + <skip /> + <!-- no translation found for all_apps_tab_apps (5468972551904071712) --> + <skip /> + <!-- no translation found for all_apps_tab_games (1855736784923494918) --> + <skip /> + <!-- no translation found for all_apps_tab_downloaded (2300935549064726963) --> + <skip /> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> + <string name="rename_folder_label" msgid="5646236631298452787">"Num da l\'ordinatur"</string> + <string name="rename_folder_title" msgid="4544573104191526550">"Renumnar l\'ordinatur"</string> + <string name="rename_action" msgid="6016003384693240896">"OK"</string> + <string name="cancel_action" msgid="3811860427489435048">"Interrumper"</string> + <string name="menu_item_add_item" msgid="6233177331075781114">"Agiuntar al visur da partenza"</string> + <string name="group_applications" msgid="4118484163419674240">"Applicaziuns"</string> + <string name="group_shortcuts" msgid="9133529424900391877">"Scursanidas"</string> + <string name="group_folder" msgid="5143593791798929193">"Nov ordinatur"</string> + <string name="group_live_folders" msgid="2664945399140647217">"Ordinatur"</string> + <string name="group_widgets" msgid="6704978494073105844">"Widgets"</string> + <string name="group_wallpapers" msgid="1568191644272224858">"Funds davos"</string> + <string name="add_folder" msgid="3521088587367839879">"Ordinatur"</string> + <string name="add_clock" msgid="2337943840175865746">"Ura"</string> + <string name="add_photo_frame" msgid="3154058437359487954">"Rom da maletgs"</string> + <string name="out_of_space" msgid="8365249326091984698">"Nagin spazi liber sin il visur da partenza."</string> + <string name="shortcut_installed" msgid="7071557296331322355">"\"Creà ina scursanida \"\"<xliff:g id="NAME">%s</xliff:g>\"\".\""</string> + <string name="shortcut_uninstalled" msgid="2129499669449749995">"\"La scursanida \"\"<xliff:g id="NAME">%s</xliff:g>\"\" è vegnida stizzada.\""</string> + <string name="shortcut_duplicate" msgid="4757756326465060694">"\"La scursanida \"\"<xliff:g id="NAME">%s</xliff:g>\"\" exista gia.\""</string> + <string name="title_select_shortcut" msgid="2858897527672831763">"Tscherner ina cumbinaziun da tastas"</string> + <!-- no translation found for title_select_application (8031072293115454221) --> + <skip /> + <string name="title_select_live_folder" msgid="3753447798805166749">"Tscherner l\'ordinatur"</string> + <!-- outdated translation 3953036962111614813 --> <string name="all_apps_button_label" msgid="2578400570124163469">"Tut las applicaziuns"</string> + <string name="all_apps_home_button_label" msgid="1022222300329398558">"Pagina da partenza"</string> + <string name="menu_add" msgid="3065046628354640854">"Agiuntar"</string> + <!-- no translation found for menu_manage_apps (2308685199463588895) --> + <skip /> + <string name="menu_wallpaper" msgid="5837429080911269832">"Fund davos"</string> + <string name="menu_search" msgid="4826514464423239041">"Tschertgar"</string> + <string name="menu_notifications" msgid="6424587053194766192">"Avis"</string> + <string name="menu_gestures" msgid="514678675575912237">"Moviments"</string> + <string name="menu_settings" msgid="6233960148378443661">"Parameters"</string> + <!-- no translation found for cab_menu_delete_app (1242619904941293871) --> + <skip /> + <!-- no translation found for cab_menu_app_info (5180426909324882018) --> + <skip /> + <!-- no translation found for cab_app_selection_text (606113924828167756) --> + <skip /> + <!-- no translation found for cab_widget_selection_text (962527270506951955) --> + <skip /> + <!-- no translation found for cab_folder_selection_text (8916111874189565067) --> + <skip /> + <!-- no translation found for cab_shortcut_selection_text (8115847384500412878) --> + <skip /> + <string name="permlab_install_shortcut" msgid="1201690825493376489">"Installar scursanidas"</string> + <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Pussibilitescha ch\'ina applicaziun agiunta scursanidas senza l\'intervenziun da l\'utilisader."</string> + <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"deinstallar scursanidas"</string> + <string name="permdesc_uninstall_shortcut" msgid="959972195916090900">"Pussibilitescha ch\'ina applicaziun possia stizzar scursanidas senza ina intervenziun da l\'utilisader."</string> + <string name="permlab_read_settings" msgid="3452408290738106747">"Leger ils parameters e las scursanidas da la pagina da partenza"</string> + <string name="permdesc_read_settings" msgid="8377434937176025492">"Pussibilitescha ch\'ina applicaziun possia leger ils parameters e las scursanidas da la pagina da partenza."</string> + <string name="permlab_write_settings" msgid="1360567537236705628">"Definir ils parameters e las scursanidas per la pagina da partenza"</string> + <string name="permdesc_write_settings" msgid="1098648778383349818">"Pussibilitescha ch\'ina applicaziun possia midar ils parameters e las scursanidas sin la pagina da partenza."</string> + <string name="gadget_error_text" msgid="8359351016167075858">"Problems cun chargiar il widget"</string> + <!-- no translation found for uninstall_system_app_text (7488523163288397451) --> + <skip /> +</resources> diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index b595cf6..fd5b35d 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Обои"</string> <string name="activity_not_found" msgid="3571057450431950427">"Приложение не установлено на телефоне."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Настроить..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Виджеты"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Папки"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Еще"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Обои"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Ярлыки приложений"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Это будет вкладка обоев"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Все"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Приложения"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Игры"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Загруженные"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Название папки"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Переименовать папку"</string> <string name="rename_action" msgid="6016003384693240896">"ОК"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Выберите ярлык"</string> <string name="title_select_application" msgid="8031072293115454221">"Выбор приложения"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Выбор папки"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Все приложения"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Приложения"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Главная"</string> <string name="menu_add" msgid="3065046628354640854">"Добавить"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Управление приложениями"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Уведомления"</string> <string name="menu_gestures" msgid="514678675575912237">"Жесты"</string> <string name="menu_settings" msgid="6233960148378443661">"Настройки"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Удалить приложение"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Сведения о приложении"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"Выбрано 1 приложение"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"Выбран 1 виджет"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"Выбрана 1 папка"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"Выбран 1 ярлык"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"устанавливать ярлыки"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Позволяет приложению добавлять ярлыки без вмешательства пользователя"</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"удалять ярлыки"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"изменять настройки и ярлыки главного экрана"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Позволяет приложению изменять настройки и ярлыки на главном экране."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Не удалось загрузить виджет"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Это системное приложение. Удалить его невозможно."</string> </resources> diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 079a141..17a6cdc 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Bakgrundsbilder"</string> <string name="activity_not_found" msgid="3571057450431950427">"Programmet är inte installerat på din telefon."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Konfigurera..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widgetar"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Mappar"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Mer"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Bakgrundsbilder"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Genvägar för appar"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Det här kommer att vara fliken för bakgrundsbilder"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Alla"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Program"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Spel"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"Hämtade"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Mappnamn"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Byt namn på mapp"</string> <string name="rename_action" msgid="6016003384693240896">"OK"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Välj genväg"</string> <string name="title_select_application" msgid="8031072293115454221">"Välj program"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Välj mapp"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Alla program"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Program"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Startsida"</string> <string name="menu_add" msgid="3065046628354640854">"Lägg till"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Hantera appar"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Aviseringar"</string> <string name="menu_gestures" msgid="514678675575912237">"Gester"</string> <string name="menu_settings" msgid="6233960148378443661">"Inställningar"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Avinstallera program"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Programinformation"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1 program har valts"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1 widget vald"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 mapp vald"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 genväg har valts"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"installera genvägar"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Tillåter att ett program lägger till genvägar utan åtgärd från användaren."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"avinstallera genvägar"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"skriva inställningar och genvägar för startsidan"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Tillåter att ett program ändrar inställningar och genvägar på startsidan."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Det gick inte att läsa in widgeten"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Det här är ett systemprogram och kan inte avinstalleras."</string> </resources> diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 3e23c32..19417f0 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"Duvar Kağıtları"</string> <string name="activity_not_found" msgid="3571057450431950427">"Uygulama telefonunuza yüklenmemiş."</string> <string name="configure_wallpaper" msgid="2820186271419674623">"Yapılandır..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"Widget\'lar"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"Klasörler"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"Diğer"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"Duvar Kağıtları"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"Uygulama Kısayolları"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"Bu duvar kağıdı sekmesi olacaktır"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"Tümü"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"Uygulamalar"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"Oyunlar"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"İndirilenler"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"Klasör adı"</string> <string name="rename_folder_title" msgid="4544573104191526550">"Klasörü yeniden adlandır"</string> <string name="rename_action" msgid="6016003384693240896">"Tamam"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"Kısayolu seçin"</string> <string name="title_select_application" msgid="8031072293115454221">"Uygulama seç"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"Klasörü seçin"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"Tüm uygulamalar"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"Uygulamalar"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"Ana Sayfa"</string> <string name="menu_add" msgid="3065046628354640854">"Ekle"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"Uyglm yönet"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"Bildirimler"</string> <string name="menu_gestures" msgid="514678675575912237">"Hareketler"</string> <string name="menu_settings" msgid="6233960148378443661">"Ayarlar"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"Uygulamanın yüklemesini kaldır"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"Uygulama ayrıntıları"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"1 uygulama seçildi"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"1 widget seçildi"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 klasör seçildi"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 kısayol seçildi"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"kısayolları yükle"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"Bir uygulamaya, kısayolları kullanıcı müdahale etmeden ekleme izni verir."</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"kısayolları kaldır"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"Ana Sayfa ayarlarını ve kısayollarını yaz"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"Bir uygulamaya Ana Sayfadaki ayarları ve kısayolları değiştirme izni verir."</string> <string name="gadget_error_text" msgid="8359351016167075858">"Widget yüklenirken sorun oluştu"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"Bu bir sistem uygulamasıdır ve kaldırılamaz."</string> </resources> diff --git a/res/values-xlarge-land/dimens.xml b/res/values-xlarge-land/dimens.xml new file mode 100644 index 0000000..db31cf1 --- /dev/null +++ b/res/values-xlarge-land/dimens.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <!-- the area at the edge of the screen that makes the workspace go left + or right while you're dragging. --> + <dimen name="scroll_zone">160dip</dimen> + + <!-- Width/height gap overrides for the workspace --> + <dimen name="workspace_width_gap">16dp</dimen> + <dimen name="workspace_height_gap">5dp</dimen> +</resources>
\ No newline at end of file diff --git a/res/values-xlarge-port/dimens.xml b/res/values-xlarge-port/dimens.xml new file mode 100644 index 0000000..baa31aa --- /dev/null +++ b/res/values-xlarge-port/dimens.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <!-- the area at the edge of the screen that makes the workspace go left + or right while you're dragging. --> + <dimen name="scroll_zone">40dp</dimen> + + <!-- Width/height gap overrides for the workspace --> + <dimen name="workspace_width_gap">0dp</dimen> + <dimen name="workspace_height_gap">32dp</dimen> +</resources>
\ No newline at end of file diff --git a/res/values-xlarge/config.xml b/res/values-xlarge/config.xml new file mode 100644 index 0000000..6a56679 --- /dev/null +++ b/res/values-xlarge/config.xml @@ -0,0 +1,48 @@ +<resources> + <!-- NOTE: Many of the all apps values here are also used for the customization drawer --> + + <!-- Duration in milliseconds of the all apps zoom-in animation. --> + <!-- NB: This should be less than the workspaceShrinkTime as they happen together. --> + <integer name="config_allAppsZoomInTime">350</integer> + + <!-- Duration in milliseconds of the all apps zoom-out animation --> + <!-- NB: This should be less than the workspaceUnshrinkTime as they happen together. --> + <integer name="config_allAppsZoomOutTime">350</integer> + + <!-- Scaling factor used in the all apps zooming animations --> + <integer name="config_allAppsZoomScaleFactor">5</integer> + + <!-- The extra distance off-screen that all apps appears from --> + <integer name="config_allAppsVerticalOffset">200</integer> + + <!-- Duration in milliseconds of the animations between all apps, customize, & home. + NOTE: If these are changed, the toolbar animation times below should also be. --> + <integer name="config_allAppsCameraPanTime">700</integer> + <integer name="config_workspaceShrinkTime">700</integer> + <integer name="config_workspaceUnshrinkTime">700</integer> + + <!-- Duration in milliseconds toolbar fade in and fade out animations. + NOTE: Fade in and fade out time should together be less the transition + animations between all apps, customize, & the workspace. --> + <integer name="config_toolbarButtonFadeInTime">350</integer> + <integer name="config_toolbarButtonFadeOutTime">350</integer> + + <!-- When dragging items on the workspace, how much bigger (in pixels) the dragged view + should be, as compared to the original view. If 0, it will not be scaled at all. + Should be an even number, for pixel alignment. --> + <integer name="config_dragViewExtraPixels">0</integer> + + <!-- When dragging items on the workspace, the number of pixels by which the position of + the drag view should be offset from the position of the original view. --> + <integer name="config_dragViewOffsetX">0</integer> + <integer name="config_dragViewOffsetY">12</integer> + + <!-- When items are dropped on the mini screens in customize mode, we have a bounce animation + of the bright green hover outline, and then fade out the outline at the end. These are + the values used in that animation --> + <integer name="config_screenOnDropScalePercent">120</integer> + <integer name="config_screenOnDropScaleUpDuration">200</integer> + <integer name="config_screenOnDropScaleDownDuration">200</integer> + <integer name="config_screenOnDropAlphaFadeDelay">350</integer> + <integer name="config_screenOnDropAlphaFadeDuration">50</integer> +</resources> diff --git a/res/values-xlarge/dimens.xml b/res/values-xlarge/dimens.xml new file mode 100644 index 0000000..667f0e3 --- /dev/null +++ b/res/values-xlarge/dimens.xml @@ -0,0 +1,48 @@ +<?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> + <dimen name="workspace_cell_width">96dip</dimen> + <dimen name="workspace_cell_height">96dip</dimen> + + <!-- Width/height gap overrides for the workspace --> + <dimen name="workspace_width_gap">0dp</dimen> + <dimen name="workspace_height_gap">0dp</dimen> + + <!-- The corner radius to draw the external drop icon rounded rect --> + <dimen name="external_drop_icon_rect_radius">10dp</dimen> + + <!-- Size of icons in workspace --> + <dimen name="app_icon_size">72dp</dimen> + + <!-- extra horizontal spacing between mini screen thumbnails ie. in all + apps and in customization mode --> + <dimen name="smallScreenExtraSpacing">0dip</dimen> + + <!-- Vertical spacing between edge of screen and mini cell layouts when they + are minimized to the bottom in all apps --> + <dimen name="allAppsSmallScreenVerticalMarginLandscape">30dip</dimen> + <dimen name="allAppsSmallScreenVerticalMarginPortrait">60dip</dimen> + + <!-- Vertical spacing between edge of screen and mini cell layouts when they + are minimized to the top when the customization drawer is showing --> + <dimen name="customizeSmallScreenVerticalMarginLandscape">90dip</dimen> + <dimen name="customizeSmallScreenVerticalMarginPortrait">180dip</dimen> + + <dimen name="toolbar_padding">10dip</dimen> + + <dimen name="toolbar_button_spacing">20dip</dimen> +</resources> diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml new file mode 100644 index 0000000..83cdbcc --- /dev/null +++ b/res/values-xlarge/styles.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* Copyright (C) 2008 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +--> + +<resources> + <style name="Theme" parent="android:Theme.Holo"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowActionModeOverlay">true</item> + + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:colorBackgroundCacheHint">@null</item> + <item name="android:windowShowWallpaper">true</item> + </style> + + <style name="WorkspaceIcon.Portrait"> + <item name="android:drawablePadding">0dip</item> + <item name="android:paddingTop">0dip</item> + </style> + + <style name="WorkspaceIcon.Landscape"> + <item name="android:drawablePadding">0dip</item> + <item name="android:paddingTop">0dip</item> + </style> +</resources> diff --git a/res/values-xlarge/wallpapers.xml b/res/values-xlarge/wallpapers.xml new file mode 100644 index 0000000..f22654d --- /dev/null +++ b/res/values-xlarge/wallpapers.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<resources> + <string-array name="wallpapers" translatable="false"> + <item>glow_wallpaper</item> + </string-array> +</resources> diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 41ffac3..751474b 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"壁纸"</string> <string name="activity_not_found" msgid="3571057450431950427">"您的手机上未安装应用程序。"</string> <string name="configure_wallpaper" msgid="2820186271419674623">"配置..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"窗口小部件"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"文件夹"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"更多"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"壁纸"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"应用程序快捷方式"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"这将会成为壁纸标签"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"全部"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"应用程序"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"游戏"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"已下载"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"文件夹名称"</string> <string name="rename_folder_title" msgid="4544573104191526550">"重命名文件夹"</string> <string name="rename_action" msgid="6016003384693240896">"确定"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"选择快捷方式"</string> <string name="title_select_application" msgid="8031072293115454221">"选择应用程序"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"选择文件夹"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"所有应用程序"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"应用程序"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"主屏幕"</string> <string name="menu_add" msgid="3065046628354640854">"添加"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"管理应用程序"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"通知"</string> <string name="menu_gestures" msgid="514678675575912237">"手势"</string> <string name="menu_settings" msgid="6233960148378443661">"设置"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"卸载应用程序"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"应用程序详细信息"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"已选中 1 个应用程序"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"已选中 1 个窗口小部件"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"已选中 1 个文件夹"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"已选中 1 个快捷方式"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"安装快捷方式"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"允许应用程序在没有用户介入的情况下添加快捷方式。"</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"卸载快捷方式"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"写入主屏幕的设置和快捷方式"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"允许应用程序更改主屏幕的设置和快捷方式。"</string> <string name="gadget_error_text" msgid="8359351016167075858">"载入窗口小部件时出现问题"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"这是系统应用程序,无法卸载。"</string> </resources> diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 9b01727..824ec64 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -27,6 +27,20 @@ <string name="pick_wallpaper" msgid="5630222540525626723">"桌布"</string> <string name="activity_not_found" msgid="3571057450431950427">"應用程式未安裝到手機。"</string> <string name="configure_wallpaper" msgid="2820186271419674623">"設定..."</string> + <string name="widgets_tab_label" msgid="9145860100000983599">"小工具"</string> + <string name="folders_tab_label" msgid="1145293785541489736">"資料夾"</string> + <string name="shortcuts_tab_label" msgid="8640731503933155644">"更多選項"</string> + <string name="wallpapers_tab_label" msgid="1617804870364119879">"桌布"</string> + <string name="applications_tab_label" msgid="9046797126882613707">"應用程式捷徑"</string> + <string name="wallpapers_temp_tab_text" msgid="1660218201190495279">"桌布標籤保留位"</string> + <string name="all_apps_tab_all" msgid="2942727589595027258">"全部"</string> + <string name="all_apps_tab_apps" msgid="5468972551904071712">"應用程式"</string> + <string name="all_apps_tab_games" msgid="1855736784923494918">"遊戲"</string> + <string name="all_apps_tab_downloaded" msgid="2300935549064726963">"下載內容"</string> + <!-- no translation found for all_apps_no_games (5293893733372793696) --> + <skip /> + <!-- no translation found for all_apps_no_downloads (6145042636084482299) --> + <skip /> <string name="rename_folder_label" msgid="5646236631298452787">"資料夾名稱"</string> <string name="rename_folder_title" msgid="4544573104191526550">"重新命名資料夾"</string> <string name="rename_action" msgid="6016003384693240896">"確定"</string> @@ -48,7 +62,7 @@ <string name="title_select_shortcut" msgid="2858897527672831763">"選取捷徑"</string> <string name="title_select_application" msgid="8031072293115454221">"選取應用程式"</string> <string name="title_select_live_folder" msgid="3753447798805166749">"選取資料夾"</string> - <string name="all_apps_button_label" msgid="3953036962111614813">"所有應用程式"</string> + <string name="all_apps_button_label" msgid="2578400570124163469">"應用程式"</string> <string name="all_apps_home_button_label" msgid="1022222300329398558">"主螢幕"</string> <string name="menu_add" msgid="3065046628354640854">"新增"</string> <string name="menu_manage_apps" msgid="2308685199463588895">"管理應用程式"</string> @@ -57,6 +71,12 @@ <string name="menu_notifications" msgid="6424587053194766192">"通知"</string> <string name="menu_gestures" msgid="514678675575912237">"觸控動作"</string> <string name="menu_settings" msgid="6233960148378443661">"設定"</string> + <string name="cab_menu_delete_app" msgid="1242619904941293871">"解除安裝應用程式"</string> + <string name="cab_menu_app_info" msgid="5180426909324882018">"應用程式詳細資料"</string> + <string name="cab_app_selection_text" msgid="606113924828167756">"已選取 1 個應用程式"</string> + <string name="cab_widget_selection_text" msgid="962527270506951955">"已選取 1 個小工具"</string> + <string name="cab_folder_selection_text" msgid="8916111874189565067">"已選取 1 個資料夾"</string> + <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"已選取 1 個捷徑"</string> <string name="permlab_install_shortcut" msgid="1201690825493376489">"安裝捷徑"</string> <string name="permdesc_install_shortcut" msgid="7429365847558984148">"允許應用程式自動新增快速鍵。"</string> <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"解除安裝捷徑"</string> @@ -66,4 +86,5 @@ <string name="permlab_write_settings" msgid="1360567537236705628">"寫入首頁設定和捷徑"</string> <string name="permdesc_write_settings" msgid="1098648778383349818">"允許應用程式變更首頁中的設定和捷徑。"</string> <string name="gadget_error_text" msgid="8359351016167075858">"載入小工具時發生問題"</string> + <string name="uninstall_system_app_text" msgid="7488523163288397451">"這是系統應用程式,無法將其解除安裝。"</string> </resources> diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 7839120..5d6773c 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -32,6 +32,10 @@ <declare-styleable name="Workspace"> <!-- The first screen the workspace should display. --> <attr name="defaultScreen" format="integer" /> + <!-- The number of horizontal cells in the CellLayout --> + <attr name="cellCountX" format="integer" /> + <!-- The number of vertical cells in the CellLayout --> + <attr name="cellCountY" format="integer" /> </declare-styleable> <!-- CellLayout specific attributes. These attributes are used to customize @@ -42,17 +46,67 @@ <!-- The height of a single cell --> <attr name="cellHeight" format="dimension" /> <!-- Padding to apply at the start of the long axis --> - <attr name="longAxisStartPadding" format="dimension" /> + <attr name="xAxisStartPadding" format="dimension" /> <!-- Padding to apply at the end of the long axis --> - <attr name="longAxisEndPadding" format="dimension" /> + <attr name="xAxisEndPadding" format="dimension" /> <!-- Padding to apply at the start of the short axis --> - <attr name="shortAxisStartPadding" format="dimension" /> + <attr name="yAxisStartPadding" format="dimension" /> <!-- Padding to apply at the end of the short axis --> - <attr name="shortAxisEndPadding" format="dimension" /> - <!-- Number of cells on the short axis of the CellLayout --> - <attr name="shortAxisCells" format="integer" /> - <!-- Number of cells on the long axis of the CellLayout --> - <attr name="longAxisCells" format="integer" /> + <attr name="yAxisEndPadding" format="dimension" /> + <!-- An override for the width and height gap to allow users to specify + a specific size for the page using spacing instead of resolving the + spacing from the width of the page --> + <attr name="widthGap" format="dimension" /> + <attr name="heightGap" format="dimension" /> + + </declare-styleable> + + <!-- PagedViewIcon specific attributes. These attributes are used to customize + a PagedViewIcon view in XML files. --> + <declare-styleable name="PagedViewIcon"> + <!-- The blur color of the holographic outline --> + <attr name="blurColor" format="color" /> + <!-- The outline color of the holographic outline --> + <attr name="outlineColor" format="color" /> + <!-- The checked blur color of the holographic outline --> + <attr name="checkedBlurColor" format="color" /> + <!-- The checked outline color of the holographic outline --> + <attr name="checkedOutlineColor" format="color" /> + </declare-styleable> + + <!-- PagedViewWidgetIcon specific attributes. These attributes are used to + customize a PagedViewWidgetIcon view in XML files. --> + <declare-styleable name="PagedViewWidgetIcon"> + <!-- The checked blur color of the holographic outline --> + <attr name="checkedBlurColor" /> + <!-- The checked outline color of the holographic outline --> + <attr name="checkedOutlineColor" /> + </declare-styleable> + + <!-- PagedView specific attributes. These attributes are used to customize + a PagedView view in XML files. --> + <declare-styleable name="PagedView"> + <!-- The number of horizontal cells in a page --> + <attr name="cellCountX" /> + <!-- The number of vertical cells in a page --> + <attr name="cellCountY" /> + <!-- A spacing override for the icons within a page --> + <attr name="pageLayoutWidthGap" format="dimension" /> + <attr name="pageLayoutHeightGap" format="dimension" /> + <!-- The padding of the pages that are dynamically created per page --> + <attr name="pageLayoutPaddingTop" format="dimension" /> + <attr name="pageLayoutPaddingBottom" format="dimension" /> + <attr name="pageLayoutPaddingLeft" format="dimension" /> + <attr name="pageLayoutPaddingRight" format="dimension" /> + <!-- The space between adjacent pages of the PagedView. --> + <attr name="pageSpacing" format="dimension" /> + </declare-styleable> + + <!-- CustomizePagedView specific attributes. These attributes are used to customize + a CustomizePagedView view in XML files. --> + <declare-styleable name="CustomizePagedView"> + <!-- The number of horizontal cells for the widget tab --> + <attr name="widgetCellCountX" format="integer" /> </declare-styleable> <!-- DeleteZone specific attributes. These attributes are used to customize diff --git a/res/values/colors.xml b/res/values/colors.xml index 9b9700f..3e964c4 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -28,4 +28,9 @@ <color name="gesture_color">#ff0563c1</color> <color name="uncertain_gesture_color">#ff848484</color> + + <color name="app_info_filter">#A50000FE</color> + <color name="dimmed_view_color">#FF7F7F7F</color> + + <color name="drag_outline_color">#9DBE12</color> </resources> diff --git a/res/values/config.xml b/res/values/config.xml index 15ec05b..9350ad1 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -3,4 +3,33 @@ <integer name="config_allAppsFadeOutTime">700</integer> <integer name="config_allAppsBatchLoadDelay">0</integer> <integer name="config_allAppsBatchSize">0</integer> + <bool name="config_hardwareAccelerated">false</bool> + + <integer name="config_crosshairsFadeInTime">600</integer> + + <!-- When dragging an item on the workspace, how much bigger (in pixels) the dragged view + should be, as compared to the original view. If 0, it will not be scaled at all. + Should be an even number, for pixel alignment. --> + <integer name="config_dragViewExtraPixels">40</integer> + + <!-- When dragging items on the workspace, the number of pixels by which the position of + the drag view should be offset from the position of the original view. + Setting to 1/2 of config_dragViewExtraPixels keeps it centered on its old position. --> + <integer name="config_dragViewOffsetX">20</integer> + <integer name="config_dragViewOffsetY">20</integer> + + <!-- The duration (in ms) of the fade animation on the object outlines, used when + we are dragging objects around on the home screen. --> + <integer name="config_dragOutlineFadeTime">900</integer> + + <!-- The alpha value at which to show the most recent drop visualization outline. --> + <integer name="config_dragOutlineMaxAlpha">128</integer> + + <!-- Parameters controlling the animation for when an item is dropped on the home screen, + and it animates from its old position to the new one. --> + + <integer name="config_dropAnimMaxDuration">400</integer> + + <!-- The distance at which the animation should take the max duration --> + <integer name="config_dropAnimMaxDist">800</integer> </resources> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index c83986b..d6cd3ee 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -32,6 +32,9 @@ button cluster in landscape) --> <dimen name="half_status_bar_height">12dip</dimen> + <!-- Size of icons in workspace --> + <dimen name="app_icon_size">50dp</dimen> + <!-- height & width of the drop rectangle for the trash icon --> <dimen name="delete_zone_size">70dip</dimen> diff --git a/res/values/strings.xml b/res/values/strings.xml index a3df221..f26dfc8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -43,6 +43,46 @@ <string name="activity_not_found">Application is not installed on your phone.</string> <!-- List item for configuring the current wallpaper --> <string name="configure_wallpaper">Configure...</string> + <!-- Labels for the tabs in the customize drawer --> + <string name="widgets_tab_label">Widgets</string> + <!-- Title of tab containing all possible folders (ie standard Folders and Live Folders) that + can be added to the home screen --> + <string name="folders_tab_label">Folders</string> + <!-- Title of tab containing all possible shortcuts (eg Contacts, Bookmarks) that can be + added to the home screen --> + <string name="shortcuts_tab_label">More</string> + <!-- Title of tab for configuring wallpapers --> + <string name="wallpapers_tab_label">Wallpapers</string> + <!-- Title of tab for configuring applications --> + <string name="applications_tab_label">App Shortcuts</string> + <!-- Placeholder text, will be removed --> + <string name="wallpapers_temp_tab_text">This will be the wallpapers tab</string> + <!-- Labels for the tabs in All Apps --> + <!-- Title of the tab for all applications (includes games and non-games) [CHAR_LIMIT=12] --> + <string name="all_apps_tab_all">All</string> + <!-- Title of the tab for all applications *except* games [CHAR_LIMIT=12] --> + <string name="all_apps_tab_apps">Apps</string> + <!-- Title of the tab for applications labeled as games [CHAR_LIMIT=12] --> + <string name="all_apps_tab_games">Games</string> + <!-- Tile of the tab for applications that were downloaded from market [CHAR_LIMIT=12] --> + <string name="all_apps_tab_downloaded">Downloaded</string> + + <!-- All Apps pane --> + <!-- Message to show when there are no games [CHAR_LIMIT=25] --> + <string name="all_apps_no_games">No games found.</string> + <!-- Message to show when there are no downloaded apps [CHAR_LIMIT=25] --> + <string name="all_apps_no_downloads">No downloaded apps found.</string> + + <!-- Customization Drawer --> + <!-- The format string for the dimensions of a widget in the drawer --> + <string name="widget_dims_format" translatable="false">%1$d x %2$d</string> + + <!-- External-drop widget pick label format string [CHAR_LIMIT=25] --> + <string name="external_drop_widget_pick_format" translatable="false">%1$s (%2$d x %3$d)</string> + <!-- External-drop widget error string --> + <string name="external_drop_widget_error">Could not install clipboard item</string> + <!-- External-drop widget pick title --> + <string name="external_drop_widget_pick_title">Select widget to create</string> <!-- Folders --> <skip /> @@ -93,8 +133,8 @@ <!-- Title of dialog when user is selecting live folder to add to homescreen --> <string name="title_select_live_folder">Select folder</string> - <!-- All applications label for accessibilty (spoken when the button gets focus). --> - <string name="all_apps_button_label">All applications</string> + <!-- All applications label --> + <string name="all_apps_button_label">Apps</string> <!-- Label for button in all applications label to go back home (to the workspace / desktop) for accessibilty (spoken when the button gets focus). --> <string name="all_apps_home_button_label">Home</string> @@ -116,6 +156,23 @@ <!-- Noun, menu item used to show the system settings --> <string name="menu_settings">Settings</string> + <!-- Strings for the contextual action bar (CAB) in All Apps --> + <skip /> + <!-- Describes the button for uninstalling the currently selected application. + Text is not displayed, but provided for accessibility. [CHAR_LIMIT=none] --> + <string name="cab_menu_delete_app">Uninstall application</string> + <!-- Describes the button for getting details/info about currently selected application. + Text is not displayed, but provided for accessibility. [CHAR_LIMIT=none] --> + <string name="cab_menu_app_info">Application details</string> + <!-- Appears in the CAB when an app is selected in All Apps or Customize mode. [CHAR_LIMIT=50] --> + <string name="cab_app_selection_text">1 application selected</string> + <!-- Appears in the CAB when a widget is selected in Customize mode. [CHAR_LIMIT=50] --> + <string name="cab_widget_selection_text">1 widget selected</string> + <!-- Appears in the CAB when a folder is selected in Customize mode. [CHAR_LIMIT=50] --> + <string name="cab_folder_selection_text">1 folder selected</string> + <!-- Appears in the CAB when a shortcut is selected in Customize mode. [CHAR_LIMIT=50] --> + <string name="cab_shortcut_selection_text">1 shortcut selected</string> + <!-- Permissions: --> <skip /> <!-- Permission short label --> @@ -151,4 +208,6 @@ <string name="default_browser_url" translatable="false"> http://www.google.com/m?client=ms-{CID}&source=android-home-hotseat</string> + <!-- Text to inform the user that they can't uninstall a system application --> + <string name="uninstall_system_app_text">This is a system application and cannot be uninstalled.</string> </resources> diff --git a/res/values/styles.xml b/res/values/styles.xml index c208211..a97b3c5 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -18,7 +18,10 @@ --> <resources> - <style name="Theme" parent="android:Theme.Wallpaper"> + <style name="Theme.Base" parent="android:Theme.Wallpaper"> + </style> + + <style name="Theme" parent="Theme.Base"> <item name="android:windowNoTitle">true</item> </style> diff --git a/src/com/android/launcher2/AllApps2D.java b/src/com/android/launcher2/AllApps2D.java index 7ad5e49..9764f23 100644 --- a/src/com/android/launcher2/AllApps2D.java +++ b/src/com/android/launcher2/AllApps2D.java @@ -16,32 +16,30 @@ package com.android.launcher2; +import com.android.launcher.R; + import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; -import android.graphics.drawable.BitmapDrawable; import android.graphics.Bitmap; -import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; -import android.view.ViewGroup; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.animation.AnimationUtils; -import android.view.ViewConfiguration; import android.widget.AdapterView; -import android.widget.ImageButton; -import android.widget.TextView; import android.widget.ArrayAdapter; import android.widget.GridView; +import android.widget.ImageButton; import android.widget.RelativeLayout; +import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; -import com.android.launcher.R; - public class AllApps2D extends RelativeLayout implements AllAppsView, @@ -58,8 +56,16 @@ public class AllApps2D private GridView mGrid; + /** All applications in the system (we might only be showing a subset) */ private ArrayList<ApplicationInfo> mAllAppsList = new ArrayList<ApplicationInfo>(); + /** Currently visible applications in the grid */ + private ArrayList<ApplicationInfo> mVisibleAppsList = new ArrayList<ApplicationInfo>(); + + public enum AppType { APP, GAME, DOWNLOADED, ALL }; + + private AppType mCurrentFilter = AppType.ALL; + // preserve compatibility with 3D all apps: // 0.0 -> hidden // 1.0 -> shown and opaque @@ -120,30 +126,27 @@ public class AllApps2D setVisibility(View.GONE); setSoundEffectsEnabled(false); - mAppsAdapter = new AppsAdapter(getContext(), mAllAppsList); - mAppsAdapter.setNotifyOnChange(false); + mAppsAdapter = new AppsAdapter(getContext(), mVisibleAppsList); } @Override protected void onFinishInflate() { - setBackgroundColor(Color.BLACK); - try { mGrid = (GridView)findViewWithTag("all_apps_2d_grid"); if (mGrid == null) throw new Resources.NotFoundException(); mGrid.setOnItemClickListener(this); mGrid.setOnItemLongClickListener(this); - mGrid.setBackgroundColor(Color.BLACK); - mGrid.setCacheColorHint(Color.BLACK); + // The home button is optional; some layouts might not use it ImageButton homeButton = (ImageButton) findViewWithTag("all_apps_2d_home"); - if (homeButton == null) throw new Resources.NotFoundException(); - homeButton.setOnClickListener( - new View.OnClickListener() { - public void onClick(View v) { - mLauncher.closeAllApps(true); - } - }); + if (homeButton != null) { + homeButton.setOnClickListener( + new View.OnClickListener() { + public void onClick(View v) { + mLauncher.closeAllApps(true); + } + }); + } } catch (Resources.NotFoundException e) { Log.e(TAG, "Can't find necessary layout elements for AllApps2D"); } @@ -250,19 +253,17 @@ public class AllApps2D return mZoom > 0.001f; } - @Override - public boolean isOpaque() { - return mZoom > 0.999f; + public boolean isAnimating() { + return (getAnimation() != null); } public void setApps(ArrayList<ApplicationInfo> list) { mAllAppsList.clear(); addApps(list); + filterApps(mCurrentFilter); } public void addApps(ArrayList<ApplicationInfo> list) { -// Log.d(TAG, "addApps: " + list.size() + " apps: " + list.toString()); - final int N = list.size(); for (int i=0; i<N; i++) { @@ -274,11 +275,12 @@ public class AllApps2D } mAllAppsList.add(index, item); } - mAppsAdapter.notifyDataSetChanged(); + filterApps(mCurrentFilter); } public void removeApps(ArrayList<ApplicationInfo> list) { final int N = list.size(); + for (int i=0; i<N; i++) { final ApplicationInfo item = list.get(i); int index = findAppByComponent(mAllAppsList, item); @@ -289,7 +291,7 @@ public class AllApps2D // Try to recover. This should keep us from crashing for now. } } - mAppsAdapter.notifyDataSetChanged(); + filterApps(mCurrentFilter); } public void updateApps(ArrayList<ApplicationInfo> list) { @@ -298,6 +300,33 @@ public class AllApps2D addApps(list); } + public void filterApps(AppType appType) { + mCurrentFilter = appType; + + mAppsAdapter.setNotifyOnChange(false); + mVisibleAppsList.clear(); + if (appType == AppType.ALL) { + mVisibleAppsList.addAll(mAllAppsList); + } else { + int searchFlags = 0; + + if (appType == AppType.APP) { + searchFlags = ApplicationInfo.APP_FLAG; + } else if (appType == AppType.GAME) { + searchFlags = ApplicationInfo.GAME_FLAG; + } else if (appType == AppType.DOWNLOADED) { + searchFlags = ApplicationInfo.DOWNLOADED_FLAG; + } + + for (ApplicationInfo info : mAllAppsList) { + if ((info.flags & searchFlags) != 0) { + mVisibleAppsList.add(info); + } + } + } + mAppsAdapter.notifyDataSetChanged(); + } + private static int findAppByComponent(ArrayList<ApplicationInfo> list, ApplicationInfo item) { ComponentName component = item.intent.getComponent(); final int N = list.size(); diff --git a/src/com/android/launcher2/AllApps3D.java b/src/com/android/launcher2/AllApps3D.java index 308ad28..b56a9c9 100644 --- a/src/com/android/launcher2/AllApps3D.java +++ b/src/com/android/launcher2/AllApps3D.java @@ -16,6 +16,12 @@ package com.android.launcher2; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import com.android.launcher.R; + import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; @@ -23,19 +29,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.renderscript.Allocation; -import android.renderscript.Element; -import android.renderscript.ProgramFragment; -import android.renderscript.ProgramStore; -import android.renderscript.ProgramVertex; -import android.renderscript.RSSurfaceView; -import android.renderscript.RenderScriptGL; -import android.renderscript.RenderScript; -import android.renderscript.Sampler; -import android.renderscript.Script; -import android.renderscript.ScriptC; -import android.renderscript.SimpleMesh; -import android.renderscript.Type; +import android.renderscript.*; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -48,12 +42,6 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; - -import com.android.launcher.R; - public class AllApps3D extends RSSurfaceView implements AllAppsView, View.OnClickListener, View.OnLongClickListener, DragSource { private static final String TAG = "Launcher.AllApps3D"; @@ -131,27 +119,16 @@ public class AllApps3D extends RSSurfaceView private boolean mSurrendered; private int mRestoreFocusIndex = -1; - + @SuppressWarnings({"UnusedDeclaration"}) static class Defines { - public static final int ALLOC_PARAMS = 0; - public static final int ALLOC_STATE = 1; - public static final int ALLOC_ICON_IDS = 3; - public static final int ALLOC_LABEL_IDS = 4; - public static final int ALLOC_VP_CONSTANTS = 5; - public static final int COLUMNS_PER_PAGE_PORTRAIT = 4; public static final int ROWS_PER_PAGE_PORTRAIT = 4; public static final int COLUMNS_PER_PAGE_LANDSCAPE = 6; public static final int ROWS_PER_PAGE_LANDSCAPE = 3; - public static final int ICON_WIDTH_PX = 64; - public static final int ICON_TEXTURE_WIDTH_PX = 74; public static final int SELECTION_TEXTURE_WIDTH_PX = 74 + 20; - - public static final int ICON_HEIGHT_PX = 64; - public static final int ICON_TEXTURE_HEIGHT_PX = 74; public static final int SELECTION_TEXTURE_HEIGHT_PX = 74 + 20; } @@ -159,7 +136,6 @@ public class AllApps3D extends RSSurfaceView super(context, attrs); setFocusable(true); setSoundEffectsEnabled(false); - getHolder().setFormat(PixelFormat.TRANSLUCENT); final ViewConfiguration config = ViewConfiguration.get(context); mSlop = config.getScaledTouchSlop(); mMaxFlingVelocity = config.getScaledMaximumFlingVelocity(); @@ -170,7 +146,10 @@ public class AllApps3D extends RSSurfaceView getHolder().setFormat(PixelFormat.TRANSLUCENT); if (sRS == null) { - sRS = createRenderScript(true); + RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig(); + sc.setDepth(16, 16); + sc.setAlpha(8, 8); + sRS = createRenderScript(sc); } else { createRenderScript(sRS); } @@ -274,26 +253,11 @@ public class AllApps3D extends RSSurfaceView sRollo.dirtyCheck(); sRollo.resize(w, h); + Log.d(TAG, "sc " + sRS); if (sRS != null) { sRS.mMessageCallback = mMessageProc = new AAMessage(); } - if (sRollo.mUniformAlloc != null) { - float tf[] = new float[] {72.f, 72.f, - 120.f, 120.f, 0.f, 0.f, - 120.f, 680.f, - (2.f / 480.f), 0, -((float)w / 2) - 0.25f, -380.25f}; - if (w > h) { - tf[6] = 40.f; - tf[7] = h - 40.f; - tf[9] = 1.f; - tf[10] = -((float)w / 2) - 0.25f; - tf[11] = -((float)h / 2) - 0.25f; - } - - sRollo.mUniformAlloc.data(tf); - } - //long endTime = SystemClock.uptimeMillis(); //Log.d(TAG, "surfaceChanged took " + (endTime-startTime) + "ms"); } @@ -307,18 +271,17 @@ public class AllApps3D extends RSSurfaceView if (mArrowNavigation) { if (!hasWindowFocus) { // Clear selection when we lose window focus - mLastSelectedIcon = sRollo.mState.selectedIconIndex; + mLastSelectedIcon = sRollo.mScript.get_gSelectedIconIndex(); sRollo.setHomeSelected(SELECTED_NONE); sRollo.clearSelectedIcon(); - sRollo.mState.save(); } else { - if (sRollo.mState.iconCount > 0) { + if (sRollo.mScript.get_gIconCount() > 0) { if (mLastSelection == SELECTION_ICONS) { int selection = mLastSelectedIcon; final int firstIcon = Math.round(sRollo.mScrollPos) * mColumnsPerPage; if (selection < 0 || // No selection selection < firstIcon || // off the top of the screen - selection >= sRollo.mState.iconCount || // past last icon + selection >= sRollo.mScript.get_gIconCount() || // past last icon selection >= firstIcon + // past last icon on screen (mColumnsPerPage * mRowsPerPage)) { selection = firstIcon; @@ -326,10 +289,8 @@ public class AllApps3D extends RSSurfaceView // Select the first icon when we gain window focus sRollo.selectIcon(selection, SELECTED_FOCUSED); - sRollo.mState.save(); } else if (mLastSelection == SELECTION_HOME) { sRollo.setHomeSelected(SELECTED_FOCUSED); - sRollo.mState.save(); } } } @@ -356,7 +317,6 @@ public class AllApps3D extends RSSurfaceView // Clear selection when we lose focus sRollo.clearSelectedIcon(); sRollo.setHomeSelected(SELECTED_NONE); - sRollo.mState.save(); mArrowNavigation = false; } } else { @@ -366,11 +326,10 @@ public class AllApps3D extends RSSurfaceView } private void gainFocus() { - if (!mArrowNavigation && sRollo.mState.iconCount > 0) { + if (!mArrowNavigation && sRollo.mScript.get_gIconCount() > 0) { // Select the first icon when we gain keyboard focus mArrowNavigation = true; sRollo.selectIcon(Math.round(sRollo.mScrollPos) * mColumnsPerPage, SELECTED_FOCUSED); - sRollo.mState.save(); } } @@ -382,7 +341,7 @@ public class AllApps3D extends RSSurfaceView if (!isVisible()) { return false; } - final int iconCount = sRollo.mState.iconCount; + final int iconCount = sRollo.mScript.get_gIconCount(); if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) { if (mArrowNavigation) { @@ -390,7 +349,7 @@ public class AllApps3D extends RSSurfaceView reallyPlaySoundEffect(SoundEffectConstants.CLICK); mLauncher.closeAllApps(true); } else { - int whichApp = sRollo.mState.selectedIconIndex; + int whichApp = sRollo.mScript.get_gSelectedIconIndex(); if (whichApp >= 0) { ApplicationInfo app = mAllAppsList.get(whichApp); mLauncher.startActivitySafely(app.intent, app); @@ -402,10 +361,10 @@ public class AllApps3D extends RSSurfaceView if (iconCount > 0) { final boolean isPortrait = getWidth() < getHeight(); - + mArrowNavigation = true; - int currentSelection = sRollo.mState.selectedIconIndex; + int currentSelection = sRollo.mScript.get_gSelectedIconIndex(); int currentTopRow = Math.round(sRollo.mScrollPos); // The column of the current selection, in the range 0..COLUMNS_PER_PAGE_PORTRAIT-1 @@ -511,7 +470,6 @@ public class AllApps3D extends RSSurfaceView } if (newSelection != currentSelection) { sRollo.selectIcon(newSelection, SELECTED_FOCUSED); - sRollo.mState.save(); } } return handled; @@ -611,7 +569,6 @@ public class AllApps3D extends RSSurfaceView (!isPortrait && x > mTouchXBorders[mTouchXBorders.length-1])) { mTouchTracking = TRACKING_HOME; sRollo.setHomeSelected(SELECTED_PRESSED); - sRollo.mState.save(); mCurrentIconIndex = -1; } else { mTouchTracking = TRACKING_FLING; @@ -619,9 +576,6 @@ public class AllApps3D extends RSSurfaceView mMotionDownRawX = (int)ev.getRawX(); mMotionDownRawY = (int)ev.getRawY(); - sRollo.mState.newPositionX = ev.getRawY() / getHeight(); - sRollo.mState.newTouchDown = 1; - if (!sRollo.checkClickOK()) { sRollo.clearSelectedIcon(); } else { @@ -632,8 +586,7 @@ public class AllApps3D extends RSSurfaceView cancelLongPress(); } } - sRollo.mState.save(); - sRollo.move(); + sRollo.move(ev.getRawY() / getHeight()); mVelocityTracker = VelocityTracker.obtain(); mVelocityTracker.addMovement(ev); mStartedScrolling = false; @@ -646,7 +599,6 @@ public class AllApps3D extends RSSurfaceView y > mTouchYBorders[mTouchYBorders.length-1]) || (!isPortrait && x > mTouchXBorders[mTouchXBorders.length-1]) ? SELECTED_PRESSED : SELECTED_NONE); - sRollo.mState.save(); } else if (mTouchTracking == TRACKING_FLING) { int rawY = (int)ev.getRawY(); int slop; @@ -667,14 +619,11 @@ public class AllApps3D extends RSSurfaceView cancelLongPress(); mCurrentIconIndex = -1; } - sRollo.mState.newPositionX = ev.getRawY() / getHeight(); - sRollo.mState.newTouchDown = 1; - sRollo.move(); + sRollo.move(ev.getRawY() / getHeight()); mStartedScrolling = true; sRollo.clearSelectedIcon(); mVelocityTracker.addMovement(ev); - sRollo.mState.save(); } } break; @@ -688,18 +637,13 @@ public class AllApps3D extends RSSurfaceView mLauncher.closeAllApps(true); } sRollo.setHomeSelected(SELECTED_NONE); - sRollo.mState.save(); } mCurrentIconIndex = -1; } else if (mTouchTracking == TRACKING_FLING) { - sRollo.mState.newTouchDown = 0; - sRollo.mState.newPositionX = ev.getRawY() / getHeight(); - mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, mMaxFlingVelocity); - sRollo.mState.flingVelocity = mVelocityTracker.getYVelocity() / getHeight(); sRollo.clearSelectedIcon(); - sRollo.mState.save(); - sRollo.fling(); + sRollo.fling(ev.getRawY() / getHeight(), + mVelocityTracker.getYVelocity() / getHeight()); if (mVelocityTracker != null) { mVelocityTracker.recycle(); @@ -766,7 +710,7 @@ public class AllApps3D extends RSSurfaceView int pos = -1; switch (mLastSelection) { case SELECTION_ICONS: - index = sRollo.mState.selectedIconIndex; + index = sRollo.mScript.get_gSelectedIconIndex(); if (index >= 0) { ApplicationInfo info = mAllAppsList.get(index); if (info.title != null) { @@ -825,8 +769,8 @@ public class AllApps3D extends RSSurfaceView return sRollo != null && mZoom > 0.001f; } - public boolean isOpaque() { - return mZoom > 0.999f; + public boolean isAnimating() { + return isVisible() && mZoom <= 0.999f; } public void setApps(ArrayList<ApplicationInfo> list) { @@ -858,13 +802,12 @@ public class AllApps3D extends RSSurfaceView if (sRollo != null && reload) { sRollo.setApps(list); } - + if (hasFocus() && mRestoreFocusIndex != -1) { sRollo.selectIcon(mRestoreFocusIndex, SELECTED_FOCUSED); - sRollo.mState.save(); mRestoreFocusIndex = -1; } - + mLocks &= ~LOCK_ICONS_PENDING; } @@ -881,7 +824,7 @@ public class AllApps3D extends RSSurfaceView final int N = list.size(); if (sRollo != null) { sRollo.pause(); - sRollo.reallocAppsList(sRollo.mState.iconCount + N); + sRollo.reallocAppsList(sRollo.mScript.get_gIconCount() + N); } for (int i=0; i<N; i++) { @@ -951,17 +894,6 @@ public class AllApps3D extends RSSurfaceView return -1; } - /* - private static int countPages(int iconCount) { - int iconsPerPage = getColumnsCount() * Defines.ROWS_PER_PAGE_PORTRAIT; - int pages = iconCount / iconsPerPage; - if (pages*iconsPerPage != iconCount) { - pages++; - } - return pages; - } - */ - class AAMessage extends RenderScript.RSMessage { public void run() { sRollo.mScrollPos = ((float)mData[0]) / (1 << 16); @@ -993,23 +925,12 @@ public class AllApps3D extends RSSurfaceView private int mHeight; private Resources mRes; - private Script mScript; - private Script.Invokable mInvokeMove; - private Script.Invokable mInvokeMoveTo; - private Script.Invokable mInvokeFling; - private Script.Invokable mInvokeResetWAR; - private Script.Invokable mInvokeSetZoom; - - private ProgramStore mPSIcons; - private ProgramFragment mPFTexMip; - private ProgramFragment mPFTexMipAlpha; - private ProgramFragment mPFTexNearest; - private ProgramVertex mPV; - private ProgramVertex mPVCurve; - private SimpleMesh mMesh; + ScriptC_allapps mScript; + + private Mesh mMesh; private ProgramVertex.MatrixAllocation mPVA; - private Allocation mUniformAlloc; + private ScriptField_VpConsts mUniformAlloc; private Allocation mHomeButtonNormal; private Allocation mHomeButtonFocused; @@ -1022,28 +943,15 @@ public class AllApps3D extends RSSurfaceView private Allocation[] mLabels; private int[] mLabelIds; private Allocation mAllocLabelIds; - private Allocation mSelectedIcon; private Bitmap mSelectionBitmap; private Canvas mSelectionCanvas; - - private float mScrollPos; - Params mParams; - State mState; + private float mScrollPos; AllApps3D mAllApps; boolean mInitialize; - class BaseAlloc { - Allocation mAlloc; - Type mType; - - void save() { - mAlloc.data(this); - } - } - private boolean checkClickOK() { return (Math.abs(mAllApps.mVelocity) < 0.4f) && (Math.abs(mScrollPos - Math.round(mScrollPos)) < 0.4f); @@ -1061,41 +969,6 @@ public class AllApps3D extends RSSurfaceView } } - class Params extends BaseAlloc { - Params() { - mType = Type.createFromClass(sRS, Params.class, 1, "ParamsClass"); - mAlloc = Allocation.createTyped(sRS, mType); - save(); - } - public int bubbleWidth; - public int bubbleHeight; - public int bubbleBitmapWidth; - public int bubbleBitmapHeight; - - public int homeButtonWidth; - public int homeButtonHeight; - public int homeButtonTextureWidth; - public int homeButtonTextureHeight; - } - - class State extends BaseAlloc { - public float newPositionX; - public int newTouchDown; - public float flingVelocity; - public int iconCount; - public int selectedIconIndex = -1; - public int selectedIconTexture; - public float zoomTarget; - public int homeButtonId; - public float targetPos; - - State() { - mType = Type.createFromClass(sRS, State.class, 1, "StateClass"); - mAlloc = Allocation.createTyped(sRS, mType); - save(); - } - } - public RolloRS(AllApps3D allApps) { mAllApps = allApps; } @@ -1104,16 +977,21 @@ public class AllApps3D extends RSSurfaceView mRes = res; mWidth = width; mHeight = height; + mScript = new ScriptC_allapps(sRS, mRes, R.raw.allapps, true); + initProgramVertex(); initProgramFragment(); initProgramStore(); initGl(); initData(); - initRs(); + + mScript.bind_gIconIDs(mAllocIconIds); + mScript.bind_gLabelIDs(mAllocLabelIds); + sRS.contextBindRootScript(mScript); } public void initMesh() { - SimpleMesh.TriangleMeshBuilder tm = new SimpleMesh.TriangleMeshBuilder(sRS, 2, 0); + Mesh.TriangleMeshBuilder tm = new Mesh.TriangleMeshBuilder(sRS, 2, 0); for (int ct=0; ct < 16; ct++) { float pos = (1.f / (16.f - 1)) * ct; @@ -1124,12 +1002,55 @@ public class AllApps3D extends RSSurfaceView tm.addTriangle(ct, ct+1, ct+2); tm.addTriangle(ct+1, ct+3, ct+2); } - mMesh = tm.create(); - mMesh.setName("SMCell"); + mMesh = tm.create(true); + mScript.set_gSMCell(mMesh); + } + + Matrix4f getProjectionMatrix(int w, int h) { + // range -1,1 in the narrow axis at z = 0. + Matrix4f m1 = new Matrix4f(); + Matrix4f m2 = new Matrix4f(); + + if(w > h) { + float aspect = ((float)w) / h; + m1.loadFrustum(-aspect,aspect, -1,1, 1,100); + } else { + float aspect = ((float)h) / w; + m1.loadFrustum(-1,1, -aspect,aspect, 1,100); + } + + m2.loadRotate(180, 0, 1, 0); + m1.loadMultiply(m1, m2); + + m2.loadScale(-2, 2, 1); + m1.loadMultiply(m1, m2); + + m2.loadTranslate(0, 0, 2); + m1.loadMultiply(m1, m2); + return m1; } void resize(int w, int h) { - mPVA.setupProjectionNormalized(w, h); + Matrix4f proj = getProjectionMatrix(w, h); + mPVA.loadProjection(proj); + + if (mUniformAlloc != null) { + ScriptField_VpConsts.Item i = new ScriptField_VpConsts.Item(); + i.Proj = proj; + i.ScaleOffset.x = (2.f / 480.f); + i.ScaleOffset.y = 0; + i.ScaleOffset.z = -((float)w / 2) - 0.25f; + i.ScaleOffset.w = -380.25f; + i.BendPos.x = 120.f; + i.BendPos.y = 680.f; + if (w > h) { + i.ScaleOffset.z = 40.f; + i.ScaleOffset.w = h - 40.f; + i.BendPos.y = 1.f; + } + mUniformAlloc.set(i, 0, true); + } + mWidth = w; mHeight = h; } @@ -1140,22 +1061,18 @@ public class AllApps3D extends RSSurfaceView ProgramVertex.Builder pvb = new ProgramVertex.Builder(sRS, null, null); pvb.setTextureMatrixEnable(true); - mPV = pvb.create(); - mPV.setName("PV"); - mPV.bindAllocation(mPVA); + ProgramVertex pv = pvb.create(); + pv.bindAllocation(mPVA); + sRS.contextBindProgramVertex(pv); - Element.Builder eb = new Element.Builder(sRS); - eb.add(Element.createVector(sRS, Element.DataType.FLOAT_32, 2), "ImgSize"); - eb.add(Element.createVector(sRS, Element.DataType.FLOAT_32, 4), "Position"); - eb.add(Element.createVector(sRS, Element.DataType.FLOAT_32, 2), "BendPos"); - eb.add(Element.createVector(sRS, Element.DataType.FLOAT_32, 4), "ScaleOffset"); - Element e = eb.create(); - - mUniformAlloc = Allocation.createSized(sRS, e, 1); + mUniformAlloc = new ScriptField_VpConsts(sRS, 1); + mScript.bind_vpConstants(mUniformAlloc); initMesh(); ProgramVertex.ShaderBuilder sb = new ProgramVertex.ShaderBuilder(sRS); - String t = "void main() {\n" + + String t = "varying vec4 varColor;\n" + + "varying vec2 varTex0;\n" + + "void main() {\n" + // Animation " float ani = UNI_Position.z;\n" + @@ -1205,21 +1122,18 @@ public class AllApps3D extends RSSurfaceView " pos.z -= ani * 1.5;\n" + " lum *= 1.0 - ani;\n" + - " gl_Position = UNI_MVP * pos;\n" + + " gl_Position = UNI_Proj * pos;\n" + " varColor.rgba = vec4(lum, lum, lum, 1.0);\n" + " varTex0.xy = ATTRIB_position;\n" + " varTex0.y = 1.0 - varTex0.y;\n" + - " varTex0.zw = vec2(0.0, 0.0);\n" + "}\n"; sb.setShader(t); sb.addConstant(mUniformAlloc.getType()); - sb.addInput(mMesh.getVertexType(0).getElement()); - mPVCurve = sb.create(); - mPVCurve.setName("PVCurve"); - mPVCurve.bindAllocation(mPVA); - mPVCurve.bindConstants(mUniformAlloc, 1); + sb.addInput(mMesh.getVertexAllocation(0).getType().getElement()); + ProgramVertex pvc = sb.create(); + pvc.bindConstants(mUniformAlloc.getAllocation(), 0); - sRS.contextBindProgramVertex(mPV); + mScript.set_gPVCurve(pvc); } private void initProgramFragment() { @@ -1237,20 +1151,23 @@ public class AllApps3D extends RSSurfaceView ProgramFragment.Builder bf = new ProgramFragment.Builder(sRS); bf.setTexture(ProgramFragment.Builder.EnvMode.MODULATE, ProgramFragment.Builder.Format.RGBA, 0); - mPFTexMip = bf.create(); - mPFTexMip.setName("PFTexMip"); - mPFTexMip.bindSampler(linear, 0); + bf.setVaryingColor(true); + ProgramFragment pfTexMip = bf.create(); + pfTexMip.bindSampler(linear, 0); - mPFTexNearest = bf.create(); - mPFTexNearest.setName("PFTexNearest"); - mPFTexNearest.bindSampler(nearest, 0); + bf.setVaryingColor(false); + ProgramFragment pfTexNearest = bf.create(); + pfTexNearest.bindSampler(nearest, 0); bf.setTexture(ProgramFragment.Builder.EnvMode.MODULATE, ProgramFragment.Builder.Format.ALPHA, 0); - mPFTexMipAlpha = bf.create(); - mPFTexMipAlpha.setName("PFTexMipAlpha"); - mPFTexMipAlpha.bindSampler(linear, 0); + bf.setVaryingColor(true); + ProgramFragment pfTexMipAlpha = bf.create(); + pfTexMipAlpha.bindSampler(linear, 0); + mScript.set_gPFTexNearest(pfTexNearest); + mScript.set_gPFTexMip(pfTexMip); + mScript.set_gPFTexMipAlpha(pfTexMipAlpha); } private void initProgramStore() { @@ -1260,23 +1177,17 @@ public class AllApps3D extends RSSurfaceView bs.setDitherEnable(true); bs.setBlendFunc(ProgramStore.BlendSrcFunc.SRC_ALPHA, ProgramStore.BlendDstFunc.ONE_MINUS_SRC_ALPHA); - mPSIcons = bs.create(); - mPSIcons.setName("PSIcons"); + mScript.set_gPS(bs.create()); } private void initGl() { } private void initData() { - mParams = new Params(); - mState = new State(); - - final Utilities.BubbleText bubble = new Utilities.BubbleText(mAllApps.getContext()); - - mParams.bubbleWidth = bubble.getBubbleWidth(); - mParams.bubbleHeight = bubble.getMaxBubbleHeight(); - mParams.bubbleBitmapWidth = bubble.getBitmapWidth(); - mParams.bubbleBitmapHeight = bubble.getBitmapHeight(); + mScript.set_COLUMNS_PER_PAGE_PORTRAIT(Defines.COLUMNS_PER_PAGE_PORTRAIT); + mScript.set_ROWS_PER_PAGE_PORTRAIT(Defines.ROWS_PER_PAGE_PORTRAIT); + mScript.set_COLUMNS_PER_PAGE_LANDSCAPE(Defines.COLUMNS_PER_PAGE_LANDSCAPE); + mScript.set_ROWS_PER_PAGE_LANDSCAPE(Defines.ROWS_PER_PAGE_LANDSCAPE); mHomeButtonNormal = Allocation.createFromBitmapResource(sRS, mRes, R.drawable.home_button_normal, Element.RGBA_8888(sRS), false); @@ -1287,15 +1198,8 @@ public class AllApps3D extends RSSurfaceView mHomeButtonPressed = Allocation.createFromBitmapResource(sRS, mRes, R.drawable.home_button_pressed, Element.RGBA_8888(sRS), false); mHomeButtonPressed.uploadToTexture(0); - mParams.homeButtonWidth = 76; - mParams.homeButtonHeight = 68; - mParams.homeButtonTextureWidth = 128; - mParams.homeButtonTextureHeight = 128; - mState.homeButtonId = mHomeButtonNormal.getID(); - - mParams.save(); - mState.save(); + mScript.set_gHomeButton(mHomeButtonNormal); mSelectionBitmap = Bitmap.createBitmap(Defines.SELECTION_TEXTURE_WIDTH_PX, Defines.SELECTION_TEXTURE_HEIGHT_PX, Bitmap.Config.ARGB_8888); @@ -1304,30 +1208,6 @@ public class AllApps3D extends RSSurfaceView setApps(null); } - private void initRs() { - ScriptC.Builder sb = new ScriptC.Builder(sRS); - sb.setScript(mRes, R.raw.allapps); - sb.setRoot(true); - sb.addDefines(mAllApps.mDefines); - sb.setType(mParams.mType, "params", Defines.ALLOC_PARAMS); - sb.setType(mState.mType, "state", Defines.ALLOC_STATE); - sb.setType(mUniformAlloc.getType(), "vpConstants", Defines.ALLOC_VP_CONSTANTS); - mInvokeMove = sb.addInvokable("move"); - mInvokeFling = sb.addInvokable("fling"); - mInvokeMoveTo = sb.addInvokable("moveTo"); - mInvokeResetWAR = sb.addInvokable("resetHWWar"); - mInvokeSetZoom = sb.addInvokable("setZoom"); - mScript = sb.create(); - mScript.setClearColor(0.0f, 0.0f, 0.0f, 0.0f); - mScript.bindAllocation(mParams.mAlloc, Defines.ALLOC_PARAMS); - mScript.bindAllocation(mState.mAlloc, Defines.ALLOC_STATE); - mScript.bindAllocation(mAllocIconIds, Defines.ALLOC_ICON_IDS); - mScript.bindAllocation(mAllocLabelIds, Defines.ALLOC_LABEL_IDS); - mScript.bindAllocation(mUniformAlloc, Defines.ALLOC_VP_CONSTANTS); - - sRS.contextBindRootScript(mScript); - } - void dirtyCheck() { if (sZoomDirty) { setZoom(mAllApps.sNextZoom, mAllApps.sAnimateNextZoom); @@ -1345,20 +1225,21 @@ public class AllApps3D extends RSSurfaceView mIcons = new Allocation[count]; mIconIds = new int[allocCount]; - mAllocIconIds = Allocation.createSized(sRS, Element.USER_I32(sRS), allocCount); + mAllocIconIds = Allocation.createSized(sRS, Element.I32(sRS), allocCount); mLabels = new Allocation[count]; mLabelIds = new int[allocCount]; - mAllocLabelIds = Allocation.createSized(sRS, Element.USER_I32(sRS), allocCount); + mAllocLabelIds = Allocation.createSized(sRS, Element.I32(sRS), allocCount); - mState.iconCount = count; - for (int i=0; i < mState.iconCount; i++) { + mScript.set_gIconCount(count); + for (int i=0; i < count; i++) { createAppIconAllocations(i, list.get(i)); } - for (int i=0; i < mState.iconCount; i++) { + for (int i=0; i < count; i++) { uploadAppIcon(i, list.get(i)); } saveAppsList(); + android.util.Log.e("rs", "setApps"); sRollo.resume(); } @@ -1367,15 +1248,7 @@ public class AllApps3D extends RSSurfaceView sRollo.clearSelectedIcon(); sRollo.setHomeSelected(SELECTED_NONE); } - if (zoom > 0.001f) { - sRollo.mState.zoomTarget = zoom; - } else { - sRollo.mState.zoomTarget = 0; - } - sRollo.mState.save(); - if (!animate) { - sRollo.mInvokeSetZoom.execute(); - } + sRollo.mScript.invoke_setZoom(zoom, animate ? 1 : 0); } private void createAppIconAllocations(int index, ApplicationInfo item) { @@ -1405,13 +1278,13 @@ public class AllApps3D extends RSSurfaceView private void reallocAppsList(int count) { Allocation[] icons = new Allocation[count]; int[] iconIds = new int[count]; - mAllocIconIds = Allocation.createSized(sRS, Element.USER_I32(sRS), count); + mAllocIconIds = Allocation.createSized(sRS, Element.I32(sRS), count); Allocation[] labels = new Allocation[count]; int[] labelIds = new int[count]; - mAllocLabelIds = Allocation.createSized(sRS, Element.USER_I32(sRS), count); + mAllocLabelIds = Allocation.createSized(sRS, Element.I32(sRS), count); - final int oldCount = sRollo.mState.iconCount; + final int oldCount = sRollo.mScript.get_gIconCount(); System.arraycopy(mIcons, 0, icons, 0, oldCount); System.arraycopy(mIconIds, 0, iconIds, 0, oldCount); @@ -1428,7 +1301,7 @@ public class AllApps3D extends RSSurfaceView * Handle the allocations for the new app. Make sure you call saveAppsList when done. */ private void addApp(int index, ApplicationInfo item) { - final int count = mState.iconCount - index; + final int count = mScript.get_gIconCount() - index; final int dest = index + 1; System.arraycopy(mIcons, index, mIcons, dest, count); @@ -1438,14 +1311,15 @@ public class AllApps3D extends RSSurfaceView createAppIconAllocations(index, item); uploadAppIcon(index, item); - sRollo.mState.iconCount++; + + mScript.set_gIconCount(mScript.get_gIconCount() + 1); } /** * Handle the allocations for the removed app. Make sure you call saveAppsList when done. */ private void removeApp(int index) { - final int count = mState.iconCount - index - 1; + final int count = mScript.get_gIconCount() - index - 1; final int src = index + 1; System.arraycopy(mIcons, src, mIcons, index, count); @@ -1453,8 +1327,8 @@ public class AllApps3D extends RSSurfaceView System.arraycopy(mLabels, src, mLabels, index, count); System.arraycopy(mLabelIds, src, mLabelIds, index, count); - sRollo.mState.iconCount--; - final int last = mState.iconCount; + mScript.set_gIconCount(mScript.get_gIconCount() - 1); + final int last = mScript.get_gIconCount(); mIcons[last] = null; mIconIds[last] = 0; @@ -1471,31 +1345,21 @@ public class AllApps3D extends RSSurfaceView mAllocIconIds.data(mIconIds); mAllocLabelIds.data(mLabelIds); - mScript.bindAllocation(mAllocIconIds, Defines.ALLOC_ICON_IDS); - mScript.bindAllocation(mAllocLabelIds, Defines.ALLOC_LABEL_IDS); - - mState.save(); - - // Note: mScript may be null if we haven't initialized it yet. - // In that case, this is a no-op. - if (mInvokeResetWAR != null) { - mInvokeResetWAR.execute(); - } + mScript.bind_gIconIDs(mAllocIconIds); + mScript.bind_gLabelIDs(mAllocLabelIds); } } - void fling() { - mInvokeFling.execute(); + void fling(float pos, float v) { + mScript.invoke_fling(pos, v); } - void move() { - mInvokeMove.execute(); + void move(float pos) { + mScript.invoke_move(pos); } void moveTo(float row) { - mState.targetPos = row; - mState.save(); - mInvokeMoveTo.execute(); + mScript.invoke_moveTo(row); } /** @@ -1525,7 +1389,7 @@ public class AllApps3D extends RSSurfaceView if (mAllApps != null) { mAllApps.mRestoreFocusIndex = index; } - mState.selectedIconIndex = -1; + mScript.set_gSelectedIconIndex(-1); if (mAllApps.mLastSelection == SELECTION_ICONS) { mAllApps.mLastSelection = SELECTION_NONE; } @@ -1534,8 +1398,8 @@ public class AllApps3D extends RSSurfaceView mAllApps.mLastSelection = SELECTION_ICONS; } - int prev = mState.selectedIconIndex; - mState.selectedIconIndex = index; + int prev = mScript.get_gSelectedIconIndex(); + mScript.set_gSelectedIconIndex(index); ApplicationInfo info = appsList.get(index); Bitmap selectionBitmap = mSelectionBitmap; @@ -1544,10 +1408,10 @@ public class AllApps3D extends RSSurfaceView selectionBitmap.getWidth(), selectionBitmap.getHeight(), pressed == SELECTED_PRESSED, info.iconBitmap); - mSelectedIcon = Allocation.createFromBitmap(sRS, selectionBitmap, + Allocation si = Allocation.createFromBitmap(sRS, selectionBitmap, Element.RGBA_8888(sRS), false); - mSelectedIcon.uploadToTexture(0); - mState.selectedIconTexture = mSelectedIcon.getID(); + si.uploadToTexture(0); + mScript.set_gSelectedIconTexture(si); if (prev != index) { if (info.title != null && info.title.length() > 0) { @@ -1562,24 +1426,24 @@ public class AllApps3D extends RSSurfaceView * You need to call save() on mState on your own after calling this. */ void clearSelectedIcon() { - mState.selectedIconIndex = -1; + mScript.set_gSelectedIconIndex(-1); } void setHomeSelected(int mode) { final int prev = mAllApps.mLastSelection; switch (mode) { case SELECTED_NONE: - mState.homeButtonId = mHomeButtonNormal.getID(); + mScript.set_gHomeButton(mHomeButtonNormal); break; case SELECTED_FOCUSED: mAllApps.mLastSelection = SELECTION_HOME; - mState.homeButtonId = mHomeButtonFocused.getID(); + mScript.set_gHomeButton(mHomeButtonFocused); if (prev != SELECTION_HOME) { mAllApps.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } break; case SELECTED_PRESSED: - mState.homeButtonId = mHomeButtonPressed.getID(); + mScript.set_gHomeButton(mHomeButtonPressed); break; } } @@ -1599,23 +1463,15 @@ public class AllApps3D extends RSSurfaceView Log.d(TAG, "sRollo.mLabelIds.length=" + mLabelIds.length); } Log.d(TAG, "sRollo.mLabelIds=" + Arrays.toString(mLabelIds)); - Log.d(TAG, "sRollo.mState.newPositionX=" + mState.newPositionX); - Log.d(TAG, "sRollo.mState.newTouchDown=" + mState.newTouchDown); - Log.d(TAG, "sRollo.mState.flingVelocity=" + mState.flingVelocity); - Log.d(TAG, "sRollo.mState.iconCount=" + mState.iconCount); - Log.d(TAG, "sRollo.mState.selectedIconIndex=" + mState.selectedIconIndex); - Log.d(TAG, "sRollo.mState.selectedIconTexture=" + mState.selectedIconTexture); - Log.d(TAG, "sRollo.mState.zoomTarget=" + mState.zoomTarget); - Log.d(TAG, "sRollo.mState.homeButtonId=" + mState.homeButtonId); - Log.d(TAG, "sRollo.mState.targetPos=" + mState.targetPos); - Log.d(TAG, "sRollo.mParams.bubbleWidth=" + mParams.bubbleWidth); - Log.d(TAG, "sRollo.mParams.bubbleHeight=" + mParams.bubbleHeight); - Log.d(TAG, "sRollo.mParams.bubbleBitmapWidth=" + mParams.bubbleBitmapWidth); - Log.d(TAG, "sRollo.mParams.bubbleBitmapHeight=" + mParams.bubbleBitmapHeight); - Log.d(TAG, "sRollo.mParams.homeButtonWidth=" + mParams.homeButtonWidth); - Log.d(TAG, "sRollo.mParams.homeButtonHeight=" + mParams.homeButtonHeight); - Log.d(TAG, "sRollo.mParams.homeButtonTextureWidth=" + mParams.homeButtonTextureWidth); - Log.d(TAG, "sRollo.mParams.homeButtonTextureHeight=" + mParams.homeButtonTextureHeight); + //Log.d(TAG, "sRollo.mState.newPositionX=" + mState.newPositionX); + //Log.d(TAG, "sRollo.mState.newTouchDown=" + mState.newTouchDown); + //Log.d(TAG, "sRollo.mState.flingVelocity=" + mState.flingVelocity); + //Log.d(TAG, "sRollo.mState.iconCount=" + mState.iconCount); + //Log.d(TAG, "sRollo.mState.selectedIconIndex=" + mState.selectedIconIndex); + //Log.d(TAG, "sRollo.mState.selectedIconTexture=" + mState.selectedIconTexture); + //Log.d(TAG, "sRollo.mState.zoomTarget=" + mState.zoomTarget); + //Log.d(TAG, "sRollo.mState.homeButtonId=" + mState.homeButtonId); + //Log.d(TAG, "sRollo.mState.targetPos=" + mState.targetPos); } } @@ -1646,5 +1502,3 @@ public class AllApps3D extends RSSurfaceView } } } - - diff --git a/src/com/android/launcher2/AllAppsList.java b/src/com/android/launcher2/AllAppsList.java index 3a5baea..4c9bc5e 100644 --- a/src/com/android/launcher2/AllAppsList.java +++ b/src/com/android/launcher2/AllAppsList.java @@ -16,17 +16,16 @@ package com.android.launcher2; +import java.util.ArrayList; +import java.util.List; + import android.content.ComponentName; -import android.content.Intent; import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - /** * Stores the list of all applications for the all apps view. @@ -92,7 +91,7 @@ class AllAppsList { if (matches.size() > 0) { for (ResolveInfo info : matches) { - add(new ApplicationInfo(info, mIconCache)); + add(new ApplicationInfo(context.getPackageManager(), info, mIconCache)); } } } @@ -143,7 +142,7 @@ class AllAppsList { info.activityInfo.applicationInfo.packageName, info.activityInfo.name); if (applicationInfo == null) { - add(new ApplicationInfo(info, mIconCache)); + add(new ApplicationInfo(context.getPackageManager(), info, mIconCache)); } else { mIconCache.remove(applicationInfo.componentName); mIconCache.getTitleAndIcon(applicationInfo, info); diff --git a/src/com/android/launcher2/AllAppsPagedView.java b/src/com/android/launcher2/AllAppsPagedView.java new file mode 100644 index 0000000..351384f --- /dev/null +++ b/src/com/android/launcher2/AllAppsPagedView.java @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; + +import android.content.ComponentName; +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.widget.Checkable; +import android.widget.TextView; + +import com.android.launcher.R; + +/** + * An implementation of PagedView that populates the pages of the workspace + * with all of the user's applications. + */ +public class AllAppsPagedView extends PagedView + implements AllAppsView, View.OnClickListener, View.OnLongClickListener, DragSource, + DropTarget, ActionMode.Callback { + + private static final String TAG = "AllAppsPagedView"; + private static final boolean DEBUG = false; + + private static final int MENU_DELETE_APP = 1; + private static final int MENU_APP_INFO = 2; + + private Launcher mLauncher; + private DragController mDragController; + + // preserve compatibility with 3D all apps: + // 0.0 -> hidden + // 1.0 -> shown and opaque + // intermediate values -> partially shown & partially opaque + private float mZoom; + + // set of all applications + private ArrayList<ApplicationInfo> mApps; + private ArrayList<ApplicationInfo> mFilteredApps; + + // the types of applications to filter + static final int ALL_APPS_FLAG = -1; + private int mAppFilter = ALL_APPS_FLAG; + + private final LayoutInflater mInflater; + + private ViewGroup mOrigInfoButtonParent; + private LayoutParams mOrigInfoButtonLayoutParams; + + private ViewGroup mOrigDeleteZoneParent; + private LayoutParams mOrigDeleteZoneLayoutParams; + + public AllAppsPagedView(Context context) { + this(context, null); + } + + public AllAppsPagedView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AllAppsPagedView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, defStyle, 0); + mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 6); + mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4); + mInflater = LayoutInflater.from(context); + a.recycle(); + setSoundEffectsEnabled(false); + } + + @Override + protected void init() { + super.init(); + mCenterPagesVertically = false; + } + + @Override + public void setLauncher(Launcher launcher) { + mLauncher = launcher; + mLauncher.setAllAppsPagedView(this); + } + + @Override + public void setDragController(DragController dragger) { + mDragController = dragger; + } + + public void setAppFilter(int filterType) { + mAppFilter = filterType; + if (mApps != null) { + mFilteredApps = rebuildFilteredApps(mApps); + setCurrentPage(0); + invalidatePageData(); + } + } + + @Override + public void zoom(float zoom, boolean animate) { + mZoom = zoom; + cancelLongPress(); + + if (isVisible()) { + getParent().bringChildToFront(this); + setVisibility(View.VISIBLE); + if (animate) { + startAnimation(AnimationUtils.loadAnimation(getContext(), + R.anim.all_apps_2d_fade_in)); + } else { + onAnimationEnd(); + } + } else { + if (animate) { + startAnimation(AnimationUtils.loadAnimation(getContext(), + R.anim.all_apps_2d_fade_out)); + } else { + onAnimationEnd(); + } + } + } + + protected void onAnimationEnd() { + if (!isVisible()) { + setVisibility(View.GONE); + mZoom = 0.0f; + + endChoiceMode(); + } else { + mZoom = 1.0f; + } + + if (mLauncher != null) + mLauncher.zoomed(mZoom); + } + + private int getChildIndexForGrandChild(View v) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; ++i) { + final PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i); + if (layout.indexOfChild(v) > -1) { + return i; + } + } + return -1; + } + + @Override + public void onClick(View v) { + // if we are already in a choice mode, then just change the selection + if (v instanceof Checkable) { + if (!isChoiceMode(CHOICE_MODE_NONE)) { + Checkable c = (Checkable) v; + if (isChoiceMode(CHOICE_MODE_SINGLE)) { + // Uncheck all the other grandchildren, and toggle the clicked one + boolean wasChecked = c.isChecked(); + resetCheckedGrandchildren(); + c.setChecked(!wasChecked); + } else { + c.toggle(); + } + if (getCheckedGrandchildren().size() == 0) { + endChoiceMode(); + } + + return; + } + } + + // otherwise continue and launch the application + int childIndex = getChildIndexForGrandChild(v); + if (childIndex == getCurrentPage()) { + final ApplicationInfo app = (ApplicationInfo) v.getTag(); + + // animate some feedback to the click + animateClickFeedback(v, new Runnable() { + @Override + public void run() { + mLauncher.startActivitySafely(app.intent, app); + } + }); + + endChoiceMode(); + } + } + + @Override + public boolean onLongClick(View v) { + if (!v.isInTouchMode()) { + return false; + } + + if (v instanceof Checkable) { + // In preparation for drag, we always reset the checked grand children regardless of + // what choice mode we are in + resetCheckedGrandchildren(); + + // Toggle the selection on the dragged app + Checkable c = (Checkable) v; + c.toggle(); + } + + // Start choice mode AFTER the item is selected + if (isChoiceMode(CHOICE_MODE_NONE)) { + startChoiceMode(CHOICE_MODE_SINGLE, this); + } + + ApplicationInfo app = (ApplicationInfo) v.getTag(); + app = new ApplicationInfo(app); + + mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1); + mDragController.startDrag(v, this, app, DragController.DRAG_ACTION_COPY); + return true; + } + + @Override + public void onDropCompleted(View target, boolean success) { + // close the choice action mode if we have a proper drop + if (target != this) { + endChoiceMode(); + } + mLauncher.getWorkspace().onDragStopped(); + } + + @Override + public boolean isVisible() { + return mZoom > 0.001f; + } + + @Override + public boolean isAnimating() { + return (getAnimation() != null); + } + + private ArrayList<ApplicationInfo> rebuildFilteredApps(ArrayList<ApplicationInfo> apps) { + ArrayList<ApplicationInfo> filteredApps = new ArrayList<ApplicationInfo>(); + if (mAppFilter == ALL_APPS_FLAG) { + return apps; + } else { + final int length = apps.size(); + for (int i = 0; i < length; ++i) { + ApplicationInfo info = apps.get(i); + if ((info.flags & mAppFilter) > 0) { + filteredApps.add(info); + } + } + } + return filteredApps; + } + + @Override + public void setApps(ArrayList<ApplicationInfo> list) { + mApps = list; + Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR); + mFilteredApps = rebuildFilteredApps(mApps); + mPageViewIconCache.clear(); + invalidatePageData(); + } + + private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { + // we add it in place, in alphabetical order + final int count = list.size(); + for (int i = 0; i < count; ++i) { + final ApplicationInfo info = list.get(i); + final int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR); + if (index < 0) { + mApps.add(-(index + 1), info); + } + } + mFilteredApps = rebuildFilteredApps(mApps); + } + @Override + public void addApps(ArrayList<ApplicationInfo> list) { + addAppsWithoutInvalidate(list); + invalidatePageData(); + } + + private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { + // End the choice mode if any of the items in the list that are being removed are + // currently selected + ArrayList<Checkable> checkedList = getCheckedGrandchildren(); + HashSet<ApplicationInfo> checkedAppInfos = new HashSet<ApplicationInfo>(); + for (Checkable checked : checkedList) { + PagedViewIcon icon = (PagedViewIcon) checked; + checkedAppInfos.add((ApplicationInfo) icon.getTag()); + } + for (ApplicationInfo info : list) { + if (checkedAppInfos.contains(info)) { + endChoiceMode(); + break; + } + } + + // Loop through all the apps and remove apps that have the same component + final int length = list.size(); + for (int i = 0; i < length; ++i) { + final ApplicationInfo info = list.get(i); + int removeIndex = findAppByComponent(mApps, info); + if (removeIndex > -1) { + mApps.remove(removeIndex); + mPageViewIconCache.removeOutline(info); + } + } + mFilteredApps = rebuildFilteredApps(mApps); + } + @Override + public void removeApps(ArrayList<ApplicationInfo> list) { + removeAppsWithoutInvalidate(list); + invalidatePageData(); + } + + @Override + public void updateApps(ArrayList<ApplicationInfo> list) { + removeAppsWithoutInvalidate(list); + addAppsWithoutInvalidate(list); + invalidatePageData(); + } + + private int findAppByComponent(ArrayList<ApplicationInfo> list, ApplicationInfo item) { + ComponentName removeComponent = item.intent.getComponent(); + final int length = list.size(); + for (int i = 0; i < length; ++i) { + ApplicationInfo info = list.get(i); + if (info.intent.getComponent().equals(removeComponent)) { + return i; + } + } + return -1; + } + + @Override + public void dumpState() { + ApplicationInfo.dumpApplicationInfoList(TAG, "mApps", mApps); + } + + @Override + public void surrender() { + // do nothing? + } + + @Override + public void syncPages() { + // ensure that we have the right number of pages (min of 1, since we have placeholders) + int numPages = Math.max(1, + (int) Math.ceil((float) mFilteredApps.size() / (mCellCountX * mCellCountY))); + int curNumPages = getChildCount(); + // remove any extra pages after the "last" page + int extraPageDiff = curNumPages - numPages; + for (int i = 0; i < extraPageDiff; ++i) { + removeViewAt(numPages); + } + // add any necessary pages + for (int i = curNumPages; i < numPages; ++i) { + PagedViewCellLayout layout = new PagedViewCellLayout(getContext()); + layout.setCellCount(mCellCountX, mCellCountY); + layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, + mPageLayoutPaddingRight, mPageLayoutPaddingBottom); + layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); + addView(layout); + } + + // bound the current page + setCurrentPage(Math.max(0, Math.min(numPages - 1, getCurrentPage()))); + } + + @Override + public void syncPageItems(int page) { + // Ensure that we have the right number of items on the pages + final int cellsPerPage = mCellCountX * mCellCountY; + final int startIndex = page * cellsPerPage; + final int endIndex = Math.min(startIndex + cellsPerPage, mFilteredApps.size()); + PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page); + + if (!mFilteredApps.isEmpty()) { + int curNumPageItems = layout.getChildCount(); + int numPageItems = endIndex - startIndex; + + // If we were previously an empty page, then restart anew + boolean wasEmptyPage = false; + if (curNumPageItems == 1) { + View icon = layout.getChildAt(0); + if (icon.getTag() == null) { + wasEmptyPage = true; + } + } + + if (wasEmptyPage) { + // Remove all the previous items + curNumPageItems = 0; + layout.removeAllViews(); + } else { + // Remove any extra items + int extraPageItemsDiff = curNumPageItems - numPageItems; + for (int i = 0; i < extraPageItemsDiff; ++i) { + layout.removeViewAt(numPageItems); + } + } + + // Add any necessary items + for (int i = curNumPageItems; i < numPageItems; ++i) { + TextView text = (TextView) mInflater.inflate( + R.layout.all_apps_paged_view_application, layout, false); + text.setOnClickListener(this); + text.setOnLongClickListener(this); + + layout.addViewToCellLayout(text, -1, i, + new PagedViewCellLayout.LayoutParams(0, 0, 1, 1)); + } + + // Actually reapply to the existing text views + for (int i = startIndex; i < endIndex; ++i) { + final int index = i - startIndex; + final ApplicationInfo info = mFilteredApps.get(i); + PagedViewIcon icon = (PagedViewIcon) layout.getChildAt(index); + icon.applyFromApplicationInfo(info, mPageViewIconCache, true); + + PagedViewCellLayout.LayoutParams params = + (PagedViewCellLayout.LayoutParams) icon.getLayoutParams(); + params.cellX = index % mCellCountX; + params.cellY = index / mCellCountX; + } + + // Default to left-aligned icons + layout.enableCenteredContent(false); + } else { + // There are no items, so show the user a small message + TextView icon = (TextView) mInflater.inflate( + R.layout.all_apps_no_items_placeholder, layout, false); + switch (mAppFilter) { + case ApplicationInfo.DOWNLOADED_FLAG: + icon.setText(mContext.getString(R.string.all_apps_no_downloads)); + break; + default: break; + } + + // Center-align the message + layout.enableCenteredContent(true); + layout.removeAllViews(); + layout.addViewToCellLayout(icon, -1, 0, + new PagedViewCellLayout.LayoutParams(0, 0, 2, 1)); + } + } + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + mode.setTitle(R.string.cab_app_selection_text); + + // Until the workspace has a selection mode and the CAB supports drag-and-drop, we + // take a hybrid approach: grab the views from the workspace and stuff them into the CAB. + // When the action mode is done, restore the views to their original place in the toolbar. + + ApplicationInfoDropTarget infoButton = + (ApplicationInfoDropTarget) mLauncher.findViewById(R.id.info_button); + mOrigInfoButtonParent = (ViewGroup) infoButton.getParent(); + mOrigInfoButtonLayoutParams = infoButton.getLayoutParams(); + mOrigInfoButtonParent.removeView(infoButton); + infoButton.setManageVisibility(false); + infoButton.setVisibility(View.VISIBLE); + infoButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + final ApplicationInfo appInfo = (ApplicationInfo) getChosenItem(); + mLauncher.startApplicationDetailsActivity(appInfo.componentName); + } + }); + + DeleteZone deleteZone = (DeleteZone) mLauncher.findViewById(R.id.delete_zone); + mOrigDeleteZoneParent = (ViewGroup) deleteZone.getParent(); + mOrigDeleteZoneLayoutParams = deleteZone.getLayoutParams(); + mOrigDeleteZoneParent.removeView(deleteZone); + deleteZone.setManageVisibility(false); + deleteZone.setVisibility(View.VISIBLE); + deleteZone.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + final ApplicationInfo appInfo = (ApplicationInfo) getChosenItem(); + mLauncher.startApplicationUninstallActivity(appInfo); + } + }); + + menu.add(0, MENU_APP_INFO, 0, R.string.cab_menu_app_info).setActionView(infoButton); + menu.add(0, MENU_DELETE_APP, 0, R.string.cab_menu_delete_app).setActionView(deleteZone); + + return true; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mDragController.addDropTarget(this); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + final Menu menu = mode.getMenu(); + + // Re-parent the drop targets into the toolbar, and restore their layout params + + ApplicationInfoDropTarget infoButton = + (ApplicationInfoDropTarget) menu.findItem(MENU_APP_INFO).getActionView(); + ((ViewGroup) infoButton.getParent()).removeView(infoButton); + mOrigInfoButtonParent.addView(infoButton, mOrigInfoButtonLayoutParams); + infoButton.setVisibility(View.GONE); + infoButton.setManageVisibility(true); + + DeleteZone deleteZone = (DeleteZone) menu.findItem(MENU_DELETE_APP).getActionView(); + ((ViewGroup) deleteZone.getParent()).removeView(deleteZone); + mOrigDeleteZoneParent.addView(deleteZone, mOrigDeleteZoneLayoutParams); + deleteZone.setVisibility(View.GONE); + deleteZone.setManageVisibility(true); + + mDragController.removeDropTarget(this); + endChoiceMode(); + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // This is never called. Because we use setActionView(), we handle our own click events. + return false; + } + + /* + * We don't actually use AllAppsPagedView as a drop target... it's only used to intercept a drop + * to the workspace. + */ + @Override + public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + return false; + } + @Override + public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, + int yOffset, DragView dragView, Object dragInfo) { + return null; + } + @Override + public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) {} + @Override + public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) {} + @Override + public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) {} + @Override + public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) {} + + public boolean isDropEnabled() { + return true; + } +} diff --git a/src/com/android/launcher2/AllAppsTabbed.java b/src/com/android/launcher2/AllAppsTabbed.java new file mode 100644 index 0000000..e0ff1a8 --- /dev/null +++ b/src/com/android/launcher2/AllAppsTabbed.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import com.android.launcher.R; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.TabHost; + +import java.util.ArrayList; + +/** + * Implements a tabbed version of AllApps2D. + */ +public class AllAppsTabbed extends TabHost implements AllAppsView { + + private static final String TAG = "Launcher.AllAppsTabbed"; + + private static final String TAG_ALL = "ALL"; + private static final String TAG_DOWNLOADED = "DOWNLOADED"; + + private AllAppsPagedView mAllApps; + private Context mContext; + + public AllAppsTabbed(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + } + + @Override + protected void onFinishInflate() { + // setup the tab host + setup(); + + try { + mAllApps = (AllAppsPagedView) findViewById(R.id.all_apps_paged_view); + if (mAllApps == null) throw new Resources.NotFoundException(); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Can't find necessary layout elements for AllAppsTabbed"); + } + + // share the same AllApps workspace across all the tabs + TabContentFactory contentFactory = new TabContentFactory() { + public View createTabContent(String tag) { + return mAllApps; + } + }; + + String label = mContext.getString(R.string.all_apps_tab_all); + addTab(newTabSpec(TAG_ALL).setIndicator(label).setContent(contentFactory)); + + label = mContext.getString(R.string.all_apps_tab_downloaded); + addTab(newTabSpec(TAG_DOWNLOADED).setIndicator(label).setContent(contentFactory)); + + setOnTabChangedListener(new OnTabChangeListener() { + public void onTabChanged(String tabId) { + // animate the changing of the tab content by fading pages in and out + final int duration = 150; + final float alpha = mAllApps.getAlpha(); + ValueAnimator alphaAnim = ObjectAnimator.ofFloat(mAllApps, "alpha", alpha, 0.0f). + setDuration(duration); + alphaAnim.addListener(new LauncherAnimatorListenerAdapter() { + @Override + public void onAnimationEndOrCancel(Animator animation) { + String tag = getCurrentTabTag(); + if (tag == TAG_ALL) { + mAllApps.setAppFilter(AllAppsPagedView.ALL_APPS_FLAG); + } else if (tag == TAG_DOWNLOADED) { + mAllApps.setAppFilter(ApplicationInfo.DOWNLOADED_FLAG); + } + + final float alpha = mAllApps.getAlpha(); + ObjectAnimator.ofFloat(mAllApps, "alpha", alpha, 1.0f). + setDuration(duration).start(); + } + }); + alphaAnim.start(); + } + }); + + // It needs to be INVISIBLE so that it will be measured in the layout. + // Otherwise the animations is messed up when we show it for the first time. + setVisibility(INVISIBLE); + } + + @Override + public void setLauncher(Launcher launcher) { + mAllApps.setLauncher(launcher); + } + + @Override + public void setDragController(DragController dragger) { + mAllApps.setDragController(dragger); + } + + @Override + public void zoom(float zoom, boolean animate) { + // NOTE: animate parameter is ignored for the TabHost itself + setVisibility((zoom == 0.0f) ? View.GONE : View.VISIBLE); + mAllApps.zoom(zoom, animate); + } + + @Override + public void setVisibility(int visibility) { + final boolean isVisible = (visibility == View.VISIBLE); + super.setVisibility(visibility); + float zoom = (isVisible ? 1.0f : 0.0f); + mAllApps.zoom(zoom, false); + } + + @Override + public boolean isVisible() { + return mAllApps.isVisible(); + } + + @Override + public boolean isAnimating() { + return (getAnimation() != null); + } + + @Override + public void setApps(ArrayList<ApplicationInfo> list) { + mAllApps.setApps(list); + } + + @Override + public void addApps(ArrayList<ApplicationInfo> list) { + mAllApps.addApps(list); + } + + @Override + public void removeApps(ArrayList<ApplicationInfo> list) { + mAllApps.removeApps(list); + } + + @Override + public void updateApps(ArrayList<ApplicationInfo> list) { + mAllApps.updateApps(list); + } + + @Override + public void dumpState() { + mAllApps.dumpState(); + } + + @Override + public void surrender() { + mAllApps.surrender(); + } +} diff --git a/src/com/android/launcher2/AllAppsView.java b/src/com/android/launcher2/AllAppsView.java index 877c075..007ecf8 100644 --- a/src/com/android/launcher2/AllAppsView.java +++ b/src/com/android/launcher2/AllAppsView.java @@ -31,7 +31,7 @@ public interface AllAppsView { public boolean isVisible(); - public boolean isOpaque(); + public boolean isAnimating(); public void setApps(ArrayList<ApplicationInfo> list); diff --git a/src/com/android/launcher2/ApplicationInfo.java b/src/com/android/launcher2/ApplicationInfo.java index 5bb5037..0851cd3 100644 --- a/src/com/android/launcher2/ApplicationInfo.java +++ b/src/com/android/launcher2/ApplicationInfo.java @@ -17,12 +17,11 @@ package com.android.launcher2; import android.content.ComponentName; -import android.content.ContentValues; -import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; import android.util.Log; import java.util.ArrayList; @@ -31,6 +30,7 @@ import java.util.ArrayList; * Represents an app in AllAppsView. */ class ApplicationInfo extends ItemInfo { + private static final String TAG = "Launcher2.ApplicationInfo"; /** * The application name. @@ -54,6 +54,10 @@ class ApplicationInfo extends ItemInfo { ComponentName componentName; + static final int APP_FLAG = 1; + static final int GAME_FLAG = 2; + static final int DOWNLOADED_FLAG = 4; + int flags = 0; ApplicationInfo() { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; @@ -62,15 +66,32 @@ class ApplicationInfo extends ItemInfo { /** * Must not hold the Context. */ - public ApplicationInfo(ResolveInfo info, IconCache iconCache) { - this.componentName = new ComponentName( - info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); + public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache) { + final String packageName = info.activityInfo.applicationInfo.packageName; + this.componentName = new ComponentName(packageName, info.activityInfo.name); this.container = ItemInfo.NO_ID; this.setActivity(componentName, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + try { + int appFlags = pm.getApplicationInfo(packageName, 0).flags; + if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) { + flags |= DOWNLOADED_FLAG; + } + if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + flags |= DOWNLOADED_FLAG; + } + // TODO: Figure out how to determine what is a game + + // If it's not a game, it's an app + if ((flags & GAME_FLAG) == 0) { + flags |= APP_FLAG; + } + } catch (NameNotFoundException e) { + Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName); + } + iconCache.getTitleAndIcon(this, info); } @@ -79,6 +100,7 @@ class ApplicationInfo extends ItemInfo { componentName = info.componentName; title = info.title.toString(); intent = new Intent(info.intent); + flags = info.flags; } /** diff --git a/src/com/android/launcher2/ApplicationInfoDropTarget.java b/src/com/android/launcher2/ApplicationInfoDropTarget.java new file mode 100644 index 0000000..3e5ebd5 --- /dev/null +++ b/src/com/android/launcher2/ApplicationInfoDropTarget.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.content.ComponentName; +import android.content.Context; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +/** + * Implements a DropTarget which allows applications to be dropped on it, + * in order to launch the application info for that app. + */ +public class ApplicationInfoDropTarget extends ImageView implements DropTarget, DragController.DragListener { + private Launcher mLauncher; + private boolean mActive = false; + + /** + * If true, this View responsible for managing its own visibility, and that of its handle. + * This is generally the case, but it will be set to false when this is part of the + * Contextual Action Bar. + */ + private boolean mManageVisibility = true; + + /** The view that this view should appear in the place of. */ + private View mHandle = null; + + private final Paint mPaint = new Paint(); + + public ApplicationInfoDropTarget(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ApplicationInfoDropTarget(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Set the color that will be used as a filter over objects dragged over this object. + */ + public void setDragColor(int color) { + mPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); + } + + public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + + // acceptDrop is called just before onDrop. We do the work here, rather than + // in onDrop, because it allows us to reject the drop (by returning false) + // so that the object being dragged isn't removed from the home screen. + + ComponentName componentName = null; + if (dragInfo instanceof ApplicationInfo) { + componentName = ((ApplicationInfo)dragInfo).componentName; + } else if (dragInfo instanceof ShortcutInfo) { + componentName = ((ShortcutInfo)dragInfo).intent.getComponent(); + } + mLauncher.startApplicationDetailsActivity(componentName); + return false; + } + + public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + + } + + public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + dragView.setPaint(mPaint); + } + + public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + } + + public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + dragView.setPaint(null); + } + + public void onDragStart(DragSource source, Object info, int dragAction) { + if (info != null) { + final int itemType = ((ItemInfo)info).itemType; + mActive = (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION); + if (mManageVisibility) { + // Only show the info icon when an application is selected + if (mActive) { + setVisibility(VISIBLE); + } + mHandle.setVisibility(INVISIBLE); + } + } + } + + public boolean isDropEnabled() { + return mActive; + } + + public void onDragEnd() { + if (mActive) { + mActive = false; + } + if (mManageVisibility) { + setVisibility(GONE); + mHandle.setVisibility(VISIBLE); + } + } + + @Override + public void getHitRect(Rect outRect) { + super.getHitRect(outRect); + if (LauncherApplication.isScreenXLarge()) { + // TODO: This is a temporary hack. mManageVisiblity = false when you're in CAB mode. + // In that case, this icon is more tightly spaced next to the delete icon so we want + // it to have a smaller drag region. When the new drag&drop system comes in, we'll + // dispatch the drag/drop by calculating what target you're overlapping + final int dragPadding = mManageVisibility ? 50 : 10; + outRect.top -= dragPadding; + outRect.left -= dragPadding; + outRect.bottom += dragPadding; + outRect.right += dragPadding; + } + } + + void setLauncher(Launcher launcher) { + mLauncher = launcher; + } + + void setHandle(View view) { + mHandle = view; + } + + void setManageVisibility(boolean value) { + mManageVisibility = value; + } + + @Override + public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + return null; + } +} diff --git a/src/com/android/launcher2/BubbleTextView.java b/src/com/android/launcher2/BubbleTextView.java index 4a56e1b..076f574 100644 --- a/src/com/android/launcher2/BubbleTextView.java +++ b/src/com/android/launcher2/BubbleTextView.java @@ -144,4 +144,10 @@ public class BubbleTextView extends TextView { super.onDetachedFromWindow(); mBackground.setCallback(null); } + + @Override + protected boolean onSetAlpha(int alpha) { + mPaint.setAlpha(alpha); + return super.onSetAlpha(alpha); + } } diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java index 9d39c2c..11c0c18 100644 --- a/src/com/android/launcher2/CellLayout.java +++ b/src/com/android/launcher2/CellLayout.java @@ -16,54 +16,114 @@ package com.android.launcher2; +import com.android.launcher.R; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.app.WallpaperManager; import android.content.Context; -import android.content.res.TypedArray; import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.Canvas; +import android.graphics.Region; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.Log; import android.view.ContextMenu; import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; -import android.app.WallpaperManager; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LayoutAnimationController; -import java.util.ArrayList; +import java.util.Arrays; -import com.android.launcher.R; - -public class CellLayout extends ViewGroup { - private boolean mPortrait; +public class CellLayout extends ViewGroup implements Dimmable { + static final String TAG = "CellLayout"; private int mCellWidth; private int mCellHeight; - - private int mLongAxisStartPadding; - private int mLongAxisEndPadding; - private int mShortAxisStartPadding; - private int mShortAxisEndPadding; + private int mLeftPadding; + private int mRightPadding; + private int mTopPadding; + private int mBottomPadding; - private int mShortAxisCells; - private int mLongAxisCells; + private int mCountX; + private int mCountY; private int mWidthGap; private int mHeightGap; private final Rect mRect = new Rect(); + private final RectF mRectF = new RectF(); private final CellInfo mCellInfo = new CellInfo(); - - int[] mCellXY = new int[2]; + + // These are temporary variables to prevent having to allocate a new object just to + // return an (x, y) value from helper functions. Do NOT use them to maintain other state. + private final int[] mTmpCellXY = new int[2]; + private final int[] mTmpPoint = new int[2]; + private final PointF mTmpPointF = new PointF(); + boolean[][] mOccupied; - private RectF mDragRect = new RectF(); + private OnTouchListener mInterceptTouchListener; + + private float mBackgroundAlpha; + private float mBackgroundAlphaMultiplier = 1.0f; + + private Drawable mBackground; + private Drawable mBackgroundMini; + private Drawable mBackgroundMiniHover; + private Drawable mBackgroundHover; + private Drawable mBackgroundMiniAcceptsDrops; + private Rect mBackgroundRect; + private Rect mHoverRect; + private float mHoverScale; + private float mHoverAlpha; + private boolean mAcceptsDrops; + + // If we're actively dragging something over this screen, mHover is true + private boolean mHover = false; + + private final Point mDragCenter = new Point(); - private boolean mDirtyTag; - private boolean mLastDownOnOccupiedCell = false; - - private final WallpaperManager mWallpaperManager; + // These arrays are used to implement the drag visualization on x-large screens. + // They are used as circular arrays, indexed by mDragOutlineCurrent. + private Point[] mDragOutlines = new Point[8]; + private float[] mDragOutlineAlphas = new float[mDragOutlines.length]; + private InterruptibleInOutAnimator[] mDragOutlineAnims = + new InterruptibleInOutAnimator[mDragOutlines.length]; + + // Used as an index into the above 3 arrays; indicates which is the most current value. + private int mDragOutlineCurrent = 0; + private final Paint mDragOutlinePaint = new Paint(); + + private Drawable mCrosshairsDrawable = null; + private InterruptibleInOutAnimator mCrosshairsAnimator = null; + private float mCrosshairsVisibility = 0.0f; + + // When a drag operation is in progress, holds the nearest cell to the touch point + private final int[] mDragCell = new int[2]; + + private final WallpaperManager mWallpaperManager; + + private boolean mDragging = false; + + private TimeInterpolator mEaseOutInterpolator; public CellLayout(Context context) { this(context, null); @@ -75,44 +135,309 @@ public class CellLayout extends ViewGroup { public CellLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + + // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show + // the user where a dragged item will land when dropped. + setWillNotDraw(false); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0); mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10); mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10); - - mLongAxisStartPadding = - a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10); - mLongAxisEndPadding = - a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10); - mShortAxisStartPadding = - a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10); - mShortAxisEndPadding = - a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10); - - mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4); - mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4); + mWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, -1); + mHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, -1); + + mLeftPadding = + a.getDimensionPixelSize(R.styleable.CellLayout_xAxisStartPadding, 10); + mRightPadding = + a.getDimensionPixelSize(R.styleable.CellLayout_xAxisEndPadding, 10); + mTopPadding = + a.getDimensionPixelSize(R.styleable.CellLayout_yAxisStartPadding, 10); + mBottomPadding = + a.getDimensionPixelSize(R.styleable.CellLayout_yAxisEndPadding, 10); + + mCountX = LauncherModel.getCellCountX(); + mCountY = LauncherModel.getCellCountY(); + mOccupied = new boolean[mCountX][mCountY]; a.recycle(); setAlwaysDrawnWithCacheEnabled(false); - if (mOccupied == null) { - if (mPortrait) { - mOccupied = new boolean[mShortAxisCells][mLongAxisCells]; - } else { - mOccupied = new boolean[mLongAxisCells][mShortAxisCells]; + mWallpaperManager = WallpaperManager.getInstance(context); + + final Resources res = getResources(); + + if (LauncherApplication.isScreenXLarge()) { + mBackgroundMini = res.getDrawable(R.drawable.mini_home_screen_bg); + mBackgroundMini.setFilterBitmap(true); + mBackground = res.getDrawable(R.drawable.home_screen_bg); + mBackground.setFilterBitmap(true); + mBackgroundMiniHover = res.getDrawable(R.drawable.mini_home_screen_bg_hover); + mBackgroundMiniHover.setFilterBitmap(true); + mBackgroundHover = res.getDrawable(R.drawable.home_screen_bg_hover); + mBackgroundHover.setFilterBitmap(true); + mBackgroundMiniAcceptsDrops = res.getDrawable( + R.drawable.mini_home_screen_bg_accepts_drops); + mBackgroundMiniAcceptsDrops.setFilterBitmap(true); + } + + // Initialize the data structures used for the drag visualization. + + mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs); + mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out + + // Set up the animation for fading the crosshairs in and out + int animDuration = res.getInteger(R.integer.config_crosshairsFadeInTime); + mCrosshairsAnimator = new InterruptibleInOutAnimator(animDuration, 0.0f, 1.0f); + mCrosshairsAnimator.getAnimator().addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + mCrosshairsVisibility = ((Float) animation.getAnimatedValue()).floatValue(); + invalidate(); } + }); + mCrosshairsAnimator.getAnimator().setInterpolator(mEaseOutInterpolator); + + for (int i = 0; i < mDragOutlines.length; i++) { + mDragOutlines[i] = new Point(-1, -1); } - - mWallpaperManager = WallpaperManager.getInstance(getContext()); + + // When dragging things around the home screens, we show a green outline of + // where the item will land. The outlines gradually fade out, leaving a trail + // behind the drag path. + // Set up all the animations that are used to implement this fading. + final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime); + final float fromAlphaValue = 0; + final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha); + + Arrays.fill(mDragOutlineAlphas, fromAlphaValue); + + for (int i = 0; i < mDragOutlineAnims.length; i++) { + final InterruptibleInOutAnimator anim = + new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue); + anim.getAnimator().setInterpolator(mEaseOutInterpolator); + final int thisIndex = i; + anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + final Bitmap outline = (Bitmap)anim.getTag(); + + // If an animation is started and then stopped very quickly, we can still + // get spurious updates we've cleared the tag. Guard against this. + if (outline == null) { + if (false) { + Object val = animation.getAnimatedValue(); + Log.d(TAG, "anim " + thisIndex + " update: " + val + + ", isStopped " + anim.isStopped()); + } + // Try to prevent it from continuing to run + animation.cancel(); + } else { + mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue(); + final int left = mDragOutlines[thisIndex].x; + final int top = mDragOutlines[thisIndex].y; + CellLayout.this.invalidate(left, top, + left + outline.getWidth(), top + outline.getHeight()); + } + } + }); + // The animation holds a reference to the drag outline bitmap as long is it's + // running. This way the bitmap can be GCed when the animations are complete. + anim.getAnimator().addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) { + anim.setTag(null); + } + } + }); + mDragOutlineAnims[i] = anim; + } + + mBackgroundRect = new Rect(); + mHoverRect = new Rect(); + setHoverScale(1.0f); + setHoverAlpha(1.0f); } - @Override - public void dispatchDraw(Canvas canvas) { + private void updateHoverRect() { + float marginFraction = (mHoverScale - 1.0f) / 2.0f; + int marginX = (int) (marginFraction * (mBackgroundRect.right - mBackgroundRect.left)); + int marginY = (int) (marginFraction * (mBackgroundRect.bottom - mBackgroundRect.top)); + mHoverRect.set(mBackgroundRect.left - marginX, mBackgroundRect.top - marginY, + mBackgroundRect.right + marginX, mBackgroundRect.bottom + marginY); + invalidate(); + } + + public void setHoverScale(float scaleFactor) { + if (scaleFactor != mHoverScale) { + mHoverScale = scaleFactor; + updateHoverRect(); + } + } + + public float getHoverScale() { + return mHoverScale; + } + + public float getHoverAlpha() { + return mHoverAlpha; + } + + public void setHoverAlpha(float alpha) { + mHoverAlpha = alpha; + invalidate(); + } + + void animateDrop() { + if (LauncherApplication.isScreenXLarge()) { + Resources res = getResources(); + float onDropScale = res.getInteger(R.integer.config_screenOnDropScalePercent) / 100.0f; + ObjectAnimator scaleUp = ObjectAnimator.ofFloat(this, "hoverScale", onDropScale); + scaleUp.setDuration(res.getInteger(R.integer.config_screenOnDropScaleUpDuration)); + ObjectAnimator scaleDown = ObjectAnimator.ofFloat(this, "hoverScale", 1.0f); + scaleDown.setDuration(res.getInteger(R.integer.config_screenOnDropScaleDownDuration)); + ObjectAnimator alphaFadeOut = ObjectAnimator.ofFloat(this, "hoverAlpha", 0.0f); + + alphaFadeOut.setStartDelay(res.getInteger(R.integer.config_screenOnDropAlphaFadeDelay)); + alphaFadeOut.setDuration(res.getInteger(R.integer.config_screenOnDropAlphaFadeDelay)); + + AnimatorSet bouncer = new AnimatorSet(); + bouncer.play(scaleUp).before(scaleDown); + bouncer.play(scaleUp).with(alphaFadeOut); + bouncer.addListener(new LauncherAnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + setHover(true); + } + @Override + public void onAnimationEndOrCancel(Animator animation) { + setHover(false); + setHoverScale(1.0f); + setHoverAlpha(1.0f); + } + }); + bouncer.start(); + } + } + + public void setHover(boolean value) { + if (mHover != value) { + mHover = value; + invalidate(); + } + } + + public boolean getHover() { + return mHover; + } + + public void drawChildren(Canvas canvas) { super.dispatchDraw(canvas); } @Override + protected void onDraw(Canvas canvas) { + // When we're large, we are either drawn in a "hover" state (ie when dragging an item to + // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f) + // When we're small, we are either drawn normally or in the "accepts drops" state (during + // a drag). However, we also drag the mini hover background *over* one of those two + // backgrounds + if (mBackgroundAlpha > 0.0f) { + Drawable bg; + if (getScaleX() < 0.5f) { + bg = mAcceptsDrops ? mBackgroundMiniAcceptsDrops : mBackgroundMini; + } else { + bg = mHover ? mBackgroundHover : mBackground; + } + if (bg != null) { + bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255)); + bg.setBounds(mBackgroundRect); + bg.draw(canvas); + } + if (mHover && getScaleX() < 0.5f) { + boolean modifiedClipRect = false; + if (mHoverScale > 1.0f) { + // If the hover background's scale is greater than 1, we'll be drawing outside + // the bounds of this CellLayout. Get around that by temporarily increasing the + // size of the clip rect + float marginFraction = (mHoverScale - 1.0f) / 2.0f; + Rect clipRect = canvas.getClipBounds(); + int marginX = (int) (marginFraction * (clipRect.right - clipRect.left)); + int marginY = (int) (marginFraction * (clipRect.bottom - clipRect.top)); + canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipRect(-marginX, -marginY, + getWidth() + marginX, getHeight() + marginY, Region.Op.REPLACE); + modifiedClipRect = true; + } + + mBackgroundMiniHover.setAlpha((int) (mBackgroundAlpha * mHoverAlpha * 255)); + mBackgroundMiniHover.setBounds(mHoverRect); + mBackgroundMiniHover.draw(canvas); + if (modifiedClipRect) { + canvas.restore(); + } + } + } + + if (mCrosshairsVisibility > 0.0f) { + final int countX = mCountX; + final int countY = mCountY; + + final float MAX_ALPHA = 0.4f; + final int MAX_VISIBLE_DISTANCE = 600; + final float DISTANCE_MULTIPLIER = 0.002f; + + final Drawable d = mCrosshairsDrawable; + final int width = d.getIntrinsicWidth(); + final int height = d.getIntrinsicHeight(); + + int x = getLeftPadding() - (mWidthGap / 2) - (width / 2); + for (int col = 0; col <= countX; col++) { + int y = getTopPadding() - (mHeightGap / 2) - (height / 2); + for (int row = 0; row <= countY; row++) { + mTmpPointF.set(x - mDragCenter.x, y - mDragCenter.y); + float dist = mTmpPointF.length(); + // Crosshairs further from the drag point are more faint + float alpha = Math.min(MAX_ALPHA, + DISTANCE_MULTIPLIER * (MAX_VISIBLE_DISTANCE - dist)); + if (alpha > 0.0f) { + d.setBounds(x, y, x + width, y + height); + d.setAlpha((int) (alpha * 255 * mCrosshairsVisibility)); + d.draw(canvas); + } + y += mCellHeight + mHeightGap; + } + x += mCellWidth + mWidthGap; + } + } + + final Paint paint = mDragOutlinePaint; + for (int i = 0; i < mDragOutlines.length; i++) { + final float alpha = mDragOutlineAlphas[i]; + if (alpha > 0) { + final Point p = mDragOutlines[i]; + final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag(); + paint.setAlpha((int)(alpha + .5f)); + canvas.drawBitmap(b, p.x, p.y, paint); + } + } + } + + public void setDimmableProgress(float progress) { + for (int i = 0; i < getChildCount(); i++) { + Dimmable d = (Dimmable) getChildAt(i); + d.setDimmableProgress(progress); + } + } + + public float getDimmableProgress() { + if (getChildCount() > 0) { + return ((Dimmable) getChildAt(0)).getDimmableProgress(); + } + return 0.0f; + } + + @Override public void cancelLongPress() { super.cancelLongPress(); @@ -124,22 +449,104 @@ public class CellLayout extends ViewGroup { } } + public void setOnInterceptTouchListener(View.OnTouchListener listener) { + mInterceptTouchListener = listener; + } + int getCountX() { - return mPortrait ? mShortAxisCells : mLongAxisCells; + return mCountX; } int getCountY() { - return mPortrait ? mLongAxisCells : mShortAxisCells; + return mCountY; } - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { + public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) { + return addViewToCellLayout(child, index, childId, params, true); + } + + public boolean addViewToCellLayout( + View child, int index, int childId, LayoutParams params, boolean markCells) { + final LayoutParams lp = params; + // Generate an id for each view, this assumes we have at most 256x256 cells // per workspace screen - final LayoutParams cellParams = (LayoutParams) params; - cellParams.regenerateId = true; + if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) { + // If the horizontal or vertical span is set to -1, it is taken to + // mean that it spans the extent of the CellLayout + if (lp.cellHSpan < 0) lp.cellHSpan = mCountX; + if (lp.cellVSpan < 0) lp.cellVSpan = mCountY; + + child.setId(childId); + + // We might be in the middle or end of shrinking/fading to a dimmed view + // Make sure this view's alpha is set the same as all the rest of the views + child.setAlpha(getAlpha()); + addView(child, index, lp); + + if (markCells) markCellsAsOccupiedForView(child); + + return true; + } + return false; + } + public void setAcceptsDrops(boolean acceptsDrops) { + if (mAcceptsDrops != acceptsDrops) { + mAcceptsDrops = acceptsDrops; + invalidate(); + } + } + + public boolean getAcceptsDrops() { + return mAcceptsDrops; + } + + @Override + public void removeAllViews() { + clearOccupiedCells(); + } + + @Override + public void removeAllViewsInLayout() { + clearOccupiedCells(); + } + + public void removeViewWithoutMarkingCells(View view) { + super.removeView(view); + } + + @Override + public void removeView(View view) { + markCellsAsUnoccupiedForView(view); + super.removeView(view); + } + + @Override + public void removeViewAt(int index) { + markCellsAsUnoccupiedForView(getChildAt(index)); + super.removeViewAt(index); + } + + @Override + public void removeViewInLayout(View view) { + markCellsAsUnoccupiedForView(view); + super.removeViewInLayout(view); + } - super.addView(child, index, params); + @Override + public void removeViews(int start, int count) { + for (int i = start; i < start + count; i++) { + markCellsAsUnoccupiedForView(getChildAt(i)); + } + super.removeViews(start, count); + } + + @Override + public void removeViewsInLayout(int start, int count) { + for (int i = start; i < start + count; i++) { + markCellsAsUnoccupiedForView(getChildAt(i)); + } + super.removeViewsInLayout(start, count); } @Override @@ -158,67 +565,59 @@ public class CellLayout extends ViewGroup { mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this); } + public void setTagToCellInfoForPoint(int touchX, int touchY) { + final CellInfo cellInfo = mCellInfo; + final Rect frame = mRect; + final int x = touchX + mScrollX; + final int y = touchY + mScrollY; + final int count = getChildCount(); + + boolean found = false; + for (int i = count - 1; i >= 0; i--) { + final View child = getChildAt(i); + + if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) { + child.getHitRect(frame); + if (frame.contains(x, y)) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + cellInfo.cell = child; + cellInfo.cellX = lp.cellX; + cellInfo.cellY = lp.cellY; + cellInfo.spanX = lp.cellHSpan; + cellInfo.spanY = lp.cellVSpan; + cellInfo.valid = true; + found = true; + break; + } + } + } + + if (!found) { + final int cellXY[] = mTmpCellXY; + pointToCellExact(x, y, cellXY); + + cellInfo.cell = null; + cellInfo.cellX = cellXY[0]; + cellInfo.cellY = cellXY[1]; + cellInfo.spanX = 1; + cellInfo.spanY = 1; + cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < mCountX && + cellXY[1] < mCountY && !mOccupied[cellXY[0]][cellXY[1]]; + } + setTag(cellInfo); + } + + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) { + return true; + } final int action = ev.getAction(); final CellInfo cellInfo = mCellInfo; if (action == MotionEvent.ACTION_DOWN) { - final Rect frame = mRect; - final int x = (int) ev.getX() + mScrollX; - final int y = (int) ev.getY() + mScrollY; - final int count = getChildCount(); - - boolean found = false; - for (int i = count - 1; i >= 0; i--) { - final View child = getChildAt(i); - - if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) { - child.getHitRect(frame); - if (frame.contains(x, y)) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - cellInfo.cell = child; - cellInfo.cellX = lp.cellX; - cellInfo.cellY = lp.cellY; - cellInfo.spanX = lp.cellHSpan; - cellInfo.spanY = lp.cellVSpan; - cellInfo.valid = true; - found = true; - mDirtyTag = false; - break; - } - } - } - - mLastDownOnOccupiedCell = found; - - if (!found) { - int cellXY[] = mCellXY; - pointToCellExact(x, y, cellXY); - - final boolean portrait = mPortrait; - final int xCount = portrait ? mShortAxisCells : mLongAxisCells; - final int yCount = portrait ? mLongAxisCells : mShortAxisCells; - - final boolean[][] occupied = mOccupied; - findOccupiedCells(xCount, yCount, occupied, null); - - cellInfo.cell = null; - cellInfo.cellX = cellXY[0]; - cellInfo.cellY = cellXY[1]; - cellInfo.spanX = 1; - cellInfo.spanY = 1; - cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount && - cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]]; - - // Instead of finding the interesting vacant cells here, wait until a - // caller invokes getTag() to retrieve the result. Finding the vacant - // cells is a bit expensive and can generate many new objects, it's - // therefore better to defer it until we know we actually need it. - - mDirtyTag = true; - } - setTag(cellInfo); + setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY()); } else if (action == MotionEvent.ACTION_UP) { cellInfo.cell = null; cellInfo.cellX = -1; @@ -226,7 +625,6 @@ public class CellLayout extends ViewGroup { cellInfo.spanX = 0; cellInfo.spanY = 0; cellInfo.valid = false; - mDirtyTag = false; setTag(cellInfo); } @@ -235,104 +633,12 @@ public class CellLayout extends ViewGroup { @Override public CellInfo getTag() { - final CellInfo info = (CellInfo) super.getTag(); - if (mDirtyTag && info.valid) { - final boolean portrait = mPortrait; - final int xCount = portrait ? mShortAxisCells : mLongAxisCells; - final int yCount = portrait ? mLongAxisCells : mShortAxisCells; - - final boolean[][] occupied = mOccupied; - findOccupiedCells(xCount, yCount, occupied, null); - - findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied); - - mDirtyTag = false; - } - return info; - } - - private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y, - int xCount, int yCount, boolean[][] occupied) { - - cellInfo.maxVacantSpanX = Integer.MIN_VALUE; - cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE; - cellInfo.maxVacantSpanY = Integer.MIN_VALUE; - cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE; - cellInfo.clearVacantCells(); - - if (occupied[x][y]) { - return; - } - - cellInfo.current.set(x, y, x, y); - - findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo); - } - - private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied, - CellInfo cellInfo) { - - addVacantCell(current, cellInfo); - - if (current.left > 0) { - if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) { - current.left--; - findVacantCell(current, xCount, yCount, occupied, cellInfo); - current.left++; - } - } - - if (current.right < xCount - 1) { - if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) { - current.right++; - findVacantCell(current, xCount, yCount, occupied, cellInfo); - current.right--; - } - } - - if (current.top > 0) { - if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) { - current.top--; - findVacantCell(current, xCount, yCount, occupied, cellInfo); - current.top++; - } - } - - if (current.bottom < yCount - 1) { - if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) { - current.bottom++; - findVacantCell(current, xCount, yCount, occupied, cellInfo); - current.bottom--; - } - } - } - - private static void addVacantCell(Rect current, CellInfo cellInfo) { - CellInfo.VacantCell cell = CellInfo.VacantCell.acquire(); - cell.cellX = current.left; - cell.cellY = current.top; - cell.spanX = current.right - current.left + 1; - cell.spanY = current.bottom - current.top + 1; - if (cell.spanX > cellInfo.maxVacantSpanX) { - cellInfo.maxVacantSpanX = cell.spanX; - cellInfo.maxVacantSpanXSpanY = cell.spanY; - } - if (cell.spanY > cellInfo.maxVacantSpanY) { - cellInfo.maxVacantSpanY = cell.spanY; - cellInfo.maxVacantSpanYSpanX = cell.spanX; - } - cellInfo.vacantCells.add(cell); - } - - private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) { - for (int y = top; y <= bottom; y++) { - if (occupied[x][y]) { - return false; - } - } - return true; + return (CellInfo) super.getTag(); } + /** + * Check if the row 'y' is empty from columns 'left' to 'right', inclusive. + */ private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) { for (int x = left; x <= right; x++) { if (occupied[x][y]) { @@ -342,79 +648,28 @@ public class CellLayout extends ViewGroup { return true; } - CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) { - final boolean portrait = mPortrait; - final int xCount = portrait ? mShortAxisCells : mLongAxisCells; - final int yCount = portrait ? mLongAxisCells : mShortAxisCells; - - boolean[][] occupied = mOccupied; - - if (occupiedCells != null) { - for (int y = 0; y < yCount; y++) { - for (int x = 0; x < xCount; x++) { - occupied[x][y] = occupiedCells[y * xCount + x]; - } - } - } else { - findOccupiedCells(xCount, yCount, occupied, ignoreView); - } - - CellInfo cellInfo = new CellInfo(); - - cellInfo.cellX = -1; - cellInfo.cellY = -1; - cellInfo.spanY = 0; - cellInfo.spanX = 0; - cellInfo.maxVacantSpanX = Integer.MIN_VALUE; - cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE; - cellInfo.maxVacantSpanY = Integer.MIN_VALUE; - cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE; - cellInfo.screen = mCellInfo.screen; - - Rect current = cellInfo.current; - - for (int x = 0; x < xCount; x++) { - for (int y = 0; y < yCount; y++) { - if (!occupied[x][y]) { - current.set(x, y, x, y); - findVacantCell(current, xCount, yCount, occupied, cellInfo); - occupied[x][y] = true; - } - } - } - - cellInfo.valid = cellInfo.vacantCells.size() > 0; - - // Assume the caller will perform their own cell searching, otherwise we - // risk causing an unnecessary rebuild after findCellForSpan() - - return cellInfo; - } - /** - * Given a point, return the cell that strictly encloses that point + * Given a point, return the cell that strictly encloses that point * @param x X coordinate of the point * @param y Y coordinate of the point * @param result Array of 2 ints to hold the x and y coordinate of the cell */ void pointToCellExact(int x, int y, int[] result) { - final boolean portrait = mPortrait; - - final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding; - final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding; + final int hStartPadding = getLeftPadding(); + final int vStartPadding = getTopPadding(); result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap); result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap); - final int xAxis = portrait ? mShortAxisCells : mLongAxisCells; - final int yAxis = portrait ? mLongAxisCells : mShortAxisCells; + final int xAxis = mCountX; + final int yAxis = mCountY; if (result[0] < 0) result[0] = 0; if (result[0] >= xAxis) result[0] = xAxis - 1; if (result[1] < 0) result[1] = 0; if (result[1] >= yAxis) result[1] = yAxis - 1; } - + /** * Given a point, return the cell that most closely encloses that point * @param x X coordinate of the point @@ -427,18 +682,15 @@ public class CellLayout extends ViewGroup { /** * Given a cell coordinate, return the point that represents the upper left corner of that cell - * - * @param cellX X coordinate of the cell + * + * @param cellX X coordinate of the cell * @param cellY Y coordinate of the cell - * + * * @param result Array of 2 ints to hold the x and y coordinate of the point */ void cellToPoint(int cellX, int cellY, int[] result) { - final boolean portrait = mPortrait; - - final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding; - final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding; - + final int hStartPadding = getLeftPadding(); + final int vStartPadding = getTopPadding(); result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap); result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap); @@ -453,101 +705,76 @@ public class CellLayout extends ViewGroup { } int getLeftPadding() { - return mPortrait ? mShortAxisStartPadding : mLongAxisStartPadding; + return mLeftPadding; } int getTopPadding() { - return mPortrait ? mLongAxisStartPadding : mShortAxisStartPadding; + return mTopPadding; } int getRightPadding() { - return mPortrait ? mShortAxisEndPadding : mLongAxisEndPadding; + return mRightPadding; } int getBottomPadding() { - return mPortrait ? mLongAxisEndPadding : mShortAxisEndPadding; + return mBottomPadding; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO: currently ignoring padding - + int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); - int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); - + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - + if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); } - final int shortAxisCells = mShortAxisCells; - final int longAxisCells = mLongAxisCells; - final int longAxisStartPadding = mLongAxisStartPadding; - final int longAxisEndPadding = mLongAxisEndPadding; - final int shortAxisStartPadding = mShortAxisStartPadding; - final int shortAxisEndPadding = mShortAxisEndPadding; final int cellWidth = mCellWidth; final int cellHeight = mCellHeight; - mPortrait = heightSpecSize > widthSpecSize; + int numWidthGaps = mCountX - 1; + int numHeightGaps = mCountY - 1; - int numShortGaps = shortAxisCells - 1; - int numLongGaps = longAxisCells - 1; + if (mWidthGap < 0 || mHeightGap < 0) { + int vSpaceLeft = heightSpecSize - mTopPadding - mBottomPadding - (cellHeight * mCountY); + mHeightGap = vSpaceLeft / numHeightGaps; - if (mPortrait) { - int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding - - (cellHeight * longAxisCells); - mHeightGap = vSpaceLeft / numLongGaps; + int hSpaceLeft = widthSpecSize - mLeftPadding - mRightPadding - (cellWidth * mCountX); + mWidthGap = hSpaceLeft / numWidthGaps; - int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding - - (cellWidth * shortAxisCells); - if (numShortGaps > 0) { - mWidthGap = hSpaceLeft / numShortGaps; - } else { - mWidthGap = 0; - } - } else { - int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding - - (cellWidth * longAxisCells); - mWidthGap = hSpaceLeft / numLongGaps; - - int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding - - (cellHeight * shortAxisCells); - if (numShortGaps > 0) { - mHeightGap = vSpaceLeft / numShortGaps; - } else { - mHeightGap = 0; - } + // center it around the min gaps + int minGap = Math.min(mWidthGap, mHeightGap); + mWidthGap = mHeightGap = minGap; } - + int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (mPortrait) { - lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding, - longAxisStartPadding); - } else { - lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding, - shortAxisStartPadding); - } - - if (lp.regenerateId) { - child.setId(((getId() & 0xFF) << 16) | (lp.cellX & 0xFF) << 8 | (lp.cellY & 0xFF)); - lp.regenerateId = false; - } + lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, + mLeftPadding, mTopPadding); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); - int childheightMeasureSpec = - MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); + int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, + MeasureSpec.EXACTLY); + child.measure(childWidthMeasureSpec, childheightMeasureSpec); } - - setMeasuredDimension(widthSpecSize, heightSpecSize); + if (widthSpecMode == MeasureSpec.AT_MOST) { + int newWidth = mLeftPadding + mRightPadding + (mCountX * cellWidth) + + ((mCountX - 1) * mWidthGap); + int newHeight = mTopPadding + mBottomPadding + (mCountY * cellHeight) + + ((mCountY - 1) * mHeightGap); + setMeasuredDimension(newWidth, newHeight); + } else if (widthSpecMode == MeasureSpec.EXACTLY) { + setMeasuredDimension(widthSpecSize, heightSpecSize); + } } @Override @@ -555,7 +782,7 @@ public class CellLayout extends ViewGroup { int count = getChildCount(); for (int i = 0; i < count; i++) { - View child = getChildAt(i); + final View child = getChildAt(i); if (child.getVisibility() != GONE) { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); @@ -567,24 +794,35 @@ public class CellLayout extends ViewGroup { if (lp.dropped) { lp.dropped = false; - final int[] cellXY = mCellXY; + final int[] cellXY = mTmpCellXY; getLocationOnScreen(cellXY); mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop", cellXY[0] + childLeft + lp.width / 2, cellXY[1] + childTop + lp.height / 2, 0, null); + + ((Workspace) mParent).animateViewIntoPosition(child); } } } } @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mBackgroundRect.set(0, 0, w, h); + updateHoverRect(); + } + + @Override protected void setChildrenDrawingCacheEnabled(boolean enabled) { final int count = getChildCount(); for (int i = 0; i < count; i++) { final View view = getChildAt(i); view.setDrawingCacheEnabled(enabled); // Update the drawing caches - view.buildDrawingCache(true); + if (!view.isHardwareAccelerated()) { + view.buildDrawingCache(true); + } } } @@ -593,154 +831,472 @@ public class CellLayout extends ViewGroup { super.setChildrenDrawnWithCacheEnabled(enabled); } + public float getBackgroundAlpha() { + return mBackgroundAlpha; + } + + public void setBackgroundAlphaMultiplier(float multiplier) { + mBackgroundAlphaMultiplier = multiplier; + } + + public void setBackgroundAlpha(float alpha) { + mBackgroundAlpha = alpha; + invalidate(); + } + + // Need to return true to let the view system know we know how to handle alpha-- this is + // because when our children have an alpha of 0.0f, they are still rendering their "dimmed" + // versions + @Override + protected boolean onSetAlpha(int alpha) { + return true; + } + + public void setAlpha(float alpha) { + setChildrenAlpha(alpha); + super.setAlpha(alpha); + } + + private void setChildrenAlpha(float alpha) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + getChildAt(i).setAlpha(alpha); + } + } + + private boolean isVacantIgnoring( + int originX, int originY, int spanX, int spanY, View ignoreView) { + if (ignoreView != null) { + markCellsAsUnoccupiedForView(ignoreView); + } + boolean isVacant = true; + for (int i = 0; i < spanY; i++) { + if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) { + isVacant = false; + break; + } + } + if (ignoreView != null) { + markCellsAsOccupiedForView(ignoreView); + } + return isVacant; + } + + private boolean isVacant(int originX, int originY, int spanX, int spanY) { + return isVacantIgnoring(originX, originY, spanX, spanY, null); + } + + public View getChildAt(int x, int y) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) && + (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) { + return child; + } + } + return null; + } + + /** + * Estimate the size that a child with the given dimensions will take in the layout. + */ + void estimateChildSize(int minWidth, int minHeight, int[] result) { + // Assuming it's placed at 0, 0, find where the bottom right cell will land + rectToCell(minWidth, minHeight, result); + + // Then figure out the rect it will occupy + cellToRect(0, 0, result[0], result[1], mRectF); + result[0] = (int)mRectF.width(); + result[1] = (int)mRectF.height(); + } + + /** + * Estimate where the top left cell of the dragged item will land if it is dropped. + * + * @param originX The X value of the top left corner of the item + * @param originY The Y value of the top left corner of the item + * @param spanX The number of horizontal cells that the item spans + * @param spanY The number of vertical cells that the item spans + * @param result The estimated drop cell X and Y. + */ + void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) { + final int countX = mCountX; + final int countY = mCountY; + + // pointToCellRounded takes the top left of a cell but will pad that with + // cellWidth/2 and cellHeight/2 when finding the matching cell + pointToCellRounded(originX, originY, result); + + // If the item isn't fully on this screen, snap to the edges + int rightOverhang = result[0] + spanX - countX; + if (rightOverhang > 0) { + result[0] -= rightOverhang; // Snap to right + } + result[0] = Math.max(0, result[0]); // Snap to left + int bottomOverhang = result[1] + spanY - countY; + if (bottomOverhang > 0) { + result[1] -= bottomOverhang; // Snap to bottom + } + result[1] = Math.max(0, result[1]); // Snap to top + } + + void visualizeDropLocation( + View v, Bitmap dragOutline, int originX, int originY, int spanX, int spanY) { + + final int oldDragCellX = mDragCell[0]; + final int oldDragCellY = mDragCell[1]; + final int[] nearest = findNearestVacantArea(originX, originY, spanX, spanY, v, mDragCell); + if (v != null) { + mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2)); + } else { + mDragCenter.set(originX, originY); + } + + if (nearest != null && (nearest[0] != oldDragCellX || nearest[1] != oldDragCellY)) { + // Find the top left corner of the rect the object will occupy + final int[] topLeft = mTmpPoint; + cellToPoint(nearest[0], nearest[1], topLeft); + + int left = topLeft[0]; + int top = topLeft[1]; + + if (v != null) { + if (v.getParent() instanceof CellLayout) { + LayoutParams lp = (LayoutParams) v.getLayoutParams(); + left += lp.leftMargin; + top += lp.topMargin; + } + + // Offsets due to the size difference between the View and the dragOutline + left += (v.getWidth() - dragOutline.getWidth()) / 2; + top += (v.getHeight() - dragOutline.getHeight()) / 2; + } + + final int oldIndex = mDragOutlineCurrent; + mDragOutlineAnims[oldIndex].animateOut(); + mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length; + + mDragOutlines[mDragOutlineCurrent].set(left, top); + mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline); + mDragOutlineAnims[mDragOutlineCurrent].animateIn(); + } + + // If we are drawing crosshairs, the entire CellLayout needs to be invalidated + if (mCrosshairsDrawable != null) { + invalidate(); + } + } + + /** + * 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 result Array in which to place the result, or null (in which case a new array will + * be allocated) + * @return The X, Y cell of a vacant area that can contain this object, + * nearest the requested location. + */ + int[] findNearestVacantArea( + int pixelX, int pixelY, int spanX, int spanY, int[] result) { + return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result); + } + /** * 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. + * @param ignoreView Considers space occupied by this view as unoccupied + * @param result Previously returned value to possibly recycle. * @return The X, Y cell of a vacant area that can contain this object, * nearest the requested location. */ - int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, - CellInfo vacantCells, int[] recycle) { - + int[] findNearestVacantArea( + int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) { + // mark space take by ignoreView as available (method checks if ignoreView is null) + markCellsAsUnoccupiedForView(ignoreView); + // Keep track of best-scoring drop area - final int[] bestXY = recycle != null ? recycle : new int[2]; - final int[] cellXY = mCellXY; + final int[] bestXY = result != null ? result : new int[2]; double bestDistance = Double.MAX_VALUE; - - // Bail early if vacant cells aren't valid - if (!vacantCells.valid) { - return null; - } - // 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; - } - - // 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; + final int countX = mCountX; + final int countY = mCountY; + final boolean[][] occupied = mOccupied; + + for (int x = 0; x < countX - (spanX - 1); x++) { + inner: + for (int y = 0; y < countY - (spanY - 1); y++) { + for (int i = 0; i < spanX; i++) { + for (int j = 0; j < spanY; j++) { + if (occupied[x + i][y + j]) { + // small optimization: we can skip to below the row we just found + // an occupied cell + y += j; + continue inner; + } + } + } + final int[] cellXY = mTmpCellXY; + cellToPoint(x, y, cellXY); + + double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) + + Math.pow(cellXY[1] - pixelY, 2)); + if (distance <= bestDistance) { + bestDistance = distance; + bestXY[0] = x; + bestXY[1] = y; + } } } + // re-mark space taken by ignoreView as occupied + markCellsAsOccupiedForView(ignoreView); - // Return null if no suitable location found + // Return null if no suitable location found if (bestDistance < Double.MAX_VALUE) { return bestXY; } else { return null; } } - + + boolean existsEmptyCell() { + return findCellForSpan(null, 1, 1); + } + /** - * Drop a child at the specified position + * Finds the upper-left coordinate of the first rectangle in the grid that can + * hold a cell of the specified dimensions. If intersectX and intersectY are not -1, + * then this method will only return coordinates for rectangles that contain the cell + * (intersectX, intersectY) + * + * @param cellXY The array that will contain the position of a vacant cell if such a cell + * can be found. + * @param spanX The horizontal span of the cell we want to find. + * @param spanY The vertical span of the cell we want to find. + * + * @return True if a vacant cell of the specified dimension was found, false otherwise. + */ + boolean findCellForSpan(int[] cellXY, int spanX, int spanY) { + return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null); + } + + /** + * Like above, but ignores any cells occupied by the item "ignoreView" + * + * @param cellXY The array that will contain the position of a vacant cell if such a cell + * can be found. + * @param spanX The horizontal span of the cell we want to find. + * @param spanY The vertical span of the cell we want to find. + * @param ignoreView The home screen item we should treat as not occupying any space + * @return + */ + boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) { + return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, ignoreView); + } + + /** + * Like above, but if intersectX and intersectY are not -1, then this method will try to + * return coordinates for rectangles that contain the cell [intersectX, intersectY] + * + * @param spanX The horizontal span of the cell we want to find. + * @param spanY The vertical span of the cell we want to find. + * @param ignoreView The home screen item we should treat as not occupying any space + * @param intersectX The X coordinate of the cell that we should try to overlap + * @param intersectX The Y coordinate of the cell that we should try to overlap + * + * @return True if a vacant cell of the specified dimension was found, false otherwise. + */ + boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY, + int intersectX, int intersectY) { + return findCellForSpanThatIntersectsIgnoring( + cellXY, spanX, spanY, intersectX, intersectY, null); + } + + /** + * The superset of the above two methods + */ + boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, + int intersectX, int intersectY, View ignoreView) { + // mark space take by ignoreView as available (method checks if ignoreView is null) + markCellsAsUnoccupiedForView(ignoreView); + + boolean foundCell = false; + while (true) { + int startX = 0; + if (intersectX >= 0) { + startX = Math.max(startX, intersectX - (spanX - 1)); + } + int endX = mCountX - (spanX - 1); + if (intersectX >= 0) { + endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0)); + } + int startY = 0; + if (intersectY >= 0) { + startY = Math.max(startY, intersectY - (spanY - 1)); + } + int endY = mCountY - (spanY - 1); + if (intersectY >= 0) { + endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0)); + } + + for (int x = startX; x < endX; x++) { + inner: + for (int y = startY; y < endY; y++) { + for (int i = 0; i < spanX; i++) { + for (int j = 0; j < spanY; j++) { + if (mOccupied[x + i][y + j]) { + // small optimization: we can skip to below the row we just found + // an occupied cell + y += j; + continue inner; + } + } + } + if (cellXY != null) { + cellXY[0] = x; + cellXY[1] = y; + } + foundCell = true; + break; + } + } + if (intersectX == -1 && intersectY == -1) { + break; + } else { + // if we failed to find anything, try again but without any requirements of + // intersecting + intersectX = -1; + intersectY = -1; + continue; + } + } + + // re-mark space taken by ignoreView as occupied + markCellsAsOccupiedForView(ignoreView); + return foundCell; + } + + /** + * Called when drag has left this CellLayout or has been completed (successfully or not) + */ + void onDragExit() { + // This can actually be called when we aren't in a drag, e.g. when adding a new + // item to this layout via the customize drawer. + // Guard against that case. + if (mDragging) { + mDragging = false; + + // Fade out the drag indicators + if (mCrosshairsAnimator != null) { + mCrosshairsAnimator.animateOut(); + } + } + + // Invalidate the drag data + mDragCell[0] = -1; + mDragCell[1] = -1; + mDragOutlineAnims[mDragOutlineCurrent].animateOut(); + mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length; + + setHover(false); + } + + /** + * Mark a child as having been dropped. + * At the beginning of the drag operation, the child may have been on another + * screen, but it is re-parented before this method is called. * * @param child The child that is being dropped - * @param targetXY Destination area to move to */ - void onDropChild(View child, int[] targetXY) { + void onDropChild(View child) { if (child != null) { LayoutParams lp = (LayoutParams) child.getLayoutParams(); - lp.cellX = targetXY[0]; - lp.cellY = targetXY[1]; lp.isDragging = false; lp.dropped = true; - mDragRect.setEmpty(); + child.setVisibility(View.VISIBLE); child.requestLayout(); - invalidate(); - } - } - - void onDropAborted(View child) { - if (child != null) { - ((LayoutParams) child.getLayoutParams()).isDragging = false; - invalidate(); } - mDragRect.setEmpty(); } /** * Start dragging the specified child - * + * * @param child The child that is being dragged */ void onDragChild(View child) { LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.isDragging = true; - mDragRect.setEmpty(); + child.setVisibility(View.GONE); } - + /** - * Drag a child over the specified position - * - * @param child The child that is being dropped - * @param cellX The child's new x cell location - * @param cellY The child's new y cell location + * A drag event has begun over this layout. + * It may have begun over this layout (in which case onDragChild is called first), + * or it may have begun on another layout. */ - void onDragOverChild(View child, int cellX, int cellY) { - int[] cellXY = mCellXY; - pointToCellRounded(cellX, cellY, cellXY); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect); - invalidate(); + void onDragEnter() { + if (!mDragging) { + // Fade in the drag indicators + if (mCrosshairsAnimator != null) { + mCrosshairsAnimator.animateIn(); + } + } + mDragging = true; } - + /** * Computes a bounding rectangle for a range of cells - * + * * @param cellX X coordinate of upper left corner expressed as a cell position * @param cellY Y coordinate of upper left corner expressed as a cell position - * @param cellHSpan Width in cells + * @param cellHSpan Width in cells * @param cellVSpan Height in cells - * @param dragRect Rectnagle into which to put the results + * @param resultRect Rect into which to put the results */ - public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) { - final boolean portrait = mPortrait; + public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) { final int cellWidth = mCellWidth; final int cellHeight = mCellHeight; final int widthGap = mWidthGap; final int heightGap = mHeightGap; - - final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding; - final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding; - + + final int hStartPadding = getLeftPadding(); + final int vStartPadding = getTopPadding(); + int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap); int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap); int x = hStartPadding + cellX * (cellWidth + widthGap); int y = vStartPadding + cellY * (cellHeight + heightGap); - - dragRect.set(x, y, x + width, y + height); + + resultRect.set(x, y, x + width, y + height); } - + /** - * Computes the required horizontal and vertical cell spans to always + * Computes the required horizontal and vertical cell spans to always * fit the given rectangle. - * + * * @param width Width in pixels * @param height Height in pixels + * @param result An array of length 2 in which to store the result (may be null). */ - public int[] rectToCell(int width, int height) { + public int[] rectToCell(int width, int height, int[] result) { + return rectToCell(getResources(), width, height, result); + } + + public static int[] rectToCell(Resources resources, int width, int height, int[] result) { // Always assume we're working with the smallest span to make sure we // reserve enough space in both orientations. - final Resources resources = getResources(); int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width); int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height); int smallerSize = Math.min(actualWidth, actualHeight); @@ -749,7 +1305,12 @@ public class CellLayout extends ViewGroup { int spanX = (width + smallerSize) / smallerSize; int spanY = (height + smallerSize) / smallerSize; - return new int[] { spanX, spanY }; + if (result == null) { + return new int[] { spanX, spanY }; + } + result[0] = spanX; + result[1] = spanY; + return result; } /** @@ -758,18 +1319,12 @@ public class CellLayout extends ViewGroup { * @param vacant Holds the x and y coordinate of the vacant cell * @param spanX Horizontal cell span. * @param spanY Vertical cell span. - * + * * @return True if a vacant cell was found */ public boolean getVacantCell(int[] vacant, int spanX, int spanY) { - final boolean portrait = mPortrait; - final int xCount = portrait ? mShortAxisCells : mLongAxisCells; - final int yCount = portrait ? mLongAxisCells : mShortAxisCells; - final boolean[][] occupied = mOccupied; - - findOccupiedCells(xCount, yCount, occupied, null); - return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied); + return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied); } static boolean findVacantCell(int[] vacant, int spanX, int spanY, @@ -796,43 +1351,36 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { return false; } - boolean[] getOccupiedCells() { - final boolean portrait = mPortrait; - final int xCount = portrait ? mShortAxisCells : mLongAxisCells; - final int yCount = portrait ? mLongAxisCells : mShortAxisCells; - final boolean[][] occupied = mOccupied; - - findOccupiedCells(xCount, yCount, occupied, null); - - final boolean[] flat = new boolean[xCount * yCount]; - for (int y = 0; y < yCount; y++) { - for (int x = 0; x < xCount; x++) { - flat[y * xCount + x] = occupied[x][y]; + private void clearOccupiedCells() { + for (int x = 0; x < mCountX; x++) { + for (int y = 0; y < mCountY; y++) { + mOccupied[x][y] = false; } } + } - return flat; + public void onMove(View view, int newCellX, int newCellY) { + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + markCellsAsUnoccupiedForView(view); + markCellsForView(newCellX, newCellY, lp.cellHSpan, lp.cellVSpan, true); } - 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; - } - } + private void markCellsAsOccupiedForView(View view) { + if (view == null || view.getParent() != this) return; + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true); + } - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - if (child instanceof Folder || child.equals(ignoreView)) { - continue; - } - LayoutParams lp = (LayoutParams) child.getLayoutParams(); + private void markCellsAsUnoccupiedForView(View view) { + if (view == null || view.getParent() != this) return; + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false); + } - for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) { - for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) { - occupied[x][y] = true; - } + private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value) { + for (int x = cellX; x < cellX + spanX && x < mCountX; x++) { + for (int y = cellY; y < cellY + spanY && y < mCountY; y++) { + mOccupied[x][y] = value; } } } @@ -852,6 +1400,17 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { return new CellLayout.LayoutParams(p); } + public static class CellLayoutAnimationController extends LayoutAnimationController { + public CellLayoutAnimationController(Animation animation, float delay) { + super(animation, delay); + } + + @Override + protected long getDelayForView(View view) { + return (int) (Math.random() * 150); + } + } + public static class LayoutParams extends ViewGroup.MarginLayoutParams { /** * Horizontal location of the item in the grid. @@ -876,7 +1435,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { */ @ViewDebug.ExportedProperty public int cellVSpan; - + /** * Is this item currently being dragged */ @@ -889,8 +1448,18 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { @ViewDebug.ExportedProperty int y; - boolean regenerateId; - + /** + * The old X coordinate of this item, relative to its current parent. + * Used to animate the item into its new position. + */ + int oldX; + + /** + * The old Y coordinate of this item, relative to its current parent. + * Used to animate the item into its new position. + */ + int oldY; + boolean dropped; public LayoutParams(Context c, AttributeSet attrs) { @@ -904,7 +1473,15 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { cellHSpan = 1; cellVSpan = 1; } - + + public LayoutParams(LayoutParams source) { + super(source); + this.cellX = source.cellX; + this.cellY = source.cellY; + this.cellHSpan = source.cellHSpan; + this.cellVSpan = source.cellVSpan; + } + public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); this.cellX = cellX; @@ -915,12 +1492,12 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap, int hStartPadding, int vStartPadding) { - + final int myCellHSpan = cellHSpan; final int myCellVSpan = cellVSpan; final int myCellX = cellX; final int myCellY = cellY; - + width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - leftMargin - rightMargin; height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - @@ -929,172 +1506,31 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; } - } - - static final class CellInfo implements ContextMenu.ContextMenuInfo { - /** - * See View.AttachInfo.InvalidateInfo for futher explanations about - * the recycling mechanism. In this case, we recycle the vacant cells - * instances because up to several hundreds can be instanciated when - * the user long presses an empty cell. - */ - static final class VacantCell { - int cellX; - int cellY; - int spanX; - int spanY; - - // We can create up to 523 vacant cells on a 4x4 grid, 100 seems - // like a reasonable compromise given the size of a VacantCell and - // the fact that the user is not likely to touch an empty 4x4 grid - // very often - private static final int POOL_LIMIT = 100; - private static final Object sLock = new Object(); - - private static int sAcquiredCount = 0; - private static VacantCell sRoot; - - private VacantCell next; - - static VacantCell acquire() { - synchronized (sLock) { - if (sRoot == null) { - return new VacantCell(); - } - - VacantCell info = sRoot; - sRoot = info.next; - sAcquiredCount--; - - return info; - } - } - void release() { - synchronized (sLock) { - if (sAcquiredCount < POOL_LIMIT) { - sAcquiredCount++; - next = sRoot; - sRoot = this; - } - } - } - - @Override - public String toString() { - return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX + - ", spanY=" + spanY + "]"; - } + public String toString() { + return "(" + this.cellX + ", " + this.cellY + ")"; } + } + // This class stores info for two purposes: + // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY, + // its spanX, spanY, and the screen it is on + // 2. When long clicking on an empty cell in a CellLayout, we save information about the + // cellX and cellY coordinates and which page was clicked. We then set this as a tag on + // the CellLayout that was long clicked + static final class CellInfo implements ContextMenu.ContextMenuInfo { View cell; - int cellX; - int cellY; + int cellX = -1; + int cellY = -1; int spanX; int spanY; int screen; boolean valid; - final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT); - int maxVacantSpanX; - int maxVacantSpanXSpanY; - int maxVacantSpanY; - int maxVacantSpanYSpanX; - final Rect current = new Rect(); - - void clearVacantCells() { - final ArrayList<VacantCell> list = vacantCells; - final int count = list.size(); - - for (int i = 0; i < count; i++) list.get(i).release(); - - list.clear(); - } - - void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) { - if (cellX < 0 || cellY < 0) { - maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE; - maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE; - clearVacantCells(); - return; - } - - final boolean[][] unflattened = new boolean[xCount][yCount]; - for (int y = 0; y < yCount; y++) { - for (int x = 0; x < xCount; x++) { - unflattened[x][y] = occupied[y * xCount + x]; - } - } - CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened); - } - - /** - * This method can be called only once! Calling #findVacantCellsFromOccupied will - * restore the ability to call this method. - * - * Finds the upper-left coordinate of the first rectangle in the grid that can - * hold a cell of the specified dimensions. - * - * @param cellXY The array that will contain the position of a vacant cell if such a cell - * can be found. - * @param spanX The horizontal span of the cell we want to find. - * @param spanY The vertical span of the cell we want to find. - * - * @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(); - - boolean found = false; - - if (this.spanX >= spanX && this.spanY >= spanY) { - cellXY[0] = cellX; - cellXY[1] = cellY; - found = true; - } - - // Look for an exact match first - for (int i = 0; i < count; i++) { - VacantCell cell = list.get(i); - if (cell.spanX == spanX && cell.spanY == spanY) { - cellXY[0] = cell.cellX; - cellXY[1] = cell.cellY; - found = true; - break; - } - } - - // Look for the first cell large enough - for (int i = 0; i < count; i++) { - VacantCell cell = list.get(i); - if (cell.spanX >= spanX && cell.spanY >= spanY) { - cellXY[0] = cell.cellX; - cellXY[1] = cell.cellY; - found = true; - break; - } - } - - if (clear) clearVacantCells(); - - return found; - } - @Override public String toString() { - return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX + - ", y=" + cellY + "]"; + return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + + ", x=" + cellX + ", y=" + cellY + "]"; } } - - public boolean lastDownOnOccupiedCell() { - return mLastDownOnOccupiedCell; - } } - - diff --git a/src/com/android/launcher2/CustomizePagedView.java b/src/com/android/launcher2/CustomizePagedView.java new file mode 100644 index 0000000..4cce81e --- /dev/null +++ b/src/com/android/launcher2/CustomizePagedView.java @@ -0,0 +1,795 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +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.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.Region.Op; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ActionMode; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.launcher.R; + +public class CustomizePagedView extends PagedView + implements View.OnLongClickListener, View.OnClickListener, + DragSource, ActionMode.Callback { + + public enum CustomizationType { + WidgetCustomization, + ShortcutCustomization, + WallpaperCustomization, + ApplicationCustomization + } + + private static final String TAG = "CustomizeWorkspace"; + private static final boolean DEBUG = false; + + private Launcher mLauncher; + private DragController mDragController; + private PackageManager mPackageManager; + + private CustomizationType mCustomizationType; + + // The layout used to emulate the workspace in resolve the cell dimensions of a widget + private PagedViewCellLayout mWorkspaceWidgetLayout; + + // The mapping between the pages and the widgets that will be laid out on them + private ArrayList<ArrayList<AppWidgetProviderInfo>> mWidgetPages; + + // The max dimensions for the ImageView we use for displaying the widget + private int mMaxWidgetWidth; + + // The max number of widget cells to take a "page" of widget + private int mMaxWidgetsCellHSpan; + + // The raw sources of data for each of the different tabs of the customization page + private List<AppWidgetProviderInfo> mWidgetList; + private List<ResolveInfo> mShortcutList; + private List<ResolveInfo> mWallpaperList; + private List<ApplicationInfo> mApps; + + private static final int sMinWidgetCellHSpan = 2; + private static final int sMaxWidgetCellHSpan = 4; + + private int mChoiceModeTitleText; + + // The scale factor for widget previews inside the widget drawer + private static final float sScaleFactor = 0.75f; + + private final Canvas mCanvas = new Canvas(); + private final LayoutInflater mInflater; + + public CustomizePagedView(Context context) { + this(context, null, 0); + } + + public CustomizePagedView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CustomizePagedView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a; + a = context.obtainStyledAttributes(attrs, R.styleable.CustomizePagedView, + defStyle, 0); + mMaxWidgetsCellHSpan = a.getInt(R.styleable.CustomizePagedView_widgetCellCountX, 8); + a.recycle(); + a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, defStyle, 0); + mCellCountX = a.getInt(R.styleable.PagedView_cellCountX, 7); + mCellCountY = a.getInt(R.styleable.PagedView_cellCountY, 4); + + a.recycle(); + mCustomizationType = CustomizationType.WidgetCustomization; + mWidgetPages = new ArrayList<ArrayList<AppWidgetProviderInfo>>(); + mWorkspaceWidgetLayout = new PagedViewCellLayout(context); + mInflater = LayoutInflater.from(context); + + setVisibility(View.GONE); + setSoundEffectsEnabled(false); + setupWorkspaceLayout(); + } + + @Override + protected void init() { + super.init(); + mCenterPagesVertically = false; + } + + public void setLauncher(Launcher launcher) { + Context context = getContext(); + mLauncher = launcher; + mPackageManager = context.getPackageManager(); + } + + /** + * Sets the list of applications that launcher has loaded. + */ + public void setApps(ArrayList<ApplicationInfo> list) { + mApps = list; + Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR); + + // Update the widgets/shortcuts to reflect changes in the set of available apps + invalidatePageDataAndIconCache(); + } + + /** + * Convenience function to add new items to the set of applications that were previously loaded. + * Called by both updateApps() and setApps(). + */ + private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { + // we add it in place, in alphabetical order + final int count = list.size(); + for (int i = 0; i < count; ++i) { + final ApplicationInfo info = list.get(i); + final int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR); + if (index < 0) { + mApps.add(-(index + 1), info); + } + } + } + + /** + * Adds new applications to the loaded list, and notifies the paged view to update itself. + */ + public void addApps(ArrayList<ApplicationInfo> list) { + addAppsWithoutInvalidate(list); + + // Update the widgets/shortcuts to reflect changes in the set of available apps + invalidatePageDataAndIconCache(); + } + + /** + * Convenience function to remove items to the set of applications that were previously loaded. + * Called by both updateApps() and removeApps(). + */ + private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) { + // loop through all the apps and remove apps that have the same component + final int length = list.size(); + for (int i = 0; i < length; ++i) { + final ApplicationInfo info = list.get(i); + int removeIndex = findAppByComponent(mApps, info); + if (removeIndex > -1) { + mApps.remove(removeIndex); + mPageViewIconCache.removeOutline(info); + } + } + } + + /** + * Removes applications from the loaded list, and notifies the paged view to update itself. + */ + public void removeApps(ArrayList<ApplicationInfo> list) { + removeAppsWithoutInvalidate(list); + + // Update the widgets/shortcuts to reflect changes in the set of available apps + invalidatePageDataAndIconCache(); + } + + /** + * Updates a set of applications from the loaded list, and notifies the paged view to update + * itself. + */ + public void updateApps(ArrayList<ApplicationInfo> list) { + // We remove and re-add the updated applications list because it's properties may have + // changed (ie. the title), and this will ensure that the items will be in their proper + // place in the list. + removeAppsWithoutInvalidate(list); + addAppsWithoutInvalidate(list); + + // Update the widgets/shortcuts to reflect changes in the set of available apps + invalidatePageDataAndIconCache(); + } + + /** + * Convenience function to find matching ApplicationInfos for removal. + */ + private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) { + ComponentName removeComponent = item.intent.getComponent(); + final int length = list.size(); + for (int i = 0; i < length; ++i) { + ApplicationInfo info = list.get(i); + if (info.intent.getComponent().equals(removeComponent)) { + return i; + } + } + return -1; + } + + public void update() { + // get the list of widgets + mWidgetList = AppWidgetManager.getInstance(mLauncher).getInstalledProviders(); + Collections.sort(mWidgetList, new Comparator<AppWidgetProviderInfo>() { + @Override + public int compare(AppWidgetProviderInfo object1, AppWidgetProviderInfo object2) { + return object1.label.compareTo(object2.label); + } + }); + + Comparator<ResolveInfo> resolveInfoComparator = new Comparator<ResolveInfo>() { + @Override + public int compare(ResolveInfo object1, ResolveInfo object2) { + return object1.loadLabel(mPackageManager).toString().compareTo( + object2.loadLabel(mPackageManager).toString()); + } + }; + + // get the list of shortcuts + Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); + mShortcutList = mPackageManager.queryIntentActivities(shortcutsIntent, 0); + Collections.sort(mShortcutList, resolveInfoComparator); + + // get the list of wallpapers + Intent wallpapersIntent = new Intent(Intent.ACTION_SET_WALLPAPER); + mWallpaperList = mPackageManager.queryIntentActivities(wallpapersIntent, 0); + Collections.sort(mWallpaperList, resolveInfoComparator); + + invalidatePageDataAndIconCache(); + } + + private void invalidatePageDataAndIconCache() { + // Reset the icon cache + mPageViewIconCache.clear(); + + // Refresh all the tabs + invalidatePageData(); + } + + public void setDragController(DragController dragger) { + mDragController = dragger; + } + + public void setCustomizationFilter(CustomizationType filterType) { + mCustomizationType = filterType; + setCurrentPage(0); + invalidatePageData(); + + // End the current choice mode so that we don't carry selections across tabs + endChoiceMode(); + } + + @Override + public void onDropCompleted(View target, boolean success) { + mLauncher.getWorkspace().onDragStopped(); + } + + @Override + public void onClick(View v) { + if (!v.isInTouchMode()) { + return; + } + + // On certain pages, we allow single tap to mark items as selected so that they can be + // dropped onto the mini workspaces + boolean enterChoiceMode = false; + switch (mCustomizationType) { + case WidgetCustomization: + mChoiceModeTitleText = R.string.cab_widget_selection_text; + enterChoiceMode = true; + break; + case ApplicationCustomization: + mChoiceModeTitleText = R.string.cab_app_selection_text; + enterChoiceMode = true; + break; + case ShortcutCustomization: + mChoiceModeTitleText = R.string.cab_shortcut_selection_text; + enterChoiceMode = true; + break; + default: + break; + } + + if (enterChoiceMode) { + final ItemInfo itemInfo = (ItemInfo) v.getTag(); + + Workspace w = mLauncher.getWorkspace(); + int currentWorkspaceScreen = mLauncher.getCurrentWorkspaceScreen(); + final CellLayout cl = (CellLayout)w.getChildAt(currentWorkspaceScreen); + + animateClickFeedback(v, new Runnable() { + @Override + public void run() { + mLauncher.addExternalItemToScreen(itemInfo, cl); + } + }); + return; + } + + // Otherwise, we just handle the single click here + switch (mCustomizationType) { + case WallpaperCustomization: + // animate some feedback to the long press + final View clickView = v; + animateClickFeedback(v, new Runnable() { + @Override + public void run() { + // add the shortcut + ResolveInfo info = (ResolveInfo) clickView.getTag(); + Intent createWallpapersIntent = new Intent(Intent.ACTION_SET_WALLPAPER); + ComponentName name = new ComponentName(info.activityInfo.packageName, + info.activityInfo.name); + createWallpapersIntent.setComponent(name); + mLauncher.processWallpaper(createWallpapersIntent); + } + }); + break; + default: + break; + } + } + + @Override + public boolean onLongClick(View v) { + if (!v.isInTouchMode()) { + return false; + } + + // End the current choice mode before we start dragging anything + if (isChoiceMode(CHOICE_MODE_SINGLE)) { + endChoiceMode(); + } + + PendingAddItemInfo createItemInfo; + switch (mCustomizationType) { + case WidgetCustomization: + // Get the icon as the drag representation + final LinearLayout l = (LinearLayout) v; + final Drawable icon = ((ImageView) l.findViewById(R.id.widget_preview)).getDrawable(); + Bitmap b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + icon.draw(c); + PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) v.getTag(); + + mLauncher.getWorkspace().onDragStartedWithItemMinSize( + createWidgetInfo.minWidth, createWidgetInfo.minHeight); + mDragController.startDrag(v, b, this, createWidgetInfo, DragController.DRAG_ACTION_COPY, + null); + + // Cleanup the icon + b.recycle(); + return true; + case ShortcutCustomization: + createItemInfo = (PendingAddItemInfo) v.getTag(); + mDragController.startDrag( + v, this, createItemInfo, DragController.DRAG_ACTION_COPY, null); + mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1); + return true; + case ApplicationCustomization: + // Pick up the application for dropping + ApplicationInfo app = (ApplicationInfo) v.getTag(); + app = new ApplicationInfo(app); + + mDragController.startDrag(v, this, app, DragController.DRAG_ACTION_COPY); + mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1); + return true; + } + return false; + } + + /** + * Pre-processes the layout of the different widget pages. + * @return the number of pages of widgets that we have + */ + private int relayoutWidgets() { + if (mWidgetList.isEmpty()) return 0; + + // create a new page for the first set of widgets + ArrayList<AppWidgetProviderInfo> newPage = new ArrayList<AppWidgetProviderInfo>(); + mWidgetPages.clear(); + mWidgetPages.add(newPage); + + // do this until we have no more widgets to lay out + final int maxNumCellsPerRow = mMaxWidgetsCellHSpan; + final int widgetCount = mWidgetList.size(); + int numCellsInRow = 0; + for (int i = 0; i < widgetCount; ++i) { + final AppWidgetProviderInfo info = mWidgetList.get(i); + + // determine the size of the current widget + int cellSpanX = Math.max(sMinWidgetCellHSpan, Math.min(sMaxWidgetCellHSpan, + mWorkspaceWidgetLayout.estimateCellHSpan(info.minWidth))); + + // create a new page if necessary + if ((numCellsInRow + cellSpanX) > maxNumCellsPerRow) { + numCellsInRow = 0; + newPage = new ArrayList<AppWidgetProviderInfo>(); + mWidgetPages.add(newPage); + } + + // add the item to the current page + newPage.add(info); + numCellsInRow += cellSpanX; + } + + return mWidgetPages.size(); + } + + /** + * Helper function to draw a drawable to the specified canvas with the specified bounds. + */ + private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int l, int t, int r, int b) { + if (bitmap != null) mCanvas.setBitmap(bitmap); + mCanvas.save(); + d.setBounds(l, t, r, b); + d.draw(mCanvas); + mCanvas.restore(); + } + + /** + * This method will extract the preview image specified by the widget developer (if it exists), + * otherwise, it will try to generate a default image preview with the widget's package icon. + * @return the drawable will be used and sized in the ImageView to represent the widget + */ + private Drawable getWidgetIcon(AppWidgetProviderInfo info) { + PackageManager packageManager = mLauncher.getPackageManager(); + String packageName = info.provider.getPackageName(); + Drawable drawable = null; + if (info.previewImage != 0) { + drawable = packageManager.getDrawable(packageName, info.previewImage, null); + if (drawable == null) { + Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon) + + " for provider: " + info.provider); + } + } + + // If we don't have a preview image, create a default one + final int minDim = mWorkspaceWidgetLayout.estimateCellWidth(1); + final int maxDim = mWorkspaceWidgetLayout.estimateCellWidth(3); + if (drawable == null) { + Resources resources = mLauncher.getResources(); + + // Create a new bitmap to hold the widget preview + int width = (int) (Math.max(minDim, Math.min(maxDim, info.minWidth)) * sScaleFactor); + int height = (int) (Math.max(minDim, Math.min(maxDim, info.minHeight)) * sScaleFactor); + final Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); + final Drawable background = resources.getDrawable(R.drawable.default_widget_preview); + renderDrawableToBitmap(background, bitmap, 0, 0, width, height); + + // Draw the icon vertically centered, flush left + try { + Rect tmpRect = new Rect(); + Drawable icon = null; + if (info.icon > 0) { + icon = packageManager.getDrawable(packageName, info.icon, null); + } + if (icon == null) { + icon = resources.getDrawable(R.drawable.ic_launcher_application); + } + background.getPadding(tmpRect); + + final int iconSize = minDim / 2; + final int offset = iconSize / 4; + final int offsetIconSize = offset + iconSize; + renderDrawableToBitmap(icon, null, offset, offset, offsetIconSize, offsetIconSize); + } catch (Resources.NotFoundException e) { + // if we can't find the icon, then just don't draw it + } + + drawable = new FastBitmapDrawable(bitmap); + } else { + // Scale down the preview if necessary + final float aspect = (float) info.minWidth / info.minHeight; + final int boundedWidth = (int) Math.max(minDim, Math.min(maxDim, info.minWidth)); + final int boundedHeight = (int) Math.max(minDim, Math.min(maxDim, info.minHeight)); + final int scaledWidth = (int) (boundedWidth * sScaleFactor); + final int scaledHeight = (int) (boundedHeight * sScaleFactor); + int width; + int height; + if (scaledWidth > scaledHeight) { + width = scaledWidth; + height = (int) (width / aspect); + } else { + height = scaledHeight; + width = (int) (height * aspect); + } + + final Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); + renderDrawableToBitmap(drawable, bitmap, 0, 0, width, height); + + drawable = new FastBitmapDrawable(bitmap); + } + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + return drawable; + } + + private void setupPage(PagedViewCellLayout layout) { + layout.setCellCount(mCellCountX, mCellCountY); + layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, mPageLayoutPaddingRight, + mPageLayoutPaddingBottom); + layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); + } + + private void setupWorkspaceLayout() { + mWorkspaceWidgetLayout.setCellCount(mCellCountX, mCellCountY); + mWorkspaceWidgetLayout.setPadding(20, 10, 20, 0); + + mMaxWidgetWidth = mWorkspaceWidgetLayout.estimateCellWidth(sMaxWidgetCellHSpan); + } + + private void syncWidgetPages() { + if (mWidgetList == null) return; + + // we need to repopulate with the LinearLayout layout for the widget pages + removeAllViews(); + int numPages = relayoutWidgets(); + for (int i = 0; i < numPages; ++i) { + LinearLayout layout = new PagedViewWidgetLayout(getContext()); + layout.setGravity(Gravity.CENTER_HORIZONTAL); + layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, + mPageLayoutPaddingRight, mPageLayoutPaddingBottom); + + // Temporary change to prevent the last page from being too small (and items bleeding + // onto it). We can remove this once we properly fix the fading algorithm + if (i < numPages - 1) { + addView(layout, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT)); + } else { + addView(layout, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT)); + } + } + } + + private void syncWidgetPageItems(int page) { + // ensure that we have the right number of items on the pages + LinearLayout layout = (LinearLayout) getChildAt(page); + final ArrayList<AppWidgetProviderInfo> list = mWidgetPages.get(page); + final int count = list.size(); + layout.removeAllViews(); + for (int i = 0; i < count; ++i) { + final AppWidgetProviderInfo info = (AppWidgetProviderInfo) list.get(i); + final PendingAddWidgetInfo createItemInfo = new PendingAddWidgetInfo(info, null, null); + + LinearLayout l = (LinearLayout) mInflater.inflate( + R.layout.customize_paged_view_widget, layout, false); + l.setTag(createItemInfo); + l.setOnClickListener(this); + l.setOnLongClickListener(this); + + final Drawable icon = getWidgetIcon(info); + + int[] spans = CellLayout.rectToCell(getResources(), info.minWidth, info.minHeight, null); + final int hSpan = spans[0]; + final int vSpan = spans[1]; + + ImageView image = (ImageView) l.findViewById(R.id.widget_preview); + image.setMaxWidth(mMaxWidgetWidth); + image.setImageDrawable(icon); + TextView name = (TextView) l.findViewById(R.id.widget_name); + name.setText(info.label); + TextView dims = (TextView) l.findViewById(R.id.widget_dims); + dims.setText(mContext.getString(R.string.widget_dims_format, hSpan, vSpan)); + + layout.addView(l); + } + } + + private void syncListPages(List<ResolveInfo> list) { + // we need to repopulate with PagedViewCellLayouts + removeAllViews(); + + // ensure that we have the right number of pages + int numPages = (int) Math.ceil((float) list.size() / (mCellCountX * mCellCountY)); + for (int i = 0; i < numPages; ++i) { + PagedViewCellLayout layout = new PagedViewCellLayout(getContext()); + setupPage(layout); + addView(layout); + } + } + + private void syncListPageItems(int page, List<ResolveInfo> list) { + // ensure that we have the right number of items on the pages + int numCells = mCellCountX * mCellCountY; + int startIndex = page * numCells; + int endIndex = Math.min(startIndex + numCells, list.size()); + PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page); + // TODO: we can optimize by just re-applying to existing views + layout.removeAllViews(); + for (int i = startIndex; i < endIndex; ++i) { + ResolveInfo info = list.get(i); + PendingAddItemInfo createItemInfo = new PendingAddItemInfo(); + + PagedViewIcon icon = (PagedViewIcon) mInflater.inflate( + R.layout.customize_paged_view_item, layout, false); + icon.applyFromResolveInfo(info, mPackageManager, mPageViewIconCache, + ((LauncherApplication)mLauncher.getApplication()).getIconCache()); + switch (mCustomizationType) { + case WallpaperCustomization: + icon.setOnClickListener(this); + break; + case ShortcutCustomization: + createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; + createItemInfo.componentName = new ComponentName(info.activityInfo.packageName, + info.activityInfo.name); + icon.setTag(createItemInfo); + icon.setOnClickListener(this); + icon.setOnLongClickListener(this); + break; + default: + break; + } + + final int index = i - startIndex; + final int x = index % mCellCountX; + final int y = index / mCellCountX; + setupPage(layout); + layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1)); + } + } + + private void syncAppPages() { + if (mApps == null) return; + + // We need to repopulate with PagedViewCellLayouts + removeAllViews(); + + // Ensure that we have the right number of pages + int numPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY)); + for (int i = 0; i < numPages; ++i) { + PagedViewCellLayout layout = new PagedViewCellLayout(getContext()); + setupPage(layout); + addView(layout); + } + } + + private void syncAppPageItems(int page) { + if (mApps == null) return; + + // ensure that we have the right number of items on the pages + int numCells = mCellCountX * mCellCountY; + int startIndex = page * numCells; + int endIndex = Math.min(startIndex + numCells, mApps.size()); + PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page); + // TODO: we can optimize by just re-applying to existing views + layout.removeAllViews(); + for (int i = startIndex; i < endIndex; ++i) { + final ApplicationInfo info = mApps.get(i); + PagedViewIcon icon = (PagedViewIcon) mInflater.inflate( + R.layout.all_apps_paged_view_application, layout, false); + icon.applyFromApplicationInfo(info, mPageViewIconCache, true); + icon.setOnClickListener(this); + icon.setOnLongClickListener(this); + + final int index = i - startIndex; + final int x = index % mCellCountX; + final int y = index / mCellCountX; + setupPage(layout); + layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1)); + } + } + + @Override + public void syncPages() { + boolean centerPagedViewCellLayouts = false; + switch (mCustomizationType) { + case WidgetCustomization: + syncWidgetPages(); + break; + case ShortcutCustomization: + syncListPages(mShortcutList); + centerPagedViewCellLayouts = true; + break; + case WallpaperCustomization: + syncListPages(mWallpaperList); + centerPagedViewCellLayouts = true; + break; + case ApplicationCustomization: + syncAppPages(); + centerPagedViewCellLayouts = false; + break; + default: + removeAllViews(); + setCurrentPage(0); + break; + } + + // only try and center the page if there is one page + final int childCount = getChildCount(); + if (centerPagedViewCellLayouts) { + if (childCount == 1) { + PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(0); + layout.enableCenteredContent(true); + } else { + for (int i = 0; i < childCount; ++i) { + PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i); + layout.enableCenteredContent(false); + } + } + } + + // bound the current page + setCurrentPage(Math.max(0, Math.min(childCount - 1, getCurrentPage()))); + } + + @Override + public void syncPageItems(int page) { + switch (mCustomizationType) { + case WidgetCustomization: + syncWidgetPageItems(page); + break; + case ShortcutCustomization: + syncListPageItems(page, mShortcutList); + break; + case WallpaperCustomization: + syncListPageItems(page, mWallpaperList); + break; + case ApplicationCustomization: + syncAppPageItems(page); + break; + } + } + + @Override + protected int getAssociatedLowerPageBound(int page) { + return 0; + } + @Override + protected int getAssociatedUpperPageBound(int page) { + return getChildCount(); + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + mode.setTitle(mChoiceModeTitleText); + return true; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + endChoiceMode(); + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } +} diff --git a/src/com/android/launcher2/DeferredHandler.java b/src/com/android/launcher2/DeferredHandler.java index 7801642..0323c7f 100644 --- a/src/com/android/launcher2/DeferredHandler.java +++ b/src/com/android/launcher2/DeferredHandler.java @@ -16,13 +16,12 @@ package com.android.launcher2; +import java.util.LinkedList; + import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; -import android.util.Log; - -import java.util.LinkedList; /** * Queue of things to run on a looper thread. Items posted with {@link #post} will not diff --git a/src/com/android/launcher2/DeleteZone.java b/src/com/android/launcher2/DeleteZone.java index 4e8b204..4acde1c 100644 --- a/src/com/android/launcher2/DeleteZone.java +++ b/src/com/android/launcher2/DeleteZone.java @@ -16,24 +16,24 @@ package com.android.launcher2; -import android.widget.ImageView; +import com.android.launcher.R; + import android.content.Context; import android.content.res.TypedArray; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.TransitionDrawable; import android.util.AttributeSet; import android.view.View; -import android.view.animation.TranslateAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; import android.view.animation.AccelerateInterpolator; import android.view.animation.AlphaAnimation; -import android.graphics.RectF; -import android.graphics.drawable.TransitionDrawable; - -import com.android.launcher.R; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.TranslateAnimation; +import android.widget.ImageView; public class DeleteZone extends ImageView implements DropTarget, DragController.DragListener { private static final int ORIENTATION_HORIZONTAL = 1; @@ -45,6 +45,13 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. private Launcher mLauncher; private boolean mTrashMode; + /** + * If true, this View responsible for managing its own visibility, and that of its handle. + * This is generally the case, but it will be set to false when this is part of the + * Contextual Action Bar. + */ + private boolean mManageVisibility = true; + private AnimationSet mInAnimation; private AnimationSet mOutAnimation; private Animation mHandleInAnimation; @@ -53,11 +60,14 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. private int mOrientation; private DragController mDragController; - private final RectF mRegion = new RectF(); + private final RectF mRegionF = new RectF(); + private final Rect mRegion = new Rect(); private TransitionDrawable mTransition; - private View mHandle; private final Paint mTrashPaint = new Paint(); + /** The View that this view will replace. */ + private View mHandle = null; + public DeleteZone(Context context, AttributeSet attrs) { this(context, attrs, 0); } @@ -71,6 +81,7 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DeleteZone, defStyle, 0); mOrientation = a.getInt(R.styleable.DeleteZone_direction, ORIENTATION_HORIZONTAL); a.recycle(); + } @Override @@ -83,31 +94,30 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. DragView dragView, Object dragInfo) { return true; } - - public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, - DragView dragView, Object dragInfo, Rect recycle) { - return null; - } public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { final ItemInfo item = (ItemInfo) dragInfo; + // On x-large screens, you can uninstall an app by dragging from all apps + if (item instanceof ApplicationInfo && LauncherApplication.isScreenXLarge()) { + mLauncher.startApplicationUninstallActivity((ApplicationInfo) item); + } + if (item.container == -1) return; if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { if (item instanceof LauncherAppWidgetInfo) { mLauncher.removeAppWidget((LauncherAppWidgetInfo) item); } - } else { - if (source instanceof UserFolder) { - final UserFolder userFolder = (UserFolder) source; - final UserFolderInfo userFolderInfo = (UserFolderInfo) userFolder.getInfo(); - // Item must be a ShortcutInfo otherwise it couldn't have been in the folder - // in the first place. - userFolderInfo.remove((ShortcutInfo)item); - } + } else if (source instanceof UserFolder) { + final UserFolder userFolder = (UserFolder) source; + final UserFolderInfo userFolderInfo = (UserFolderInfo) userFolder.getInfo(); + // Item must be a ShortcutInfo otherwise it couldn't have been in the folder + // in the first place. + userFolderInfo.remove((ShortcutInfo)item); } + if (item instanceof UserFolderInfo) { final UserFolderInfo userFolderInfo = (UserFolderInfo)item; LauncherModel.deleteUserFolderContentsFromDatabase(mLauncher, userFolderInfo); @@ -126,6 +136,7 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. }.start(); } } + LauncherModel.deleteItemFromDatabase(mLauncher, item); } @@ -149,16 +160,27 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. final ItemInfo item = (ItemInfo) info; if (item != null) { mTrashMode = true; - createAnimations(); - final int[] location = mLocation; - getLocationOnScreen(location); - mRegion.set(location[0], location[1], location[0] + mRight - mLeft, - location[1] + mBottom - mTop); - mDragController.setDeleteRegion(mRegion); + getHitRect(mRegion); + mRegionF.set(mRegion); + + if (LauncherApplication.isScreenXLarge()) { + // This region will be a "dead zone" for scrolling; make it extend to the edge of + // the screen so users don't accidentally trigger a scroll while deleting items + mRegionF.top = mLauncher.getWorkspace().getTop(); + mRegionF.right = mLauncher.getWorkspace().getRight(); + } + + mDragController.setDeleteRegion(mRegionF); + + // Make sure the icon is set to the default drawable, not the hover drawable mTransition.resetTransition(); - startAnimation(mInAnimation); - mHandle.startAnimation(mHandleOutAnimation); - setVisibility(VISIBLE); + + if (mManageVisibility) { + createAnimations(); + startAnimation(mInAnimation); + mHandle.startAnimation(mHandleOutAnimation); + setVisibility(VISIBLE); + } } } @@ -166,9 +188,33 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. if (mTrashMode) { mTrashMode = false; mDragController.setDeleteRegion(null); - startAnimation(mOutAnimation); - mHandle.startAnimation(mHandleInAnimation); - setVisibility(GONE); + + if (mOutAnimation != null) startAnimation(mOutAnimation); + if (mHandleInAnimation != null) mHandle.startAnimation(mHandleInAnimation); + + if (mManageVisibility) { + setVisibility(GONE); + } + } + } + + public boolean isDropEnabled() { + return true; + } + + @Override + public void getHitRect(Rect outRect) { + super.getHitRect(outRect); + if (LauncherApplication.isScreenXLarge()) { + // TODO: This is a temporary hack. mManageVisiblity = false when you're in CAB mode. + // In that case, this icon is more tightly spaced next to the delete icon so we want + // it to have a smaller drag region. When the new drag&drop system comes in, we'll + // dispatch the drag/drop by calculating what target you're overlapping + final int dragPadding = mManageVisibility ? 50 : 10; + outRect.top -= dragPadding; + outRect.left -= dragPadding; + outRect.bottom += dragPadding; + outRect.right += dragPadding; } } @@ -228,6 +274,10 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. mHandle = view; } + void setManageVisibility(boolean value) { + mManageVisibility = value; + } + private static class FastTranslateAnimation extends TranslateAnimation { public FastTranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue) { @@ -261,4 +311,10 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. return false; } } + + @Override + public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + return null; + } } diff --git a/src/com/android/launcher2/Dimmable.java b/src/com/android/launcher2/Dimmable.java new file mode 100644 index 0000000..df43b3c --- /dev/null +++ b/src/com/android/launcher2/Dimmable.java @@ -0,0 +1,6 @@ +package com.android.launcher2; + +public interface Dimmable { + public void setDimmableProgress(float progress); + public float getDimmableProgress(); +} diff --git a/src/com/android/launcher2/DimmableAppWidgetHostView.java b/src/com/android/launcher2/DimmableAppWidgetHostView.java new file mode 100644 index 0000000..1f512a3 --- /dev/null +++ b/src/com/android/launcher2/DimmableAppWidgetHostView.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import com.android.launcher.R; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.view.View; + +public class DimmableAppWidgetHostView extends LauncherAppWidgetHostView implements Dimmable { + public DimmableAppWidgetHostView(Context context) { + super(context); + mPaint.setFilterBitmap(true); + } + + private final Paint mPaint = new Paint(); + private Bitmap mDimmedView; + private Canvas mDimmedViewCanvas; + private boolean isDimmedViewUpdatePass; + private float mDimmableProgress; + + private void setChildAlpha(float alpha) { + if (getChildCount() > 0) { + final View child = getChildAt(0); + if (child.getAlpha() != alpha) { + getChildAt(0).setAlpha(alpha); + } + } + } + + private void updateChildAlpha() { + // hacky, but sometimes widgets get their alpha set back to 1.0f, so we call + // this to force them back + setChildAlpha(getAlpha()); + } + + //@Override + public boolean onSetAlpha(int alpha) { + super.onSetAlpha(alpha); + return true; + } + + public void setDimmableProgress(float progress) { + mDimmableProgress = progress; + } + + public float getDimmableProgress() { + return mDimmableProgress; + } + + private void updateDimmedView() { + if (mDimmedView == null) { + mDimmedView = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), + Bitmap.Config.ARGB_8888); + mDimmedViewCanvas = new Canvas(mDimmedView); + } + mDimmedViewCanvas.drawColor(0x00000000); + mDimmedViewCanvas.concat(getMatrix()); + isDimmedViewUpdatePass = true; + draw(mDimmedViewCanvas); + // make the bitmap look "dimmed" + int dimmedColor = getContext().getResources().getColor(R.color.dimmed_view_color); + mDimmedViewCanvas.drawColor(dimmedColor, PorterDuff.Mode.SRC_IN); + isDimmedViewUpdatePass = false; + invalidate(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (mDimmedView == null && mDimmableProgress > 0.0f) { + updateDimmedView(); + } + } + + @Override + public void dispatchDraw(Canvas canvas) { + if (isDimmedViewUpdatePass) { + final float alpha = getAlpha(); + canvas.save(); + setAlpha(1.0f); + super.dispatchDraw(canvas); + canvas.restore(); + setAlpha(alpha); + } else { + if (mDimmedView != null && mDimmableProgress > 0) { + // draw the dimmed version of this widget + mPaint.setAlpha((int) (mDimmableProgress * 255)); + canvas.drawBitmap(mDimmedView, 0, 0, mPaint); + } + + updateChildAlpha(); + super.dispatchDraw(canvas); + } + } +} diff --git a/src/com/android/launcher2/DimmableBubbleTextView.java b/src/com/android/launcher2/DimmableBubbleTextView.java new file mode 100644 index 0000000..cb3b8ef --- /dev/null +++ b/src/com/android/launcher2/DimmableBubbleTextView.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import com.android.launcher.R; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.util.AttributeSet; + +public class DimmableBubbleTextView extends BubbleTextView implements Dimmable { + private Paint mDimmedPaint = new Paint(); + private int mAlpha; + private Bitmap mDimmedView; + private Canvas mDimmedViewCanvas; + private boolean isDimmedViewUpdatePass; + private float mDimmableProgress; + + public DimmableBubbleTextView(Context context) { + super(context); + mDimmedPaint.setFilterBitmap(true); + } + + public DimmableBubbleTextView(Context context, AttributeSet attrs) { + super(context, attrs); + mDimmedPaint.setFilterBitmap(true); + } + + public DimmableBubbleTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mDimmedPaint.setFilterBitmap(true); + } + + public void setDimmableProgress(float progress) { + mDimmableProgress = progress; + } + + public float getDimmableProgress() { + return mDimmableProgress; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (mDimmedView == null) { + isDimmedViewUpdatePass = true; + mDimmedView = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), + Bitmap.Config.ARGB_8888); + mDimmedViewCanvas = new Canvas(mDimmedView); + mDimmedViewCanvas.concat(getMatrix()); + + draw(mDimmedViewCanvas); + + // MAKE THE DIMMED VERSION + int dimmedColor = getContext().getResources().getColor(R.color.dimmed_view_color); + mDimmedViewCanvas.drawColor(dimmedColor, PorterDuff.Mode.SRC_IN); + + isDimmedViewUpdatePass = false; + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (isDimmedViewUpdatePass) { + canvas.save(); + final float alpha = getAlpha(); + super.setAlpha(1.0f); + super.onDraw(canvas); + super.setAlpha(alpha); + canvas.restore(); + } else { + super.onDraw(canvas); + } + + if (mDimmedView != null && mDimmableProgress > 0) { + mDimmedPaint.setAlpha((int) (mDimmableProgress * 255)); + canvas.drawBitmap(mDimmedView, mScrollX, mScrollY, mDimmedPaint); + } + } +}
\ No newline at end of file diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java index b453061..3c956f0 100644 --- a/src/com/android/launcher2/DragController.java +++ b/src/com/android/launcher2/DragController.java @@ -16,25 +16,27 @@ package com.android.launcher2; +import com.android.launcher.R; + import android.content.Context; import android.graphics.Bitmap; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; -import android.os.IBinder; import android.os.Handler; +import android.os.IBinder; import android.os.Vibrator; import android.util.DisplayMetrics; import android.util.Log; -import android.view.View; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import java.util.ArrayList; -import com.android.launcher.R; - /** * Class for initiating a drag within a view or across multiple views. */ @@ -56,8 +58,8 @@ public class DragController { 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; + static final int SCROLL_LEFT = 0; + static final int SCROLL_RIGHT = 1; private Context mContext; private Handler mHandler; @@ -105,7 +107,7 @@ public class DragController { /** Who can receive drop events */ private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>(); - private DragListener mListener; + private ArrayList<DragListener> mListeners = new ArrayList<DragListener>(); /** The window token used as the parent for the DragView. */ private IBinder mWindowToken; @@ -124,6 +126,9 @@ public class DragController { private InputMethodManager mInputMethodManager; + private int mLastTouch[] = new int[2]; + private int mDistanceSinceScroll = 0; + /** * Interface to receive notifications when a drag starts or stops */ @@ -140,7 +145,7 @@ public class DragController { void onDragStart(DragSource source, Object info, int dragAction); /** - * The drag has eneded + * The drag has ended */ void onDragEnd(); } @@ -156,9 +161,13 @@ public class DragController { mScrollZone = context.getResources().getDimensionPixelSize(R.dimen.scroll_zone); } + public boolean dragging() { + return mDragging; + } + /** * Starts a drag. - * + * * @param v The view that is being dragged * @param source An object representing where the drag originated * @param dragInfo The data associated with the object that is being dragged @@ -166,6 +175,22 @@ public class DragController { * {@link #DRAG_ACTION_COPY} */ public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) { + startDrag(v, source, dragInfo, dragAction, null); + } + + /** + * Starts a drag. + * + * @param v The view that is being dragged + * @param source An object representing where the drag originated + * @param dragInfo The data associated with the object that is being dragged + * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or + * {@link #DRAG_ACTION_COPY} + * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. + * Makes dragging feel more precise, e.g. you can clip out a transparent border + */ + public void startDrag(View v, DragSource source, Object dragInfo, int dragAction, + Rect dragRegion) { mOriginator = v; Bitmap b = getViewBitmap(v); @@ -181,7 +206,7 @@ public class DragController { int screenY = loc[1]; startDrag(b, screenX, screenY, 0, 0, b.getWidth(), b.getHeight(), - source, dragInfo, dragAction); + source, dragInfo, dragAction, dragRegion); b.recycle(); @@ -192,7 +217,36 @@ public class DragController { /** * Starts a drag. - * + * + * @param v The view that is being dragged + * @param bmp The bitmap that represents the view being dragged + * @param source An object representing where the drag originated + * @param dragInfo The data associated with the object that is being dragged + * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or + * {@link #DRAG_ACTION_COPY} + * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. + * Makes dragging feel more precise, e.g. you can clip out a transparent border + */ + public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction, + Rect dragRegion) { + mOriginator = v; + + int[] loc = mCoordinatesTemp; + v.getLocationOnScreen(loc); + int screenX = loc[0]; + int screenY = loc[1]; + + startDrag(bmp, screenX, screenY, 0, 0, bmp.getWidth(), bmp.getHeight(), + source, dragInfo, dragAction, dragRegion); + + if (dragAction == DRAG_ACTION_MOVE) { + v.setVisibility(View.GONE); + } + } + + /** + * Starts a drag. + * * @param b The bitmap to display as the drag image. It will be re-scaled to the * enlarged size. * @param screenX The x position on screen of the left-top of the bitmap. @@ -209,6 +263,31 @@ public class DragController { public void startDrag(Bitmap b, int screenX, int screenY, int textureLeft, int textureTop, int textureWidth, int textureHeight, DragSource source, Object dragInfo, int dragAction) { + startDrag(b, screenX, screenY, textureLeft, textureTop, textureWidth, textureHeight, + source, dragInfo, dragAction, null); + } + + /** + * Starts a drag. + * + * @param b The bitmap to display as the drag image. It will be re-scaled to the + * enlarged size. + * @param screenX The x position on screen of the left-top of the bitmap. + * @param screenY The y position on screen of the left-top of the bitmap. + * @param textureLeft The left edge of the region inside b to use. + * @param textureTop The top edge of the region inside b to use. + * @param textureWidth The width of the region inside b to use. + * @param textureHeight The height of the region inside b to use. + * @param source An object representing where the drag originated + * @param dragInfo The data associated with the object that is being dragged + * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or + * {@link #DRAG_ACTION_COPY} + * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. + * Makes dragging feel more precise, e.g. you can clip out a transparent border + */ + public void startDrag(Bitmap b, int screenX, int screenY, + int textureLeft, int textureTop, int textureWidth, int textureHeight, + DragSource source, Object dragInfo, int dragAction, Rect dragRegion) { if (PROFILE_DRAWING_DURING_DRAG) { android.os.Debug.startMethodTracing("Launcher"); } @@ -220,15 +299,17 @@ public class DragController { } mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); - if (mListener != null) { - mListener.onDragStart(source, dragInfo, dragAction); + for (DragListener listener : mListeners) { + listener.onDragStart(source, dragInfo, dragAction); } int registrationX = ((int)mMotionDownX) - screenX; int registrationY = ((int)mMotionDownY) - screenY; - mTouchOffsetX = mMotionDownX - screenX; - mTouchOffsetY = mMotionDownY - screenY; + final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; + final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; + mTouchOffsetX = mMotionDownX - screenX - dragRegionLeft; + mTouchOffsetY = mMotionDownY - screenY - dragRegionTop; mDragging = true; mDragSource = source; @@ -238,7 +319,15 @@ public class DragController { DragView dragView = mDragView = new DragView(mContext, b, registrationX, registrationY, textureLeft, textureTop, textureWidth, textureHeight); + + if (dragRegion != null) { + dragView.setDragRegion(dragRegionLeft, dragRegion.top, + dragRegion.right - dragRegionLeft, dragRegion.bottom - dragRegionTop); + } + dragView.show(mWindowToken, (int)mMotionDownX, (int)mMotionDownY); + + handleMoveEvent((int) mMotionDownX, (int) mMotionDownY); } /** @@ -304,8 +393,8 @@ public class DragController { if (mOriginator != null) { mOriginator.setVisibility(View.VISIBLE); } - if (mListener != null) { - mListener.onDragEnd(); + for (DragListener listener : mListeners) { + listener.onDragEnd(); } if (mDragView != null) { mDragView.remove(); @@ -365,12 +454,80 @@ public class DragController { return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); } + private void handleMoveEvent(int x, int y) { + mDragView.move(x, y); + + // Drop on someone? + final int[] coordinates = mCoordinatesTemp; + DropTarget dropTarget = findDropTarget(x, y, coordinates); + if (dropTarget != null) { + DropTarget delegate = dropTarget.getDropTargetDelegate( + mDragSource, coordinates[0], coordinates[1], + (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); + if (delegate != null) { + dropTarget = delegate; + } + + if (mLastDropTarget != dropTarget) { + if (mLastDropTarget != null) { + mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1], + (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); + } + dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1], + (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); + } + dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1], + (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); + } else { + if (mLastDropTarget != null) { + mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1], + (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); + } + } + mLastDropTarget = dropTarget; + + // Scroll, maybe, but not if we're in the delete region. + boolean inDeleteRegion = false; + if (mDeleteRegion != null) { + inDeleteRegion = mDeleteRegion.contains(x, y); + } + + // After a scroll, the touch point will still be in the scroll region. + // Rather than scrolling immediately, require a bit of twiddling to scroll again + final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop(); + mDistanceSinceScroll += + Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); + mLastTouch[0] = x; + mLastTouch[1] = y; + + if (!inDeleteRegion && x < mScrollZone) { + if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) { + mScrollState = SCROLL_WAITING_IN_ZONE; + mScrollRunnable.setDirection(SCROLL_LEFT); + mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); + mDragScroller.onEnterScrollArea(SCROLL_LEFT); + } + } else if (!inDeleteRegion && x > mScrollView.getWidth() - mScrollZone) { + if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) { + mScrollState = SCROLL_WAITING_IN_ZONE; + mScrollRunnable.setDirection(SCROLL_RIGHT); + mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); + mDragScroller.onEnterScrollArea(SCROLL_RIGHT); + } + } else { + if (mScrollState == SCROLL_WAITING_IN_ZONE) { + mScrollState = SCROLL_OUTSIDE_ZONE; + mScrollRunnable.setDirection(SCROLL_RIGHT); + mHandler.removeCallbacks(mScrollRunnable); + mDragScroller.onExitScrollArea(); + } + } + } + /** * Call this from a drag source view. */ public boolean onTouchEvent(MotionEvent ev) { - View scrollView = mScrollView; - if (!mDragging) { return false; } @@ -385,69 +542,15 @@ public class DragController { mMotionDownX = screenX; mMotionDownY = screenY; - if ((screenX < mScrollZone) || (screenX > scrollView.getWidth() - mScrollZone)) { + if ((screenX < mScrollZone) || (screenX > mScrollView.getWidth() - mScrollZone)) { mScrollState = SCROLL_WAITING_IN_ZONE; mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); } else { mScrollState = SCROLL_OUTSIDE_ZONE; } - break; case MotionEvent.ACTION_MOVE: - // Update the drag view. Don't use the clamped pos here so the dragging looks - // like it goes off screen a little, intead of bumping up against the edge. - mDragView.move((int)ev.getRawX(), (int)ev.getRawY()); - - // Drop on someone? - final int[] coordinates = mCoordinatesTemp; - DropTarget dropTarget = findDropTarget(screenX, screenY, coordinates); - if (dropTarget != null) { - if (mLastDropTarget == dropTarget) { - dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1], - (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); - } else { - if (mLastDropTarget != null) { - mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1], - (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); - } - dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1], - (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); - } - } else { - if (mLastDropTarget != null) { - mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1], - (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); - } - } - mLastDropTarget = dropTarget; - - // Scroll, maybe, but not if we're in the delete region. - boolean inDeleteRegion = false; - if (mDeleteRegion != null) { - inDeleteRegion = mDeleteRegion.contains(screenX, screenY); - } - //Log.d(Launcher.TAG, "inDeleteRegion=" + inDeleteRegion + " screenX=" + screenX - // + " mScrollZone=" + mScrollZone); - if (!inDeleteRegion && screenX < mScrollZone) { - if (mScrollState == SCROLL_OUTSIDE_ZONE) { - mScrollState = SCROLL_WAITING_IN_ZONE; - mScrollRunnable.setDirection(SCROLL_LEFT); - mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); - } - } else if (!inDeleteRegion && screenX > scrollView.getWidth() - mScrollZone) { - if (mScrollState == SCROLL_OUTSIDE_ZONE) { - mScrollState = SCROLL_WAITING_IN_ZONE; - mScrollRunnable.setDirection(SCROLL_RIGHT); - mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); - } - } else { - if (mScrollState == SCROLL_WAITING_IN_ZONE) { - mScrollState = SCROLL_OUTSIDE_ZONE; - mScrollRunnable.setDirection(SCROLL_RIGHT); - mHandler.removeCallbacks(mScrollRunnable); - } - } - + handleMoveEvent(screenX, screenY); break; case MotionEvent.ACTION_UP: mHandler.removeCallbacks(mScrollRunnable); @@ -491,13 +594,28 @@ public class DragController { final ArrayList<DropTarget> dropTargets = mDropTargets; final int count = dropTargets.size(); for (int i=count-1; i>=0; i--) { - final DropTarget target = dropTargets.get(i); + DropTarget target = dropTargets.get(i); + if (!target.isDropEnabled()) + continue; + target.getHitRect(r); + + // Convert the hit rect to screen coordinates target.getLocationOnScreen(dropCoordinates); r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop()); + if (r.contains(x, y)) { + DropTarget delegate = target.getDropTargetDelegate(mDragSource, + x, y, (int)mTouchOffsetX, (int)mTouchOffsetY, mDragView, mDragInfo); + if (delegate != null) { + target = delegate; + target.getLocationOnScreen(dropCoordinates); + } + + // Make dropCoordinates relative to the DropTarget dropCoordinates[0] = x - dropCoordinates[0]; dropCoordinates[1] = y - dropCoordinates[1]; + return target; } } @@ -537,15 +655,15 @@ public class DragController { /** * Sets the drag listner which will be notified when a drag starts or ends. */ - public void setDragListener(DragListener l) { - mListener = l; + public void addDragListener(DragListener l) { + mListeners.add(l); } /** * Remove a previously installed drag listener. */ public void removeDragListener(DragListener l) { - mListener = null; + mListeners.remove(l); } /** @@ -592,6 +710,8 @@ public class DragController { mDragScroller.scrollRight(); } mScrollState = SCROLL_OUTSIDE_ZONE; + mDistanceSinceScroll = 0; + mDragScroller.onExitScrollArea(); } } diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java index c683207..ab71670 100644 --- a/src/com/android/launcher2/DragLayer.java +++ b/src/com/android/launcher2/DragLayer.java @@ -18,13 +18,13 @@ package com.android.launcher2; import android.content.Context; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; /** - * A ViewGroup that coordinated dragging across its dscendants + * A ViewGroup that coordinates dragging across its descendants */ public class DragLayer extends FrameLayout { DragController mDragController; @@ -33,7 +33,7 @@ public class DragLayer extends FrameLayout { * Used to create a new DragLayer from XML. * * @param context The application's context. - * @param attrs The attribtues set containing the Workspace's customization values. + * @param attrs The attributes set containing the Workspace's customization values. */ public DragLayer(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/src/com/android/launcher2/DragScroller.java b/src/com/android/launcher2/DragScroller.java index c3c251c..6af9c30 100644 --- a/src/com/android/launcher2/DragScroller.java +++ b/src/com/android/launcher2/DragScroller.java @@ -23,4 +23,16 @@ package com.android.launcher2; public interface DragScroller { void scrollLeft(); void scrollRight(); + + /** + * The touch point has entered the scroll area; a scroll is imminent. + * + * @param direction The scroll direction + */ + void onEnterScrollArea(int direction); + + /** + * The touch point has left the scroll area. + */ + void onExitScrollArea(); } diff --git a/src/com/android/launcher2/DragView.java b/src/com/android/launcher2/DragView.java index 248712e..ca0e7b4 100644 --- a/src/com/android/launcher2/DragView.java +++ b/src/com/android/launcher2/DragView.java @@ -17,33 +17,33 @@ package com.android.launcher2; +import com.android.launcher.R; + import android.content.Context; -import android.content.res.TypedArray; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.Point; import android.os.IBinder; -import android.util.AttributeSet; -import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.view.KeyEvent; import android.view.WindowManager; import android.view.WindowManagerImpl; public class DragView extends View implements TweenCallback { - // Number of pixels to add to the dragged item for scaling. Should be even for pixel alignment. - private static final int DRAG_SCALE = 40; - private Bitmap mBitmap; private Paint mPaint; private int mRegistrationX; private int mRegistrationY; + private int mDragRegionLeft = 0; + private int mDragRegionTop = 0; + private int mDragRegionWidth; + private int mDragRegionHeight; + SymmetricalLinearTween mTween; private float mScale; private float mAnimationScale = 1.0f; @@ -66,20 +66,63 @@ public class DragView extends View implements TweenCallback { int left, int top, int width, int height) { super(context); + final Resources res = getResources(); + final int dragScale = res.getInteger(R.integer.config_dragViewExtraPixels); + mWindowManager = WindowManagerImpl.getDefault(); mTween = new SymmetricalLinearTween(false, 110 /*ms duration*/, this); Matrix scale = new Matrix(); float scaleFactor = width; - scaleFactor = mScale = (scaleFactor + DRAG_SCALE) / scaleFactor; + scaleFactor = mScale = (scaleFactor + dragScale) / scaleFactor; scale.setScale(scaleFactor, scaleFactor); mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height, scale, true); + setDragRegion(0, 0, width, height); // The point in our scaled bitmap that the touch events are located - mRegistrationX = registrationX + (DRAG_SCALE / 2); - mRegistrationY = registrationY + (DRAG_SCALE / 2); + mRegistrationX = registrationX + res.getInteger(R.integer.config_dragViewOffsetX); + mRegistrationY = registrationY + res.getInteger(R.integer.config_dragViewOffsetY); + } + + public void setDragRegion(int left, int top, int width, int height) { + mDragRegionLeft = left; + mDragRegionTop = top; + mDragRegionWidth = width; + mDragRegionHeight = height; + } + + public int getScaledDragRegionXOffset() { + return -(int)((mScale - 1.0f) * mDragRegionWidth / 2); + } + + public int getScaledDragRegionWidth() { + return (int)(mScale * mDragRegionWidth); + } + + public int getScaledDragRegionYOffset() { + return -(int)((mScale - 1.0f) * mDragRegionHeight / 2); + } + + public int getScaledDragRegionHeight() { + return (int)(mScale * mDragRegionWidth); + } + + public int getDragRegionLeft() { + return mDragRegionLeft; + } + + public int getDragRegionTop() { + return mDragRegionTop; + } + + public int getDragRegionWidth() { + return mDragRegionWidth; + } + + public int getDragRegionHeight() { + return mDragRegionHeight; } @Override diff --git a/src/com/android/launcher2/DropTarget.java b/src/com/android/launcher2/DropTarget.java index 72eb330..308dbbe 100644 --- a/src/com/android/launcher2/DropTarget.java +++ b/src/com/android/launcher2/DropTarget.java @@ -23,6 +23,12 @@ import android.graphics.Rect; * */ public interface DropTarget { + /** + * Used to temporarily disable certain drop targets + * + * @return boolean specifying whether this drop target is currently enabled + */ + boolean isDropEnabled(); /** * Handle an object being dropped on the DropTarget @@ -51,27 +57,28 @@ public interface DropTarget { DragView dragView, Object dragInfo); /** - * 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. - * + * Allows a DropTarget to delegate drag and drop events to another object. + * + * Most subclasses will should just return null from this method. + * * @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 dragView The DragView that's being dragged around on screen. * @param dragInfo Data associated with the object being dragged - * @return True if the drop will be accepted, false otherwise. + * + * @return The DropTarget to delegate to, or null to not delegate to another object. */ - boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, + DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); /** - * Estimate the surface area where this object would land if dropped at the - * given location. + * Check if a drop action can occur at, or near, the requested location. + * This will be called just before onDrop. * * @param source DragSource where the drag started * @param x X coordinate of the drop location @@ -82,13 +89,10 @@ public interface DropTarget { * original touch happened * @param dragView The DragView that's being dragged around on screen. * @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. + * @return True if the drop will be accepted, false otherwise. */ - Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, - DragView dragView, Object dragInfo, Rect recycle); + boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo); // These methods are implemented in Views void getHitRect(Rect outRect); diff --git a/src/com/android/launcher2/FastBitmapDrawable.java b/src/com/android/launcher2/FastBitmapDrawable.java index 226d6d8..1cafa09 100644 --- a/src/com/android/launcher2/FastBitmapDrawable.java +++ b/src/com/android/launcher2/FastBitmapDrawable.java @@ -17,6 +17,7 @@ package com.android.launcher2; import android.graphics.drawable.Drawable; +import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -26,6 +27,7 @@ class FastBitmapDrawable extends Drawable { private Bitmap mBitmap; private int mWidth; private int mHeight; + private final Paint mPaint = new Paint(); FastBitmapDrawable(Bitmap b) { mBitmap = b; @@ -39,7 +41,7 @@ class FastBitmapDrawable extends Drawable { @Override public void draw(Canvas canvas) { - canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null); + canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint); } @Override @@ -49,6 +51,7 @@ class FastBitmapDrawable extends Drawable { @Override public void setAlpha(int alpha) { + mPaint.setAlpha(alpha); } @Override diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java index 7ff8328..018b284 100644 --- a/src/com/android/launcher2/Folder.java +++ b/src/com/android/launcher2/Folder.java @@ -21,11 +21,11 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; +import android.widget.AbsListView; import android.widget.AdapterView; +import android.widget.BaseAdapter; import android.widget.Button; import android.widget.LinearLayout; -import android.widget.AbsListView; -import android.widget.BaseAdapter; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; @@ -148,7 +148,7 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL void onClose() { final Workspace workspace = mLauncher.getWorkspace(); - workspace.getChildAt(workspace.getCurrentScreen()).requestFocus(); + workspace.getChildAt(workspace.getCurrentPage()).requestFocus(); } void bind(FolderInfo info) { diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java index 0013644..dae84aa 100644 --- a/src/com/android/launcher2/FolderIcon.java +++ b/src/com/android/launcher2/FolderIcon.java @@ -29,7 +29,7 @@ import com.android.launcher.R; /** * An icon that can appear on in the workspace representing an {@link UserFolder}. */ -public class FolderIcon extends BubbleTextView implements DropTarget { +public class FolderIcon extends DimmableBubbleTextView implements DropTarget { private UserFolderInfo mInfo; private Launcher mLauncher; private Drawable mCloseIcon; @@ -43,15 +43,19 @@ public class FolderIcon extends BubbleTextView implements DropTarget { super(context); } + public boolean isDropEnabled() { + return !((Workspace)getParent().getParent()).isSmall(); + } + static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group, - UserFolderInfo folderInfo) { + UserFolderInfo folderInfo, IconCache iconCache) { FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); final Resources resources = launcher.getResources(); - Drawable d = resources.getDrawable(R.drawable.ic_launcher_folder); + Drawable d = iconCache.getFullResIcon(resources, R.drawable.ic_launcher_folder); icon.mCloseIcon = d; - icon.mOpenIcon = resources.getDrawable(R.drawable.ic_launcher_folder_open); + icon.mOpenIcon = iconCache.getFullResIcon(resources, R.drawable.ic_launcher_folder_open); icon.setCompoundDrawablesWithIntrinsicBounds(null, d, null, null); icon.setText(folderInfo.title); icon.setTag(folderInfo); @@ -71,11 +75,6 @@ public class FolderIcon extends BubbleTextView implements DropTarget { && item.container != mInfo.id; } - public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, - DragView dragView, Object dragInfo, Rect recycle) { - return null; - } - public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { ShortcutInfo item; @@ -91,7 +90,9 @@ public class FolderIcon extends BubbleTextView implements DropTarget { public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { - setCompoundDrawablesWithIntrinsicBounds(null, mOpenIcon, null, null); + if (acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) { + setCompoundDrawablesWithIntrinsicBounds(null, mOpenIcon, null, null); + } } public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, @@ -102,4 +103,10 @@ public class FolderIcon extends BubbleTextView implements DropTarget { DragView dragView, Object dragInfo) { setCompoundDrawablesWithIntrinsicBounds(null, mCloseIcon, null, null); } + + @Override + public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + return null; + } } diff --git a/src/com/android/launcher2/HolographicOutlineHelper.java b/src/com/android/launcher2/HolographicOutlineHelper.java new file mode 100644 index 0000000..658490a --- /dev/null +++ b/src/com/android/launcher2/HolographicOutlineHelper.java @@ -0,0 +1,186 @@ +/* + * 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.launcher2; + +import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; +import android.graphics.Canvas; +import android.graphics.MaskFilter; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.TableMaskFilter; + +public class HolographicOutlineHelper { + private final Paint mHolographicPaint = new Paint(); + private final Paint mBlurPaint = new Paint(); + private final Paint mErasePaint = new Paint(); + + public static final int OUTER_BLUR_RADIUS; + + private static final BlurMaskFilter sThickOuterBlurMaskFilter; + private static final BlurMaskFilter sMediumOuterBlurMaskFilter; + private static final BlurMaskFilter sThinOuterBlurMaskFilter; + private static final BlurMaskFilter sThickInnerBlurMaskFilter; + + static { + final float scale = LauncherApplication.getScreenDensity(); + + OUTER_BLUR_RADIUS = (int) (scale * 6.0f); + + sThickOuterBlurMaskFilter = new BlurMaskFilter(OUTER_BLUR_RADIUS, + BlurMaskFilter.Blur.OUTER); + sMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER); + sThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER); + sThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL); + } + + private static final MaskFilter sFineClipTable = TableMaskFilter.CreateClipTable(0, 20); + private static final MaskFilter sCoarseClipTable = TableMaskFilter.CreateClipTable(0, 200); + + private int[] mTempOffset = new int[2]; + + HolographicOutlineHelper() { + mHolographicPaint.setFilterBitmap(true); + mHolographicPaint.setAntiAlias(true); + mBlurPaint.setFilterBitmap(true); + mBlurPaint.setAntiAlias(true); + mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + mErasePaint.setFilterBitmap(true); + mErasePaint.setAntiAlias(true); + } + + /** + * Returns the interpolated holographic highlight alpha for the effect we want when scrolling + * pages. + */ + public float highlightAlphaInterpolator(float r) { + float maxAlpha = 0.8f; + return (float) Math.pow(maxAlpha * (1.0f - r), 1.5f); + } + + /** + * Returns the interpolated view alpha for the effect we want when scrolling pages. + */ + public float viewAlphaInterpolator(float r) { + final float pivot = 0.95f; + if (r < pivot) { + return (float) Math.pow(r / pivot, 1.5f); + } else { + return 1.0f; + } + } + + /** + * Apply an outer blur to the given bitmap. + * You should use OUTER_BLUR_RADIUS to ensure that the bitmap is big enough to draw + * the blur without clipping. + */ + void applyOuterBlur(Bitmap bitmap, Canvas canvas, int color) { + mBlurPaint.setMaskFilter(sThickOuterBlurMaskFilter); + Bitmap glow = bitmap.extractAlpha(mBlurPaint, mTempOffset); + + // Use the clip table to make the glow heavier closer to the outline + mHolographicPaint.setMaskFilter(sCoarseClipTable); + mHolographicPaint.setAlpha(150); + mHolographicPaint.setColor(color); + canvas.drawBitmap(glow, mTempOffset[0], mTempOffset[1], mHolographicPaint); + glow.recycle(); + } + + /** + * Draws a solid outline around a bitmap, erasing the original pixels. + * + * @param bitmap The bitmap to modify + * @param canvas A canvas on the bitmap + * @param color The color to draw the outline and glow in + * @param removeOrig If true, punch out the original pixels to just leave the outline + */ + void applyExpensiveOuterOutline(Bitmap bitmap, Canvas canvas, int color, boolean removeOrig) { + Bitmap originalImage = null; + if (removeOrig) { + originalImage = bitmap.extractAlpha(); + } + + // Compute an outer blur on the original bitmap + mBlurPaint.setMaskFilter(sMediumOuterBlurMaskFilter); + Bitmap outline = bitmap.extractAlpha(mBlurPaint, mTempOffset); + + // Paint the blurred bitmap back into the canvas. Using the clip table causes any alpha + // pixels above a certain threshold to be rounded up to be fully opaque. This gives the + // effect of a thick outline, with a slight blur on the edge + mHolographicPaint.setColor(color); + mHolographicPaint.setMaskFilter(sFineClipTable); + canvas.drawBitmap(outline, mTempOffset[0], mTempOffset[1], mHolographicPaint); + outline.recycle(); + + if (removeOrig) { + // Finally, punch out the original pixels, leaving just the outline + canvas.drawBitmap(originalImage, 0, 0, mErasePaint); + originalImage.recycle(); + } + } + + /** + * Applies a more expensive and accurate outline to whatever is currently drawn in a specified + * bitmap. + */ + void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, + int outlineColor) { + // calculate the outer blur first + mBlurPaint.setMaskFilter(sThickOuterBlurMaskFilter); + int[] outerBlurOffset = new int[2]; + Bitmap thickOuterBlur = srcDst.extractAlpha(mBlurPaint, outerBlurOffset); + mBlurPaint.setMaskFilter(sThinOuterBlurMaskFilter); + int[] thinOuterBlurOffset = new int[2]; + Bitmap thinOuterBlur = srcDst.extractAlpha(mBlurPaint, thinOuterBlurOffset); + + // calculate the inner blur + srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); + mBlurPaint.setMaskFilter(sThickInnerBlurMaskFilter); + int[] thickInnerBlurOffset = new int[2]; + Bitmap thickInnerBlur = srcDst.extractAlpha(mBlurPaint, thickInnerBlurOffset); + + // mask out the inner blur + srcDstCanvas.setBitmap(thickInnerBlur); + srcDstCanvas.drawBitmap(srcDst, -thickInnerBlurOffset[0], + -thickInnerBlurOffset[1], mErasePaint); + srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), + mErasePaint); + srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], + mErasePaint); + + // draw the inner and outer blur + srcDstCanvas.setBitmap(srcDst); + srcDstCanvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); + mHolographicPaint.setColor(color); + srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1], + mHolographicPaint); + srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], + mHolographicPaint); + + // draw the bright outline + mHolographicPaint.setColor(outlineColor); + srcDstCanvas.drawBitmap(thinOuterBlur, thinOuterBlurOffset[0], thinOuterBlurOffset[1], + mHolographicPaint); + + // cleanup + thinOuterBlur.recycle(); + thickOuterBlur.recycle(); + thickInnerBlur.recycle(); + } +} diff --git a/src/com/android/launcher2/IconCache.java b/src/com/android/launcher2/IconCache.java index 81a786c..ae8c98a 100644 --- a/src/com/android/launcher2/IconCache.java +++ b/src/com/android/launcher2/IconCache.java @@ -16,13 +16,17 @@ package com.android.launcher2; +import com.android.launcher.R; + import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; import java.util.HashMap; @@ -46,16 +50,49 @@ public class IconCache { private final Utilities.BubbleText mBubble; private final HashMap<ComponentName, CacheEntry> mCache = new HashMap<ComponentName, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY); + private int mIconDpi; public IconCache(LauncherApplication context) { mContext = context; mPackageManager = context.getPackageManager(); mBubble = new Utilities.BubbleText(context); + if (LauncherApplication.isScreenXLarge()) { + mIconDpi = DisplayMetrics.DENSITY_HIGH; + } else { + mIconDpi = context.getResources().getDisplayMetrics().densityDpi; + } + // need to set mIconDpi before getting default icon mDefaultIcon = makeDefaultIcon(); } + public Drawable getFullResDefaultActivityIcon() { + return getFullResIcon(Resources.getSystem(), + com.android.internal.R.drawable.sym_def_app_icon); + } + + public Drawable getFullResIcon(Resources resources, int iconId) { + return resources.getDrawableForDensity(iconId, mIconDpi); + } + + public Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) { + Resources resources; + try { + resources = packageManager.getResourcesForApplication( + info.activityInfo.applicationInfo); + } catch (PackageManager.NameNotFoundException e) { + resources = null; + } + if (resources != null) { + int iconId = info.activityInfo.getIconResource(); + if (iconId != 0) { + return getFullResIcon(resources, iconId); + } + } + return getFullResDefaultActivityIcon(); + } + private Bitmap makeDefaultIcon() { - Drawable d = mPackageManager.getDefaultActivityIcon(); + Drawable d = getFullResDefaultActivityIcon(); Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1), Math.max(d.getIntrinsicHeight(), 1), Bitmap.Config.ARGB_8888); @@ -140,7 +177,7 @@ public class IconCache { entry.title = info.activityInfo.name; } entry.icon = Utilities.createIconBitmap( - info.activityInfo.loadIcon(mPackageManager), mContext); + getFullResIcon(info, mPackageManager), mContext); } return entry; } diff --git a/src/com/android/launcher2/InstallShortcutReceiver.java b/src/com/android/launcher2/InstallShortcutReceiver.java index 3fc568b..ac50f66 100644 --- a/src/com/android/launcher2/InstallShortcutReceiver.java +++ b/src/com/android/launcher2/InstallShortcutReceiver.java @@ -16,19 +16,23 @@ package com.android.launcher2; +import java.util.ArrayList; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.ContentResolver; -import android.database.Cursor; import android.widget.Toast; import com.android.launcher.R; public class InstallShortcutReceiver extends BroadcastReceiver { - private static final String ACTION_INSTALL_SHORTCUT = + public static final String ACTION_INSTALL_SHORTCUT = "com.android.launcher.action.INSTALL_SHORTCUT"; + // A mime-type representing shortcut data + public static final String SHORTCUT_MIMETYPE = + "com.android.launcher/shortcut"; + private final int[] mCoordinates = new int[2]; public void onReceive(Context context, Intent data) { @@ -50,11 +54,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver { String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); if (findEmptyCell(context, mCoordinates, screen)) { - CellLayout.CellInfo cell = new CellLayout.CellInfo(); - cell.cellX = mCoordinates[0]; - cell.cellY = mCoordinates[1]; - cell.screen = screen; - Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); if (intent.getAction() == null) { @@ -66,7 +65,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true); if (duplicate || !LauncherModel.shortcutExists(context, name, intent)) { ((LauncherApplication)context.getApplicationContext()).getModel() - .addShortcut(context, data, cell, true); + .addShortcut(context, data, screen, mCoordinates[0], mCoordinates[1], true); Toast.makeText(context, context.getString(R.string.shortcut_installed, name), Toast.LENGTH_SHORT).show(); } else { @@ -84,40 +83,26 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } private static boolean findEmptyCell(Context context, int[] xy, int screen) { - final int xCount = Launcher.NUMBER_CELLS_X; - final int yCount = Launcher.NUMBER_CELLS_Y; - + final int xCount = LauncherModel.getCellCountX(); + final int yCount = LauncherModel.getCellCountY(); boolean[][] occupied = new boolean[xCount][yCount]; - final ContentResolver cr = context.getContentResolver(); - Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, - new String[] { LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY, - LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, - LauncherSettings.Favorites.SCREEN + "=?", - new String[] { String.valueOf(screen) }, null); - - final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); - final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); - final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX); - final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY); - - try { - while (c.moveToNext()) { - int cellX = c.getInt(cellXIndex); - int cellY = c.getInt(cellYIndex); - int spanX = c.getInt(spanXIndex); - int spanY = c.getInt(spanYIndex); - + ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context); + ItemInfo item = null; + int cellX, cellY, spanX, spanY; + for (int i = 0; i < items.size(); ++i) { + item = items.get(i); + if (item.screen == screen) { + cellX = item.cellX; + cellY = item.cellY; + spanX = item.spanX; + spanY = item.spanY; for (int x = cellX; x < cellX + spanX && x < xCount; x++) { for (int y = cellY; y < cellY + spanY && y < yCount; y++) { occupied[x][y] = true; } } } - } catch (Exception e) { - return false; - } finally { - c.close(); } return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied); diff --git a/src/com/android/launcher2/InstallWidgetReceiver.java b/src/com/android/launcher2/InstallWidgetReceiver.java new file mode 100644 index 0000000..970c6a5 --- /dev/null +++ b/src/com/android/launcher2/InstallWidgetReceiver.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import java.util.List; + +import android.appwidget.AppWidgetProviderInfo; +import android.content.ClipData; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import com.android.launcher.R; + + +/** + * We will likely flesh this out later, to handle allow external apps to place widgets, but for now, + * we just want to expose the action around for checking elsewhere. + */ +public class InstallWidgetReceiver { + public static final String ACTION_INSTALL_WIDGET = + "com.android.launcher.action.INSTALL_WIDGET"; + public static final String ACTION_SUPPORTS_CLIPDATA_MIMETYPE = + "com.android.launcher.action.SUPPORTS_CLIPDATA_MIMETYPE"; + + // Currently not exposed. Put into Intent when we want to make it public. + // TEMP: Should we call this "EXTRA_APPWIDGET_PROVIDER"? + public static final String EXTRA_APPWIDGET_COMPONENT = + "com.android.launcher.extra.widget.COMPONENT"; + public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE = + "com.android.launcher.extra.widget.CONFIGURATION_DATA_MIME_TYPE"; + public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA = + "com.android.launcher.extra.widget.CONFIGURATION_DATA"; + + /** + * A simple data class that contains per-item information that the adapter below can reference. + */ + public static class WidgetMimeTypeHandlerData { + public ResolveInfo resolveInfo; + public AppWidgetProviderInfo widgetInfo; + + public WidgetMimeTypeHandlerData(ResolveInfo rInfo, AppWidgetProviderInfo wInfo) { + resolveInfo = rInfo; + widgetInfo = wInfo; + } + } + + /** + * The ListAdapter which presents all the valid widgets that can be created for a given drop. + */ + public static class WidgetListAdapter implements ListAdapter, DialogInterface.OnClickListener { + private LayoutInflater mInflater; + private Launcher mLauncher; + private String mMimeType; + private ClipData mClipData; + private List<WidgetMimeTypeHandlerData> mActivities; + private CellLayout mTargetLayout; + private int mTargetLayoutScreen; + private int[] mTargetLayoutPos; + + public WidgetListAdapter(Launcher l, String mimeType, ClipData data, + List<WidgetMimeTypeHandlerData> list, CellLayout target, + int targetScreen, int[] targetPos) { + mLauncher = l; + mMimeType = mimeType; + mClipData = data; + mActivities = list; + mTargetLayout = target; + mTargetLayoutScreen = targetScreen; + mTargetLayoutPos = targetPos; + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + } + + @Override + public int getCount() { + return mActivities.size(); + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final Context context = parent.getContext(); + final PackageManager packageManager = context.getPackageManager(); + + // Lazy-create inflater + if (mInflater == null) { + mInflater = LayoutInflater.from(context); + } + + // Use the convert-view where possible + if (convertView == null) { + convertView = mInflater.inflate(R.layout.external_widget_drop_list_item, parent, + false); + } + + final WidgetMimeTypeHandlerData data = mActivities.get(position); + final ResolveInfo resolveInfo = data.resolveInfo; + final AppWidgetProviderInfo widgetInfo = data.widgetInfo; + + // Set the icon + Drawable d = resolveInfo.loadIcon(packageManager); + ImageView i = (ImageView) convertView.findViewById(R.id.provider_icon); + i.setImageDrawable(d); + + // Set the text + final CharSequence component = resolveInfo.loadLabel(packageManager); + final int[] widgetSpan = new int[2]; + mTargetLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, widgetSpan); + TextView t = (TextView) convertView.findViewById(R.id.provider); + t.setText(context.getString(R.string.external_drop_widget_pick_format, + component, widgetSpan[0], widgetSpan[1])); + + return convertView; + } + + @Override + public int getItemViewType(int position) { + return 0; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public boolean isEmpty() { + return mActivities.isEmpty(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return true; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final LauncherModel model = mLauncher.getModel(); + final AppWidgetProviderInfo widgetInfo = mActivities.get(which).widgetInfo; + + final PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo(widgetInfo, mMimeType, + mClipData); + mLauncher.addAppWidgetFromDrop(createInfo, mTargetLayoutScreen, mTargetLayoutPos); + } + } +} diff --git a/src/com/android/launcher2/InterruptibleInOutAnimator.java b/src/com/android/launcher2/InterruptibleInOutAnimator.java new file mode 100644 index 0000000..570b9e7 --- /dev/null +++ b/src/com/android/launcher2/InterruptibleInOutAnimator.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.util.Log; + +/** + * A convenience class for two-way animations, e.g. a fadeIn/fadeOut animation. + * With a regular ValueAnimator, if you call reverse to show the 'out' animation, you'll get + * a frame-by-frame mirror of the 'in' animation -- i.e., the interpolated values will + * be exactly reversed. Using this class, both the 'in' and the 'out' animation use the + * interpolator in the same direction. + */ +public class InterruptibleInOutAnimator { + private long mOriginalDuration; + private float mOriginalFromValue; + private float mOriginalToValue; + private ValueAnimator mAnimator; + + private boolean mFirstRun = true; + + private Object mTag = null; + + private static final int STOPPED = 0; + private static final int IN = 1; + private static final int OUT = 2; + + // TODO: This isn't really necessary, but is here to help diagnose a bug in the drag viz + private int mDirection = STOPPED; + + public InterruptibleInOutAnimator(long duration, float fromValue, float toValue) { + mAnimator = ValueAnimator.ofFloat(fromValue, toValue).setDuration(duration); + mOriginalDuration = duration; + mOriginalFromValue = fromValue; + mOriginalToValue = toValue; + + mAnimator.addListener(new LauncherAnimatorListenerAdapter() { + @Override + public void onAnimationEndOrCancel(Animator animation) { + mDirection = STOPPED; + } + }); + } + + private void animate(int direction) { + final long currentPlayTime = mAnimator.getCurrentPlayTime(); + final float toValue = (direction == IN) ? mOriginalToValue : mOriginalFromValue; + final float startValue = mFirstRun ? mOriginalFromValue : + ((Float) mAnimator.getAnimatedValue()).floatValue(); + + // Make sure it's stopped before we modify any values + cancel(); + + // TODO: We don't really need to do the animation if startValue == toValue, but + // somehow that doesn't seem to work, possibly a quirk of the animation framework + mDirection = direction; + + // Ensure we don't calculate a non-sensical duration + long duration = mOriginalDuration - currentPlayTime; + mAnimator.setDuration(Math.max(0, Math.min(duration, mOriginalDuration))); + + mAnimator.setFloatValues(startValue, toValue); + mAnimator.start(); + mFirstRun = false; + } + + public void cancel() { + mAnimator.cancel(); + mDirection = STOPPED; + } + + public void end() { + mAnimator.end(); + mDirection = STOPPED; + } + + /** + * Return true when the animation is not running and it hasn't even been started. + */ + public boolean isStopped() { + return mDirection == STOPPED; + } + + /** + * This is the equivalent of calling Animator.start(), except that it can be called when + * the animation is running in the opposite direction, in which case we reverse + * direction and animate for a correspondingly shorter duration. + */ + public void animateIn() { + animate(IN); + } + + /** + * This is the roughly the equivalent of calling Animator.reverse(), except that it uses the + * same interpolation curve as animateIn(), rather than mirroring it. Also, like animateIn(), + * if the animation is currently running in the opposite direction, we reverse + * direction and animate for a correspondingly shorter duration. + */ + public void animateOut() { + animate(OUT); + } + + public void setTag(Object tag) { + mTag = tag; + } + + public Object getTag() { + return mTag; + } + + public ValueAnimator getAnimator() { + return mAnimator; + } +} diff --git a/src/com/android/launcher2/ItemInfo.java b/src/com/android/launcher2/ItemInfo.java index a96d9ae..dc45750 100644 --- a/src/com/android/launcher2/ItemInfo.java +++ b/src/com/android/launcher2/ItemInfo.java @@ -112,6 +112,11 @@ class ItemInfo { } } + void updateValuesWithCoordinates(ContentValues values, int cellX, int cellY) { + values.put(LauncherSettings.Favorites.CELLX, cellX); + values.put(LauncherSettings.Favorites.CELLY, cellY); + } + static byte[] flattenBitmap(Bitmap bitmap) { // Try go guesstimate how much space the icon will take when serialized // to avoid unnecessary allocations/copies during the write. diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java index b361ab5..549303d 100644 --- a/src/com/android/launcher2/Launcher.java +++ b/src/com/android/launcher2/Launcher.java @@ -1,3 +1,4 @@ + /* * Copyright (C) 2008 The Android Open Source Project * @@ -16,16 +17,31 @@ package com.android.launcher2; -import com.android.common.Search; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.SearchManager; import android.app.StatusBarManager; import android.app.WallpaperManager; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; +import android.content.ClipData; +import android.content.ClipDescription; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -35,16 +51,17 @@ import android.content.Intent.ShortcutIconResource; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.Rect; import android.graphics.Canvas; -import android.graphics.drawable.Drawable; +import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -54,6 +71,7 @@ import android.os.Parcelable; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.LiveFolders; +import android.provider.Settings; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -65,34 +83,33 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.View.OnLongClickListener; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; import android.widget.ImageView; -import android.widget.PopupWindow; import android.widget.LinearLayout; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; - -import java.util.ArrayList; -import java.util.List; -import java.util.HashMap; -import java.io.DataOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.DataInputStream; +import android.widget.PopupWindow; +import android.widget.TabHost; +import android.widget.TabHost.OnTabChangeListener; +import android.widget.TabHost.TabContentFactory; +import android.widget.TextView; +import android.widget.Toast; +import com.android.common.Search; import com.android.launcher.R; /** * Default launcher application. */ public final class Launcher extends Activity - implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher { + implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, + AllAppsView.Watcher, View.OnTouchListener { static final String TAG = "Launcher"; static final boolean LOGD = false; @@ -100,8 +117,6 @@ public final class Launcher extends Activity static final boolean DEBUG_WIDGETS = false; static final boolean DEBUG_USER_INTERFACE = false; - private static final int WALLPAPER_SCREENS_SPAN = 2; - private static final int MENU_GROUP_ADD = 1; private static final int MENU_GROUP_WALLPAPER = MENU_GROUP_ADD + 1; @@ -125,8 +140,6 @@ public final class Launcher extends Activity static final int SCREEN_COUNT = 5; static final int DEFAULT_SCREEN = 2; - static final int NUMBER_CELLS_X = 4; - static final int NUMBER_CELLS_Y = 4; static final int DIALOG_CREATE_SHORTCUT = 1; static final int DIALOG_RENAME_FOLDER = 2; @@ -135,8 +148,8 @@ public final class Launcher extends Activity // Type: int private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; - // Type: boolean - private static final String RUNTIME_STATE_ALL_APPS_FOLDER = "launcher.all_apps_folder"; + // Type: int + private static final String RUNTIME_STATE = "launcher.state"; // Type: long private static final String RUNTIME_STATE_USER_FOLDERS = "launcher.user_folder"; // Type: int @@ -145,21 +158,24 @@ public final class Launcher extends Activity private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cellX"; // Type: int private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cellY"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_spanX"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_spanY"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_COUNT_X = "launcher.add_countX"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_COUNT_Y = "launcher.add_countY"; - // Type: int[] - private static final String RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS = "launcher.add_occupied_cells"; // Type: boolean 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"; + // tags for the customization tabs + private static final String WIDGETS_TAG = "widgets"; + private static final String APPLICATIONS_TAG = "applications"; + private static final String SHORTCUTS_TAG = "shortcuts"; + private static final String WALLPAPERS_TAG = "wallpapers"; + + private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon"; + + /** The different states that Launcher can be in. */ + private enum State { WORKSPACE, ALL_APPS, CUSTOMIZE, OVERVIEW }; + private State mState = State.WORKSPACE; + private AnimatorSet mStateAnimation; + static final int APPWIDGET_HOST_ID = 1024; private static final Object sLock = new Object(); @@ -177,14 +193,21 @@ public final class Launcher extends Activity private AppWidgetManager mAppWidgetManager; private LauncherAppWidgetHost mAppWidgetHost; - private CellLayout.CellInfo mAddItemCellInfo; - private CellLayout.CellInfo mMenuAddInfo; - private final int[] mCellCoordinates = new int[2]; + private int mAddScreen = -1; + private int mAddIntersectCellX = -1; + private int mAddIntersectCellY = -1; + private int[] mAddDropPosition; + private int[] mTmpAddItemCellCoordinates = new int[2]; + private FolderInfo mFolderInfo; private DeleteZone mDeleteZone; private HandleView mHandleView; private AllAppsView mAllAppsGrid; + private TabHost mHomeCustomizationDrawer; + + private PagedView mAllAppsPagedView = null; + private CustomizePagedView mCustomizePagedView = null; private Bundle mSavedState; @@ -211,16 +234,23 @@ public final class Launcher extends Activity private ImageView mNextView; // Hotseats (quick-launch icons next to AllApps) - private static final int NUM_HOTSEATS = 2; private String[] mHotseatConfig = null; private Intent[] mHotseats = null; private Drawable[] mHotseatIcons = null; private CharSequence[] mHotseatLabels = null; + private Intent mAppMarketIntent = null; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (LauncherApplication.isInPlaceRotationEnabled()) { + // hide the status bar (temporary until we get the status bar design figured out) + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); + } + LauncherApplication app = ((LauncherApplication)getApplication()); mModel = app.setLauncher(this); mIconCache = app.getIconCache(); @@ -239,8 +269,70 @@ public final class Launcher extends Activity loadHotseats(); checkForLocaleChange(); setWallpaperDimension(); - setContentView(R.layout.launcher); + mHomeCustomizationDrawer = (TabHost) findViewById(com.android.internal.R.id.tabhost); + if (mHomeCustomizationDrawer != null) { + mHomeCustomizationDrawer.setup(); + + // share the same customization workspace across all the tabs + mCustomizePagedView = (CustomizePagedView) mInflater.inflate( + R.layout.customization_drawer, mHomeCustomizationDrawer, false); + TabContentFactory contentFactory = new TabContentFactory() { + public View createTabContent(String tag) { + return mCustomizePagedView; + } + }; + + String widgetsLabel = getString(R.string.widgets_tab_label); + mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WIDGETS_TAG) + .setIndicator(widgetsLabel).setContent(contentFactory)); + String applicationsLabel = getString(R.string.applications_tab_label); + mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(APPLICATIONS_TAG) + .setIndicator(applicationsLabel).setContent(contentFactory)); + String wallpapersLabel = getString(R.string.wallpapers_tab_label); + mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WALLPAPERS_TAG) + .setIndicator(wallpapersLabel).setContent(contentFactory)); + String shortcutsLabel = getString(R.string.shortcuts_tab_label); + mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(SHORTCUTS_TAG) + .setIndicator(shortcutsLabel).setContent(contentFactory)); + + mHomeCustomizationDrawer.setOnTabChangedListener(new OnTabChangeListener() { + public void onTabChanged(String tabId) { + // animate the changing of the tab content by fading pages in and out + final int duration = 150; + final float alpha = mCustomizePagedView.getAlpha(); + ValueAnimator alphaAnim = ObjectAnimator.ofFloat(mCustomizePagedView, + "alpha", alpha, 0.0f); + alphaAnim.setDuration(duration); + alphaAnim.addListener(new LauncherAnimatorListenerAdapter() { + @Override + public void onAnimationEndOrCancel(Animator animation) { + String tag = mHomeCustomizationDrawer.getCurrentTabTag(); + if (tag == WIDGETS_TAG) { + mCustomizePagedView.setCustomizationFilter( + CustomizePagedView.CustomizationType.WidgetCustomization); + } else if (tag == APPLICATIONS_TAG) { + mCustomizePagedView.setCustomizationFilter( + CustomizePagedView.CustomizationType.ApplicationCustomization); + } else if (tag == WALLPAPERS_TAG) { + mCustomizePagedView.setCustomizationFilter( + CustomizePagedView.CustomizationType.WallpaperCustomization); + } else if (tag == SHORTCUTS_TAG) { + mCustomizePagedView.setCustomizationFilter( + CustomizePagedView.CustomizationType.ShortcutCustomization); + } + + final float alpha = mCustomizePagedView.getAlpha(); + ValueAnimator alphaAnim = ObjectAnimator.ofFloat( + mCustomizePagedView, "alpha", alpha, 1.0f); + alphaAnim.setDuration(duration); + alphaAnim.start(); + } + }); + alphaAnim.start(); + } + }); + } setupViews(); registerContentObservers(); @@ -308,6 +400,7 @@ public final class Launcher extends Activity final LocaleConfiguration localeConfiguration = sLocaleConfiguration; new Thread("WriteLocaleConfiguration") { + @Override public void run() { writeConfiguration(Launcher.this, localeConfiguration); } @@ -383,11 +476,12 @@ public final class Launcher extends Activity WallpaperManager wpm = (WallpaperManager)getSystemService(WALLPAPER_SERVICE); Display display = getWindowManager().getDefaultDisplay(); - boolean isPortrait = display.getWidth() < display.getHeight(); - - final int width = isPortrait ? display.getWidth() : display.getHeight(); - final int height = isPortrait ? display.getHeight() : display.getWidth(); - wpm.suggestDesiredDimensions(width * WALLPAPER_SCREENS_SPAN, height); + // TODO: Put back when we decide about scrolling the wallpaper + // boolean isPortrait = display.getWidth() < display.getHeight(); + // final int width = isPortrait ? display.getWidth() : display.getHeight(); + // final int height = isPortrait ? display.getHeight() : display.getWidth(); + wpm.suggestDesiredDimensions(Math.max(display.getWidth(), display.getHeight()), + Math.max(display.getWidth(), display.getHeight())); } // Note: This doesn't do all the client-id magic that BrowserProvider does @@ -446,7 +540,7 @@ public final class Launcher extends Activity // note: if the user launches this without a default set, she // will always be taken to the default URL above; this is // unavoidable as we must specify a valid URL in order for the - // chooser to appear, and once the user selects something, that + // chooser to appear, and once the user selects something, that // URL is unavoidably sent to the chosen app. } else { try { @@ -456,7 +550,7 @@ public final class Launcher extends Activity // bogus; leave intent=null } } - + if (intent == null) { mHotseats[i] = null; mHotseatLabels[i] = getText(R.string.activity_not_found); @@ -464,15 +558,15 @@ public final class Launcher extends Activity } if (LOGD) { - Log.d(TAG, "loadHotseats: hotseat " + i - + " initial intent=[" + Log.d(TAG, "loadHotseats: hotseat " + i + + " initial intent=[" + intent.toUri(Intent.URI_INTENT_SCHEME) + "]"); } ResolveInfo bestMatch = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); List<ResolveInfo> allMatches = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - if (LOGD) { + if (LOGD) { Log.d(TAG, "Best match for intent: " + bestMatch); Log.d(TAG, "All matches: "); for (ResolveInfo ri : allMatches) { @@ -481,8 +575,8 @@ public final class Launcher extends Activity } // did this resolve to a single app, or the resolver? if (allMatches.size() == 0 || bestMatch == null) { - // can't find any activity to handle this. let's leave the - // intent as-is and let Launcher show a toast when it fails + // can't find any activity to handle this. let's leave the + // intent as-is and let Launcher show a toast when it fails // to launch. mHotseats[i] = intent; @@ -498,7 +592,7 @@ public final class Launcher extends Activity break; } } - + if (!found) { if (LOGD) Log.d(TAG, "Multiple options, no default yet"); // the bestMatch is probably the ResolveActivity, meaning the @@ -523,8 +617,8 @@ public final class Launcher extends Activity } if (LOGD) { - Log.d(TAG, "loadHotseats: hotseat " + i - + " final intent=[" + Log.d(TAG, "loadHotseats: hotseat " + i + + " final intent=[" + ((mHotseats[i] == null) ? "null" : mHotseats[i].toUri(Intent.URI_INTENT_SCHEME)) @@ -545,28 +639,30 @@ public final class Launcher extends Activity // 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 && mAddScreen != -1) { switch (requestCode) { case REQUEST_PICK_APPLICATION: - completeAddApplication(this, data, mAddItemCellInfo); + completeAddApplication( + this, data, mAddScreen, mAddIntersectCellX, mAddIntersectCellY); break; case REQUEST_PICK_SHORTCUT: processShortcut(data); break; case REQUEST_CREATE_SHORTCUT: - completeAddShortcut(data, mAddItemCellInfo); + completeAddShortcut(data, mAddScreen, mAddIntersectCellX, mAddIntersectCellY); break; case REQUEST_PICK_LIVE_FOLDER: addLiveFolder(data); break; case REQUEST_CREATE_LIVE_FOLDER: - completeAddLiveFolder(data, mAddItemCellInfo); + completeAddLiveFolder(data, mAddScreen, mAddIntersectCellX, mAddIntersectCellY); break; case REQUEST_PICK_APPWIDGET: - addAppWidget(data); + addAppWidgetFromPick(data); break; case REQUEST_CREATE_APPWIDGET: - completeAddAppWidget(data, mAddItemCellInfo); + int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + completeAddAppWidget(appWidgetId, mAddScreen); break; case REQUEST_PICK_WALLPAPER: // We just wanted the activity result here so we can clear mWaitingForResult @@ -593,14 +689,22 @@ public final class Launcher extends Activity mRestoring = false; mOnResumeNeedsLoad = false; } + // When we resume Launcher, a different Activity might be responsible for the app + // market intent, so refresh the icon + updateAppMarketIcon(); } @Override protected void onPause() { super.onPause(); + // Some launcher layouts don't have a previous and next view + if (mPreviousView != null) { + dismissPreview(mPreviousView); + } + if (mNextView != null) { + dismissPreview(mNextView); + } mPaused = true; - dismissPreview(mPreviousView); - dismissPreview(mNextView); mDragController.cancelDrag(); } @@ -675,6 +779,22 @@ public final class Launcher extends Activity } /** + * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type + * State + */ + private static State intToState(int stateOrdinal) { + State state = State.WORKSPACE; + final State[] stateValues = State.values(); + for (int i = 0; i < stateValues.length; i++) { + if (stateValues[i].ordinal() == stateOrdinal) { + state = stateValues[i]; + break; + } + } + return state; + } + + /** * Restores the previous state, if it exists. * * @param savedState The previous state. @@ -684,30 +804,25 @@ public final class Launcher extends Activity return; } - final boolean allApps = savedState.getBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, false); - if (allApps) { + State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal())); + + if (state == State.ALL_APPS) { showAllApps(false); + } else if (state == State.CUSTOMIZE) { + showCustomizationDrawer(false); } final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1); if (currentScreen > -1) { - mWorkspace.setCurrentScreen(currentScreen); + mWorkspace.setCurrentPage(currentScreen); } final int addScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1); + if (addScreen > -1) { - mAddItemCellInfo = new CellLayout.CellInfo(); - final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo; - addItemCellInfo.valid = true; - addItemCellInfo.screen = addScreen; - addItemCellInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X); - addItemCellInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y); - addItemCellInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X); - addItemCellInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y); - addItemCellInfo.findVacantCellsFromOccupied( - savedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS), - savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_X), - savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y)); + mAddScreen = addScreen; + mAddIntersectCellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X); + mAddIntersectCellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y); mRestoring = true; } @@ -723,7 +838,7 @@ public final class Launcher extends Activity * Finds all the views we need and configure them properly. */ private void setupViews() { - DragController dragController = mDragController; + final DragController dragController = mDragController; DragLayer dragLayer = (DragLayer) findViewById(R.id.drag_layer); dragLayer.setDragController(dragController); @@ -732,8 +847,15 @@ public final class Launcher extends Activity mAllAppsGrid.setLauncher(this); mAllAppsGrid.setDragController(dragController); ((View) mAllAppsGrid).setWillNotDraw(false); // We don't want a hole punched in our window. - // Manage focusability manually since this thing is always visible - ((View) mAllAppsGrid).setFocusable(false); + // Manage focusability manually since this thing is always visible (in non-xlarge) + ((View) mAllAppsGrid).setFocusable(false); + + if (LauncherApplication.isScreenXLarge()) { + // They need to be INVISIBLE initially so that they will be measured in the layout. + // Otherwise the animations are messed up when we show them for the first time. + ((View) mAllAppsGrid).setVisibility(View.INVISIBLE); + mHomeCustomizationDrawer.setVisibility(View.INVISIBLE); + } mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace); final Workspace workspace = mWorkspace; @@ -742,29 +864,39 @@ public final class Launcher extends Activity DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone); mDeleteZone = deleteZone; - mHandleView = (HandleView) findViewById(R.id.all_apps_button); - mHandleView.setLauncher(this); - mHandleView.setOnClickListener(this); - mHandleView.setOnLongClickListener(this); + View handleView = findViewById(R.id.all_apps_button); + if (handleView != null && handleView instanceof HandleView) { + // we don't use handle view in xlarge mode + mHandleView = (HandleView)handleView; + mHandleView.setLauncher(this); + mHandleView.setOnClickListener(this); + mHandleView.setOnLongClickListener(this); + } - ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left); - hotseatLeft.setContentDescription(mHotseatLabels[0]); - hotseatLeft.setImageDrawable(mHotseatIcons[0]); - ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right); - hotseatRight.setContentDescription(mHotseatLabels[1]); - hotseatRight.setImageDrawable(mHotseatIcons[1]); + if (mCustomizePagedView != null) { + mCustomizePagedView.setLauncher(this); + mCustomizePagedView.setDragController(dragController); + mCustomizePagedView.update(); + } else { + ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left); + hotseatLeft.setContentDescription(mHotseatLabels[0]); + hotseatLeft.setImageDrawable(mHotseatIcons[0]); + ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right); + hotseatRight.setContentDescription(mHotseatLabels[1]); + hotseatRight.setImageDrawable(mHotseatIcons[1]); - mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen); - mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen); + mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen); + mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen); - Drawable previous = mPreviousView.getDrawable(); - Drawable next = mNextView.getDrawable(); - mWorkspace.setIndicators(previous, next); + Drawable previous = mPreviousView.getDrawable(); + Drawable next = mNextView.getDrawable(); + mWorkspace.setIndicators(previous, next); - mPreviousView.setHapticFeedbackEnabled(false); - mPreviousView.setOnLongClickListener(this); - mNextView.setHapticFeedbackEnabled(false); - mNextView.setOnLongClickListener(this); + mPreviousView.setHapticFeedbackEnabled(false); + mPreviousView.setOnLongClickListener(this); + mNextView.setHapticFeedbackEnabled(false); + mNextView.setOnLongClickListener(this); + } workspace.setOnLongClickListener(this); workspace.setDragController(dragController); @@ -772,35 +904,52 @@ public final class Launcher extends Activity deleteZone.setLauncher(this); deleteZone.setDragController(dragController); - deleteZone.setHandle(findViewById(R.id.all_apps_button_cluster)); + int deleteZoneHandleId; + if (LauncherApplication.isScreenXLarge()) { + deleteZoneHandleId = R.id.all_apps_button; + } else { + deleteZoneHandleId = R.id.all_apps_button_cluster; + } + deleteZone.setHandle(findViewById(deleteZoneHandleId)); + dragController.addDragListener(deleteZone); + + ApplicationInfoDropTarget infoButton = (ApplicationInfoDropTarget)findViewById(R.id.info_button); + if (infoButton != null) { + infoButton.setLauncher(this); + infoButton.setHandle(findViewById(R.id.configure_button)); + infoButton.setDragColor(getResources().getColor(R.color.app_info_filter)); + dragController.addDragListener(infoButton); + } dragController.setDragScoller(workspace); - dragController.setDragListener(deleteZone); dragController.setScrollView(dragLayer); dragController.setMoveTarget(workspace); // The order here is bottom to top. dragController.addDropTarget(workspace); dragController.addDropTarget(deleteZone); + if (infoButton != null) { + dragController.addDropTarget(infoButton); + } } @SuppressWarnings({"UnusedDeclaration"}) public void previousScreen(View v) { - if (!isAllAppsVisible()) { + if (mState != State.ALL_APPS) { mWorkspace.scrollLeft(); } } @SuppressWarnings({"UnusedDeclaration"}) public void nextScreen(View v) { - if (!isAllAppsVisible()) { + if (mState != State.ALL_APPS) { mWorkspace.scrollRight(); } } @SuppressWarnings({"UnusedDeclaration"}) public void launchHotSeat(View v) { - if (isAllAppsVisible()) return; + if (mState == State.ALL_APPS) return; int index = -1; if (v.getId() == R.id.hotseat_left) { @@ -819,7 +968,7 @@ public final class Launcher extends Activity ); } } - + /** * Creates a view representing a shortcut. * @@ -829,7 +978,7 @@ public final class Launcher extends Activity */ View createShortcut(ShortcutInfo info) { return createShortcut(R.layout.application, - (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info); + (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); } /** @@ -844,8 +993,10 @@ public final class Launcher extends Activity View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) { TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false); + Bitmap b = info.getIcon(mIconCache); + favorite.setCompoundDrawablesWithIntrinsicBounds(null, - new FastBitmapDrawable(info.getIcon(mIconCache)), + new FastBitmapDrawable(b), null, null); favorite.setText(info.title); favorite.setTag(info); @@ -860,9 +1011,15 @@ public final class Launcher extends Activity * @param data The intent describing the application. * @param cellInfo The position on screen where to create the shortcut. */ - void completeAddApplication(Context context, Intent data, CellLayout.CellInfo cellInfo) { - cellInfo.screen = mWorkspace.getCurrentScreen(); - if (!findSingleSlot(cellInfo)) return; + void completeAddApplication(Context context, Intent data, int screen, + int intersectCellX, int intersectCellY) { + final int[] cellXY = mTmpAddItemCellCoordinates; + final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); + + if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) { + showOutOfSpaceMessage(); + return; + } final ShortcutInfo info = mModel.getShortcutInfo(context.getPackageManager(), data, context); @@ -871,7 +1028,8 @@ public final class Launcher extends Activity info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); info.container = ItemInfo.NO_ID; - mWorkspace.addApplicationShortcut(info, cellInfo, isWorkspaceLocked()); + mWorkspace.addApplicationShortcut(info, screen, cellXY[0], cellXY[1], + isWorkspaceLocked(), mAddIntersectCellX, mAddIntersectCellY); } else { Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data); } @@ -883,16 +1041,22 @@ public final class Launcher extends Activity * @param data The intent describing the shortcut. * @param cellInfo The position on screen where to create the shortcut. */ - private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo) { - cellInfo.screen = mWorkspace.getCurrentScreen(); - if (!findSingleSlot(cellInfo)) return; + private void completeAddShortcut(Intent data, int screen, + int intersectCellX, int intersectCellY) { + final int[] cellXY = mTmpAddItemCellCoordinates; + final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); - final ShortcutInfo info = mModel.addShortcut(this, data, cellInfo, false); + if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) { + showOutOfSpaceMessage(); + return; + } + + final ShortcutInfo info = mModel.addShortcut( + this, data, screen, cellXY[0], cellXY[1], false); if (!mRestoring) { final View view = createShortcut(info); - mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, - isWorkspaceLocked()); + mWorkspace.addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked()); } } @@ -900,36 +1064,63 @@ public final class Launcher extends Activity /** * Add a widget to the workspace. * - * @param data The intent describing the appWidgetId. + * @param appWidgetId The app widget id * @param cellInfo The position on screen where to create the widget. */ - private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo) { - Bundle extras = data.getExtras(); - int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); - - if (LOGD) Log.d(TAG, "dumping extras content=" + extras.toString()); - + private void completeAddAppWidget(int appWidgetId, int screen) { 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); + CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); + int[] spanXY = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight, null); // Try finding open space on Launcher screen - final int[] xy = mCellCoordinates; - if (!findSlot(cellInfo, xy, spans[0], spans[1])) { + // We have saved the position to which the widget was dragged-- this really only matters + // if we are placing widgets on a "spring-loaded" screen + final int[] cellXY = mTmpAddItemCellCoordinates; + + // For now, we don't save the coordinate where we dropped the icon because we're not + // supporting spring-loaded mini-screens; however, leaving the ability to directly place + // a widget on the home screen in case we want to add it in the future + int[] touchXY = null; + if (mAddDropPosition[0] > -1 && mAddDropPosition[1] > -1) { + touchXY = mAddDropPosition; + } + boolean findNearestVacantAreaFailed = false; + boolean foundCellSpan = false; + if (touchXY != null) { + // when dragging and dropping, just find the closest free spot + CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen); + int[] result = screenLayout.findNearestVacantArea( + touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY); + findNearestVacantAreaFailed = (result == null); + foundCellSpan = !findNearestVacantAreaFailed; + } else { + if (mAddIntersectCellX != -1 && mAddIntersectCellY != -1) { + // if we long pressed on an empty cell to bring up a menu, + // make sure we intersect the empty cell + foundCellSpan = layout.findCellForSpanThatIntersects(cellXY, spanXY[0], spanXY[1], + mAddIntersectCellX, mAddIntersectCellY); + } else { + // if we went through the menu -> add, just find any spot + foundCellSpan = layout.findCellForSpan(cellXY, spanXY[0], spanXY[1]); + } + } + + if (!foundCellSpan) { if (appWidgetId != -1) mAppWidgetHost.deleteAppWidgetId(appWidgetId); + showOutOfSpaceMessage(); return; } // Build Launcher-specific widget info and save to database LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId); - launcherInfo.spanX = spans[0]; - launcherInfo.spanY = spans[1]; + launcherInfo.spanX = spanXY[0]; + launcherInfo.spanY = spanXY[1]; LauncherModel.addItemToDatabase(this, launcherInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, - mWorkspace.getCurrentScreen(), xy[0], xy[1], false); + screen, cellXY[0], cellXY[1], false); if (!mRestoring) { mDesktopItems.add(launcherInfo); @@ -940,11 +1131,15 @@ public final class Launcher extends Activity launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo); launcherInfo.hostView.setTag(launcherInfo); - mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1], + mWorkspace.addInScreen(launcherInfo.hostView, screen, cellXY[0], cellXY[1], launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked()); } } + void showOutOfSpaceMessage() { + Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show(); + } + public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) { mDesktopItems.remove(launcherInfo); launcherInfo.hostView = null; @@ -954,6 +1149,10 @@ public final class Launcher extends Activity return mAppWidgetHost; } + public LauncherModel getModel() { + return mModel; + } + void closeSystemDialogs() { getWindow().closeAllPanels(); @@ -986,11 +1185,17 @@ public final class Launcher extends Activity boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); - boolean allAppsVisible = isAllAppsVisible(); - if (!mWorkspace.isDefaultScreenShowing()) { - mWorkspace.moveToDefaultScreen(alreadyOnHome && !allAppsVisible); + + // in all these cases, only animate if we're already on home + if (LauncherApplication.isScreenXLarge()) { + mWorkspace.unshrink(alreadyOnHome); } - closeAllApps(alreadyOnHome && allAppsVisible); + if (!mWorkspace.isDefaultPageShowing()) { + // on the phone, we don't animate the change to the workspace if all apps is visible + mWorkspace.moveToDefaultScreen(alreadyOnHome && + (LauncherApplication.isScreenXLarge() || mState != State.ALL_APPS)); + } + showWorkspace(alreadyOnHome); final View v = getWindow().peekDecorView(); if (v != null && v.getWindowToken() != null) { @@ -1005,11 +1210,18 @@ public final class Launcher extends Activity protected void onRestoreInstanceState(Bundle savedInstanceState) { // Do not call super here mSavedInstanceState = savedInstanceState; + + if (mHomeCustomizationDrawer != null) { + String cur = savedInstanceState.getString("currentTab"); + if (cur != null) { + mHomeCustomizationDrawer.setCurrentTabByTag(cur); + } + } } @Override protected void onSaveInstanceState(Bundle outState) { - outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentScreen()); + outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage()); final ArrayList<Folder> folders = mWorkspace.getOpenFolders(); if (folders.size() > 0) { @@ -1024,30 +1236,25 @@ public final class Launcher extends Activity super.onSaveInstanceState(outState); } - // TODO should not do this if the drawer is currently closing. - if (isAllAppsVisible()) { - outState.putBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, true); - } - - if (mAddItemCellInfo != null && mAddItemCellInfo.valid && mWaitingForResult) { - final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo; - final CellLayout layout = (CellLayout) mWorkspace.getChildAt(addItemCellInfo.screen); + outState.putInt(RUNTIME_STATE, mState.ordinal()); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, addItemCellInfo.screen); - outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, addItemCellInfo.cellX); - outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, addItemCellInfo.cellY); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, addItemCellInfo.spanX); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, addItemCellInfo.spanY); - outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_X, layout.getCountX()); - outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y, layout.getCountY()); - outState.putBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS, - layout.getOccupiedCells()); + if (mAddScreen > -1 && mWaitingForResult) { + outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mAddScreen); + outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mAddIntersectCellX); + outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mAddIntersectCellY); } if (mFolderInfo != null && mWaitingForResult) { outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true); outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id); } + + if (mHomeCustomizationDrawer != null) { + String currentTabTag = mHomeCustomizationDrawer.getCurrentTabTag(); + if (currentTabTag != null) { + outState.putString("currentTab", currentTabTag); + } + } } @Override @@ -1067,9 +1274,14 @@ public final class Launcher extends Activity unbindDesktopItems(); getContentResolver().unregisterContentObserver(mWidgetObserver); - - dismissPreview(mPreviousView); - dismissPreview(mNextView); + + // Some launcher layouts don't have a previous and next view + if (mPreviousView != null) { + dismissPreview(mPreviousView); + } + if (mNextView != null) { + dismissPreview(mNextView); + } unregisterReceiver(mCloseSystemDialogsReceiver); } @@ -1084,7 +1296,7 @@ public final class Launcher extends Activity public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch) { - closeAllApps(true); + showWorkspace(true); if (initialQuery == null) { // Use any text typed in the launcher as the initial query @@ -1143,19 +1355,19 @@ public final class Launcher extends Activity // If all apps is animating, don't show the menu, because we don't know // which one to show. - if (mAllAppsGrid.isVisible() && !mAllAppsGrid.isOpaque()) { + if (mAllAppsGrid.isAnimating()) { return false; } // Only show the add and wallpaper options when we're not in all apps. - boolean visible = !mAllAppsGrid.isOpaque(); + boolean visible = !mAllAppsGrid.isVisible(); menu.setGroupVisible(MENU_GROUP_ADD, visible); menu.setGroupVisible(MENU_GROUP_WALLPAPER, visible); // Disable add if the workspace is full. if (visible) { - mMenuAddInfo = mWorkspace.findAllVacantCells(null); - menu.setGroupEnabled(MENU_GROUP_ADD, mMenuAddInfo != null && mMenuAddInfo.valid); + CellLayout layout = (CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage()); + menu.setGroupEnabled(MENU_GROUP_ADD, layout.existsEmptyCell()); } return true; @@ -1200,17 +1412,49 @@ public final class Launcher extends Activity } private void addItems() { - closeAllApps(true); - showAddDialog(mMenuAddInfo); + if (LauncherApplication.isScreenXLarge()) { + // Animate the widget chooser up from the bottom of the screen + if (mState != State.CUSTOMIZE) { + showCustomizationDrawer(true); + } + } else { + showWorkspace(true); + showAddDialog(-1, -1); + } + } + + private void resetAddInfo() { + mAddScreen = -1; + mAddIntersectCellX = -1; + mAddIntersectCellY = -1; + mAddDropPosition = null; + } + + void addAppWidgetFromDrop(PendingAddWidgetInfo info, int screen, int[] position) { + resetAddInfo(); + mAddScreen = screen; + + // only set mAddDropPosition if we dropped on home screen in "spring-loaded" manner + mAddDropPosition = position; + + int appWidgetId = getAppWidgetHost().allocateAppWidgetId(); + AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName); + addAppWidgetImpl(appWidgetId, info); } private void manageApps() { startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS)); } - void addAppWidget(Intent data) { + void addAppWidgetFromPick(Intent data) { // TODO: catch bad widget exception when sent int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + // TODO: Is this log message meaningful? + if (LOGD) Log.d(TAG, "dumping extras content=" + data.getExtras()); + addAppWidgetImpl(appWidgetId, null); + } + + void addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info) { AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId); if (appWidget.configure != null) { @@ -1218,14 +1462,53 @@ public final class Launcher extends Activity Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); intent.setComponent(appWidget.configure); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + if (info != null) { + if (info.mimeType != null && !info.mimeType.isEmpty()) { + intent.putExtra( + InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE, + info.mimeType); + + final String mimeType = info.mimeType; + final ClipData clipData = (ClipData) info.configurationData; + final ClipDescription clipDesc = clipData.getDescription(); + for (int i = 0; i < clipDesc.getMimeTypeCount(); ++i) { + if (clipDesc.getMimeType(i).equals(mimeType)) { + final ClipData.Item item = clipData.getItem(i); + final CharSequence stringData = item.getText(); + final Uri uriData = item.getUri(); + final Intent intentData = item.getIntent(); + final String key = + InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA; + if (uriData != null) { + intent.putExtra(key, uriData); + } else if (intentData != null) { + intent.putExtra(key, intentData); + } else if (stringData != null) { + intent.putExtra(key, stringData); + } + break; + } + } + } + } startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET); } else { // Otherwise just add it - onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data); + completeAddAppWidget(appWidgetId, mAddScreen); } } + void processShortcutFromDrop(ComponentName componentName, int screen, int[] position) { + resetAddInfo(); + mAddScreen = screen; + mAddDropPosition = position; + + Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); + createShortcutIntent.setComponent(componentName); + processShortcut(createShortcutIntent); + } + void processShortcut(Intent intent) { // Handle case where user selected "Applications" String applicationName = getResources().getString(R.string.group_applications); @@ -1244,59 +1527,81 @@ public final class Launcher extends Activity } } - void addLiveFolder(Intent intent) { + void processWallpaper(Intent intent) { + startActivityForResult(intent, REQUEST_PICK_WALLPAPER); + } + + void addLiveFolderFromDrop(ComponentName componentName, int screen, int[] position) { + resetAddInfo(); + mAddScreen = screen; + mAddDropPosition = position; + + Intent createFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER); + createFolderIntent.setComponent(componentName); + + addLiveFolder(createFolderIntent); + } + + void addLiveFolder(Intent intent) { // YYY add screen intersect etc. parameters here // 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(); + addFolder(mAddScreen, mAddIntersectCellX, mAddIntersectCellY); } else { startActivityForResultSafely(intent, REQUEST_CREATE_LIVE_FOLDER); } } - void addFolder() { + void addFolder(int screen, int intersectCellX, int intersectCellY) { UserFolderInfo folderInfo = new UserFolderInfo(); folderInfo.title = getText(R.string.folder_name); - CellLayout.CellInfo cellInfo = mAddItemCellInfo; - cellInfo.screen = mWorkspace.getCurrentScreen(); - if (!findSingleSlot(cellInfo)) return; + final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); + final int[] cellXY = mTmpAddItemCellCoordinates; + if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) { + showOutOfSpaceMessage(); + return; + } // Update the model LauncherModel.addItemToDatabase(this, folderInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, - mWorkspace.getCurrentScreen(), cellInfo.cellX, cellInfo.cellY, false); + screen, cellXY[0], cellXY[1], false); sFolders.put(folderInfo.id, folderInfo); // Create the view FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, - (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), folderInfo); - mWorkspace.addInCurrentScreen(newFolder, - cellInfo.cellX, cellInfo.cellY, 1, 1, isWorkspaceLocked()); + (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), + folderInfo, mIconCache); + mWorkspace.addInScreen(newFolder, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked()); } void removeFolder(FolderInfo folder) { sFolders.remove(folder.id); } - private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo) { - cellInfo.screen = mWorkspace.getCurrentScreen(); - if (!findSingleSlot(cellInfo)) return; + private void completeAddLiveFolder( + Intent data, int screen, int intersectCellX, int intersectCellY) { + final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); + final int[] cellXY = mTmpAddItemCellCoordinates; + if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) { + showOutOfSpaceMessage(); + return; + } - final LiveFolderInfo info = addLiveFolder(this, data, cellInfo, false); + final LiveFolderInfo info = addLiveFolder(this, data, screen, cellXY[0], cellXY[1], false); if (!mRestoring) { final View view = LiveFolderIcon.fromXml(R.layout.live_folder_icon, this, - (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info); - mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, - isWorkspaceLocked()); + (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); + mWorkspace.addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked()); } } static LiveFolderInfo addLiveFolder(Context context, Intent data, - CellLayout.CellInfo cellInfo, boolean notify) { + int screen, int cellX, int cellY, boolean notify) { Intent baseIntent = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT); String name = data.getStringExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME); @@ -1332,35 +1637,12 @@ public final class Launcher extends Activity LiveFolders.DISPLAY_MODE_GRID); LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, - cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify); + screen, cellX, cellY, notify); sFolders.put(info.id, info); return info; } - private boolean findSingleSlot(CellLayout.CellInfo cellInfo) { - final int[] xy = new int[2]; - if (findSlot(cellInfo, xy, 1, 1)) { - cellInfo.cellX = xy[0]; - cellInfo.cellY = xy[1]; - return true; - } - return false; - } - - private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) { - if (!cellInfo.findCellForSpan(xy, spanX, spanY)) { - boolean[] occupied = mSavedState != null ? - mSavedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS) : null; - cellInfo = mWorkspace.findAllVacantCells(occupied); - if (!cellInfo.findCellForSpan(xy, spanX, spanY)) { - Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show(); - return false; - } - } - return true; - } - private void showNotifications() { final StatusBarManager statusBar = (StatusBarManager) getSystemService(STATUS_BAR_SERVICE); if (statusBar != null) { @@ -1369,7 +1651,7 @@ public final class Launcher extends Activity } private void startWallpaper() { - closeAllApps(true); + showWorkspace(true); final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER); Intent chooser = Intent.createChooser(pickWallpaper, getText(R.string.chooser_wallpaper)); @@ -1422,13 +1704,16 @@ public final class Launcher extends Activity @Override public void onBackPressed() { - if (isAllAppsVisible()) { - closeAllApps(true); + if (mState == State.ALL_APPS || mState == State.CUSTOMIZE) { + showWorkspace(true); } else { closeFolder(); } - dismissPreview(mPreviousView); - dismissPreview(mNextView); + // Some launcher layouts don't have a previous and next view + if (mPreviousView != null) { + dismissPreview(mPreviousView); + dismissPreview(mNextView); + } } private void closeFolder() { @@ -1442,7 +1727,8 @@ public final class Launcher extends Activity folder.getInfo().opened = false; ViewGroup parent = (ViewGroup) folder.getParent(); if (parent != null) { - parent.removeView(folder); + CellLayout cl = (CellLayout) parent; + cl.removeViewWithoutMarkingCells(folder); if (folder instanceof DropTarget) { // Live folders aren't DropTargets. mDragController.removeDropTarget((DropTarget)folder); @@ -1486,14 +1772,78 @@ public final class Launcher extends Activity } else if (tag instanceof FolderInfo) { handleFolderClick((FolderInfo) tag); } else if (v == mHandleView) { - if (isAllAppsVisible()) { - closeAllApps(true); + if (mState == State.ALL_APPS) { + showWorkspace(true); } else { showAllApps(true); } } } + public boolean onTouch(View v, MotionEvent event) { + // this is an intercepted event being forwarded from mWorkspace; + // clicking anywhere on the workspace causes the customization drawer to slide down + showWorkspace(true); + return false; + } + + /** + * Event handler for the search button + * + * @param v The view that was clicked. + */ + public void onClickSearchButton(View v) { + startSearch(null, false, null, true); + } + + /** + * Event handler for the "gear" button that appears on the home screen, which + * enters home screen customization mode. + * + * @param v The view that was clicked. + */ + public void onClickConfigureButton(View v) { + addItems(); + } + + /** + * Event handler for the "grid" button that appears on the home screen, which + * enters all apps mode. + * + * @param v The view that was clicked. + */ + public void onClickAllAppsButton(View v) { + showAllApps(true); + } + + public void onClickAppMarketButton(View v) { + if (mAppMarketIntent != null) { + startActivitySafely(mAppMarketIntent, "app market"); + } + } + + void startApplicationDetailsActivity(ComponentName componentName) { + String packageName = componentName.getPackageName(); + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", packageName, null)); + startActivity(intent); + } + + void startApplicationUninstallActivity(ApplicationInfo appInfo) { + if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) { + // System applications cannot be installed. For now, show a toast explaining that. + // We may give them the option of disabling apps this way. + int messageId = R.string.uninstall_system_app_text; + Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show(); + } else { + String packageName = appInfo.componentName.getPackageName(); + String className = appInfo.componentName.getClassName(); + Intent intent = new Intent( + Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className)); + startActivity(intent); + } + } + void startActivitySafely(Intent intent, Object tag) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { @@ -1509,7 +1859,7 @@ public final class Launcher extends Activity + "tag="+ tag + " intent=" + intent, e); } } - + void startActivityForResultSafely(Intent intent, int requestCode) { try { startActivityForResult(intent, requestCode); @@ -1534,10 +1884,10 @@ public final class Launcher extends Activity Folder openFolder = mWorkspace.getFolderForTag(folderInfo); int folderScreen; if (openFolder != null) { - folderScreen = mWorkspace.getScreenForView(openFolder); + folderScreen = mWorkspace.getPageForView(openFolder); // .. and close it closeFolder(openFolder); - if (folderScreen != mWorkspace.getCurrentScreen()) { + if (folderScreen != mWorkspace.getCurrentPage()) { // Close any folder open on the current screen closeFolder(); // Pull the folder onto this screen @@ -1548,13 +1898,13 @@ public final class Launcher extends Activity } /** - * Opens the user fodler described by the specified tag. The opening of the folder + * Opens the user folder described by the specified tag. The opening of the folder * is animated relative to the specified View. If the View is null, no animation * is played. * * @param folderInfo The FolderInfo describing the folder to open. */ - private void openFolder(FolderInfo folderInfo) { + public void openFolder(FolderInfo folderInfo) { Folder openFolder; if (folderInfo instanceof UserFolderInfo) { @@ -1571,28 +1921,29 @@ public final class Launcher extends Activity openFolder.bind(folderInfo); folderInfo.opened = true; - mWorkspace.addInScreen(openFolder, folderInfo.screen, 0, 0, 4, 4); + mWorkspace.addInFullScreen(openFolder, folderInfo.screen); + openFolder.onOpen(); } public boolean onLongClick(View v) { switch (v.getId()) { case R.id.previous_screen: - if (!isAllAppsVisible()) { + if (mState != State.ALL_APPS) { mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); showPreviews(v); } return true; case R.id.next_screen: - if (!isAllAppsVisible()) { + if (mState != State.ALL_APPS) { mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); showPreviews(v); } return true; case R.id.all_apps_button: - if (!isAllAppsVisible()) { + if (mState != State.ALL_APPS) { mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); showPreviews(v); @@ -1608,28 +1959,33 @@ public final class Launcher extends Activity v = (View) v.getParent(); } - CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag(); + resetAddInfo(); + CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag(); // This happens when long clicking an item with the dpad/trackball - if (cellInfo == null) { + if (longClickCellInfo == null || !longClickCellInfo.valid) { return true; } + final View itemUnderLongClick = longClickCellInfo.cell; + if (mWorkspace.allowLongPress()) { - if (cellInfo.cell == null) { - if (cellInfo.valid) { - // User long pressed on empty space - mWorkspace.setAllowLongPress(false); - mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); - showAddDialog(cellInfo); + if (itemUnderLongClick == null) { + // User long pressed on empty space + mWorkspace.setAllowLongPress(false); + mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + if (!LauncherApplication.isScreenXLarge()) { + showAddDialog(longClickCellInfo.cellX, longClickCellInfo.cellY); } } else { - if (!(cellInfo.cell instanceof Folder)) { + if (!(itemUnderLongClick instanceof Folder)) { // User long pressed on an item mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); - mWorkspace.startDrag(cellInfo); + mAddIntersectCellX = longClickCellInfo.cellX; + mAddIntersectCellY = longClickCellInfo.cellY; + mWorkspace.startDrag(longClickCellInfo); } } } @@ -1669,9 +2025,9 @@ public final class Launcher extends Activity final Workspace workspace = mWorkspace; CellLayout cell = ((CellLayout) workspace.getChildAt(start)); - + float max = workspace.getChildCount(); - + final Rect r = new Rect(); resources.getDrawable(R.drawable.preview_background).getPadding(r); int extraW = (int) ((r.left + r.right) * max); @@ -1709,7 +2065,7 @@ public final class Launcher extends Activity final Canvas c = new Canvas(bitmap); c.scale(scale, scale); c.translate(-cell.getLeftPadding(), -cell.getTopPadding()); - cell.dispatchDraw(c); + cell.drawChildren(c); image.setBackgroundDrawable(resources.getDrawable(R.drawable.preview_background)); image.setImageBitmap(bitmap); @@ -1717,12 +2073,12 @@ public final class Launcher extends Activity image.setOnClickListener(handler); image.setOnFocusChangeListener(handler); image.setFocusable(true); - if (i == mWorkspace.getCurrentScreen()) image.requestFocus(); + if (i == mWorkspace.getCurrentPage()) image.requestFocus(); preview.addView(image, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); - bitmaps.add(bitmap); + bitmaps.add(bitmap); } final PopupWindow p = new PopupWindow(this); @@ -1743,7 +2099,7 @@ public final class Launcher extends Activity anchor.setTag(p); anchor.setTag(R.id.workspace, preview); - anchor.setTag(R.id.icon, bitmaps); + anchor.setTag(R.id.icon, bitmaps); } class PreviewTouchHandler implements View.OnClickListener, Runnable, View.OnFocusChangeListener { @@ -1754,17 +2110,17 @@ public final class Launcher extends Activity } public void onClick(View v) { - mWorkspace.snapToScreen((Integer) v.getTag()); + mWorkspace.snapToPage((Integer) v.getTag()); v.post(this); } public void run() { - dismissPreview(mAnchor); + dismissPreview(mAnchor); } public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { - mWorkspace.snapToScreen((Integer) v.getTag()); + mWorkspace.snapToPage((Integer) v.getTag()); } } } @@ -1807,13 +2163,17 @@ public final class Launcher extends Activity showDialog(DIALOG_RENAME_FOLDER); } - private void showAddDialog(CellLayout.CellInfo cellInfo) { - mAddItemCellInfo = cellInfo; + private void showAddDialog(int intersectX, int intersectY) { + resetAddInfo(); + mAddIntersectCellX = intersectX; + mAddIntersectCellY = intersectY; + mAddScreen = mWorkspace.getCurrentPage(); mWaitingForResult = true; showDialog(DIALOG_CREATE_SHORTCUT); } private void pickShortcut() { + // Insert extra item to handle picking application Bundle bundle = new Bundle(); ArrayList<String> shortcutNames = new ArrayList<String>(); @@ -1915,24 +2275,370 @@ public final class Launcher extends Activity // Now a part of LauncherModel.Callbacks. Used to reorder loading steps. public boolean isAllAppsVisible() { - return (mAllAppsGrid != null) ? mAllAppsGrid.isVisible() : false; + return mState == State.ALL_APPS; } // AllAppsView.Watcher public void zoomed(float zoom) { - if (zoom == 1.0f) { + // In XLarge view, we zoom down the workspace below all apps so it's still visible + if (zoom == 1.0f && !LauncherApplication.isScreenXLarge()) { mWorkspace.setVisibility(View.GONE); } } + + private void showToolbarButton(View button) { + button.setAlpha(1.0f); + button.setVisibility(View.VISIBLE); + button.setFocusable(true); + button.setClickable(true); + } + + private void hideToolbarButton(View button) { + button.setAlpha(0.0f); + // We can't set it to GONE, otherwise the RelativeLayout gets screwed up + button.setVisibility(View.INVISIBLE); + button.setFocusable(false); + button.setClickable(false); + } + + /** + * Helper function for showing or hiding a toolbar button, possibly animated. + * + * @param show If true, create an animation to the show the item. Otherwise, hide it. + * @param view The toolbar button to be animated + * @param seq A AnimatorSet that will be used to animate the transition. If null, the + * transition will not be animated. + */ + private void hideOrShowToolbarButton(boolean show, final View view, AnimatorSet seq) { + final boolean showing = show; + final boolean hiding = !show; + + final int duration = show ? + getResources().getInteger(R.integer.config_toolbarButtonFadeInTime) : + getResources().getInteger(R.integer.config_toolbarButtonFadeOutTime); + + if (seq != null) { + Animator anim = ObjectAnimator.ofFloat(view, "alpha", show ? 1.0f : 0.0f); + anim.setDuration(duration); + anim.addListener(new LauncherAnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (showing) showToolbarButton(view); + } + @Override + public void onAnimationEndOrCancel(Animator animation) { + if (hiding) hideToolbarButton(view); + } + }); + seq.play(anim); + } else { + if (showing) { + showToolbarButton(view); + } else { + hideToolbarButton(view); + } + } + } + + /** + * Show/hide the appropriate toolbar buttons for newState. + * If showSeq or hideSeq is null, the transition will be done immediately (not animated). + * + * @param newState The state that is being switched to + * @param showSeq AnimatorSet in which to put "show" animations, or null. + * @param hideSeq AnimatorSet in which to put "hide" animations, or null. + */ + private void hideAndShowToolbarButtons(State newState, AnimatorSet showSeq, AnimatorSet hideSeq) { + final View searchButton = findViewById(R.id.search_button); + final View allAppsButton = findViewById(R.id.all_apps_button); + final View marketButton = findViewById(R.id.market_button); + final View configureButton = findViewById(R.id.configure_button); + + switch (newState) { + case WORKSPACE: + hideOrShowToolbarButton(true, searchButton, showSeq); + hideOrShowToolbarButton(true, allAppsButton, showSeq); + hideOrShowToolbarButton(true, configureButton, showSeq); + hideOrShowToolbarButton(false, marketButton, hideSeq); + mDeleteZone.setHandle(allAppsButton); + break; + case ALL_APPS: + hideOrShowToolbarButton(true, configureButton, showSeq); + hideOrShowToolbarButton(true, marketButton, showSeq); + hideOrShowToolbarButton(false, searchButton, hideSeq); + hideOrShowToolbarButton(false, allAppsButton, hideSeq); + mDeleteZone.setHandle(marketButton); + break; + case CUSTOMIZE: + hideOrShowToolbarButton(true, allAppsButton, showSeq); + hideOrShowToolbarButton(false, searchButton, hideSeq); + hideOrShowToolbarButton(false, marketButton, hideSeq); + hideOrShowToolbarButton(false, configureButton, hideSeq); + mDeleteZone.setHandle(allAppsButton); + break; + } + } + + /** + * Helper method for the cameraZoomIn/cameraZoomOut animations + * @param view The view being animated + * @param state The state that we are moving in or out of -- either ALL_APPS or CUSTOMIZE + * @param scaleFactor The scale factor used for the zoom + */ + private void setPivotsForZoom(View view, State state, float scaleFactor) { + final int height = view.getHeight(); + view.setPivotX(view.getWidth() / 2.0f); + // Set pivotY so that at the starting zoom factor, the view is off-screen by a small margin + // Assumes that the view is normally anchored to either the top or bottom of the screen + final int margin = getResources().getInteger(R.integer.config_allAppsVerticalOffset); + if (state == State.ALL_APPS) { + view.setPivotY(height + ((view.getTop() + height) / scaleFactor) + margin); + } else { + view.setPivotY(0.0f - (view.getTop() / scaleFactor) - margin); + } + } + + /** + * Zoom the camera out from the workspace to reveal 'toView'. + * Assumes that the view to show is anchored at either the very top or very bottom + * of the screen. + * @param toState The state to zoom out to. Must be ALL_APPS or CUSTOMIZE. + */ + private void cameraZoomOut(State toState, boolean animated) { + final Resources res = getResources(); + final int duration = res.getInteger(R.integer.config_allAppsZoomInTime); + final float scale = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor); + final boolean toAllApps = (toState == State.ALL_APPS); + final View toView = toAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer; + + setPivotsForZoom(toView, toState, scale); + + if (toAllApps) { + mWorkspace.shrinkToBottom(animated); + } else { + mWorkspace.shrinkToTop(animated); + } + + if (animated) { + ValueAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(toView, + PropertyValuesHolder.ofFloat("scaleX", scale, 1.0f), + PropertyValuesHolder.ofFloat("scaleY", scale, 1.0f)); + scaleAnim.setDuration(duration); + scaleAnim.setInterpolator(new DecelerateInterpolator()); + scaleAnim.addListener(new LauncherAnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // Prepare the position + toView.setTranslationX(0.0f); + toView.setTranslationY(0.0f); + toView.setVisibility(View.VISIBLE); + } + @Override + public void onAnimationEndOrCancel(Animator animation) { + // If we don't set the final scale values here, if this animation is cancelled + // it will have the wrong scale value and subsequent cameraPan animations will + // not fix that + toView.setScaleX(1.0f); + toView.setScaleY(1.0f); + } + }); + + AnimatorSet toolbarHideAnim = new AnimatorSet(); + AnimatorSet toolbarShowAnim = new AnimatorSet(); + hideAndShowToolbarButtons(toState, toolbarShowAnim, toolbarHideAnim); + + // toView should appear right at the end of the workspace shrink animation + final int startDelay = res.getInteger(R.integer.config_workspaceShrinkTime) - duration; + + if (mStateAnimation != null) mStateAnimation.cancel(); + mStateAnimation = new AnimatorSet(); + mStateAnimation.playTogether(scaleAnim, toolbarHideAnim); + mStateAnimation.play(scaleAnim).after(startDelay); + + // Show the new toolbar buttons just as the main animation is ending + final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime); + mStateAnimation.play(toolbarShowAnim).after(duration + startDelay - fadeInTime); + mStateAnimation.start(); + } else { + toView.setTranslationX(0.0f); + toView.setTranslationY(0.0f); + toView.setScaleX(1.0f); + toView.setScaleY(1.0f); + toView.setVisibility(View.VISIBLE); + hideAndShowToolbarButtons(toState, null, null); + } + } + + /** + * Zoom the camera back into the workspace, hiding 'fromView'. + * This is the opposite of cameraZoomOut. + * @param fromState The current state (must be ALL_APPS or CUSTOMIZE). + * @param animated If true, the transition will be animated. + */ + private void cameraZoomIn(State fromState, boolean animated) { + Resources res = getResources(); + int duration = res.getInteger(R.integer.config_allAppsZoomOutTime); + float scaleFactor = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor); + final View fromView = + (fromState == State.ALL_APPS) ? (View) mAllAppsGrid : mHomeCustomizationDrawer; + + mCustomizePagedView.endChoiceMode(); + mAllAppsPagedView.endChoiceMode(); + + setPivotsForZoom(fromView, fromState, scaleFactor); + + mWorkspace.unshrink(animated); + + if (animated) { + if (mStateAnimation != null) mStateAnimation.cancel(); + mStateAnimation = new AnimatorSet(); + ValueAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(fromView, + PropertyValuesHolder.ofFloat("scaleX", scaleFactor), + PropertyValuesHolder.ofFloat("scaleY", scaleFactor)); + scaleAnim.setDuration(duration); + scaleAnim.setInterpolator(new AccelerateInterpolator()); + mStateAnimation.addListener(new LauncherAnimatorListenerAdapter() { + @Override + public void onAnimationEndOrCancel(Animator animation) { + fromView.setVisibility(View.GONE); + } + }); + + AnimatorSet toolbarHideAnim = new AnimatorSet(); + AnimatorSet toolbarShowAnim = new AnimatorSet(); + hideAndShowToolbarButtons(State.WORKSPACE, toolbarShowAnim, toolbarHideAnim); + + mStateAnimation.playTogether(scaleAnim, toolbarHideAnim); + + // Show the new toolbar buttons at the very end of the whole animation + final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime); + final int unshrinkTime = res.getInteger(R.integer.config_workspaceUnshrinkTime); + mStateAnimation.play(toolbarShowAnim).after(unshrinkTime - fadeInTime); + mStateAnimation.start(); + } else { + fromView.setVisibility(View.GONE); + hideAndShowToolbarButtons(State.WORKSPACE, null, null); + } + } + + /** + * Pan the camera in the vertical plane between 'fromView' and 'toView'. + * This is the transition used on xlarge screens to go between all apps and + * the home customization drawer. + * @param fromState The view to pan away from. Must be ALL_APPS or CUSTOMIZE. + * @param toState The view to pan into the frame. Must be ALL_APPS or CUSTOMIZE. + * @param animated If true, the transition will be animated. + */ + private void cameraPan(State fromState, State toState, boolean animated) { + final Resources res = getResources(); + final int duration = res.getInteger(R.integer.config_allAppsCameraPanTime); + final int workspaceHeight = mWorkspace.getHeight(); + + final boolean fromAllApps = (fromState == State.ALL_APPS); + final View fromView = fromAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer; + final View toView = fromAllApps ? mHomeCustomizationDrawer : (View) mAllAppsGrid; + + final float fromViewStartY = fromAllApps ? 0.0f : fromView.getY(); + final float fromViewEndY = fromAllApps ? -fromView.getHeight() * 2 : workspaceHeight * 2; + final float toViewStartY = fromAllApps ? workspaceHeight * 2 : -toView.getHeight() * 2; + final float toViewEndY = fromAllApps ? workspaceHeight - toView.getHeight() : 0.0f; + + mCustomizePagedView.endChoiceMode(); + mAllAppsPagedView.endChoiceMode(); + + if (toState == State.ALL_APPS) { + mWorkspace.shrinkToBottom(animated); + } else { + mWorkspace.shrinkToTop(animated); + } + + if (animated) { + if (mStateAnimation != null) mStateAnimation.cancel(); + mStateAnimation = new AnimatorSet(); + mStateAnimation.addListener(new LauncherAnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + toView.setVisibility(View.VISIBLE); + toView.setY(toViewStartY); + } + @Override + public void onAnimationEndOrCancel(Animator animation) { + fromView.setVisibility(View.GONE); + } + }); + + AnimatorSet toolbarHideAnim = new AnimatorSet(); + AnimatorSet toolbarShowAnim = new AnimatorSet(); + hideAndShowToolbarButtons(toState, toolbarShowAnim, toolbarHideAnim); + + ObjectAnimator fromAnim = ObjectAnimator.ofFloat(fromView, "y", + fromViewStartY, fromViewEndY); + fromAnim.setDuration(duration); + ObjectAnimator toAnim = ObjectAnimator.ofPropertyValuesHolder(toView, + PropertyValuesHolder.ofFloat("y", toViewStartY, toViewEndY), + PropertyValuesHolder.ofFloat("scaleX", toView.getScaleX(), 1.0f), + PropertyValuesHolder.ofFloat("scaleY", toView.getScaleY(), 1.0f) + ); + fromAnim.setDuration(duration); + mStateAnimation.playTogether(toolbarHideAnim, fromAnim, toAnim); + + // Show the new toolbar buttons just as the main animation is ending + final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime); + mStateAnimation.play(toolbarShowAnim).after(duration - fadeInTime); + mStateAnimation.start(); + } else { + fromView.setY(fromViewEndY); + fromView.setVisibility(View.GONE); + toView.setY(toViewEndY); + toView.setScaleX(1.0f); + toView.setScaleY(1.0f); + toView.setVisibility(View.VISIBLE); + hideAndShowToolbarButtons(toState, null, null); + } + } void showAllApps(boolean animated) { - mAllAppsGrid.zoom(1.0f, animated); + if (mState == State.ALL_APPS) { + return; + } + + if (LauncherApplication.isScreenXLarge()) { + if (mState == State.CUSTOMIZE) { + cameraPan(State.CUSTOMIZE, State.ALL_APPS, animated); + } else { + cameraZoomOut(State.ALL_APPS, animated); + } + } else { + mAllAppsGrid.zoom(1.0f, animated); + } ((View) mAllAppsGrid).setFocusable(true); ((View) mAllAppsGrid).requestFocus(); - + // TODO: fade these two too mDeleteZone.setVisibility(View.GONE); + // Change the state *after* we've called all the transition code + mState = State.ALL_APPS; + } + + + void showWorkspace(boolean animated) { + showWorkspace(animated, null); + } + + void showWorkspace(boolean animated, CellLayout layout) { + if (layout != null && animated) { + mWorkspace.unshrink(layout); + } else { + mWorkspace.unshrink(animated); + } + if (mState == State.ALL_APPS) { + closeAllApps(animated); + } else if (mState == State.CUSTOMIZE) { + hideCustomizationDrawer(animated); + } + // Change the state *after* we've called all the transition code + mState = State.WORKSPACE; } /** @@ -1975,11 +2681,15 @@ public final class Launcher extends Activity * - From another workspace */ void closeAllApps(boolean animated) { - if (mAllAppsGrid.isVisible()) { + if (mState == State.ALL_APPS) { mWorkspace.setVisibility(View.VISIBLE); - mAllAppsGrid.zoom(0.0f, animated); + if (LauncherApplication.isScreenXLarge()) { + cameraZoomIn(State.ALL_APPS, animated); + } else { + mAllAppsGrid.zoom(0.0f, animated); + } ((View)mAllAppsGrid).setFocusable(false); - mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus(); + mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); } } @@ -1991,6 +2701,90 @@ public final class Launcher extends Activity // TODO } + // Show the customization drawer (only exists in x-large configuration) + private void showCustomizationDrawer(boolean animated) { + if (mState == State.ALL_APPS) { + cameraPan(State.ALL_APPS, State.CUSTOMIZE, animated); + } else { + cameraZoomOut(State.CUSTOMIZE, animated); + } + // Change the state *after* we've called all the transition code + mState = State.CUSTOMIZE; + } + + // Hide the customization drawer (only exists in x-large configuration) + void hideCustomizationDrawer(boolean animated) { + if (mState == State.CUSTOMIZE) { + cameraZoomIn(State.CUSTOMIZE, animated); + } + } + + void addExternalItemToScreen(ItemInfo itemInfo, CellLayout layout) { + if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) { + showOutOfSpaceMessage(); + } + } + + void onWorkspaceClick(CellLayout layout) { + showWorkspace(true, layout); + } + + private void updateButtonWithIconFromExternalActivity( + int buttonId, ComponentName activityName, int fallbackDrawableId) { + ImageView button = (ImageView) findViewById(buttonId); + Drawable toolbarIcon = null; + try { + PackageManager packageManager = getPackageManager(); + // Look for the toolbar icon specified in the activity meta-data + Bundle metaData = packageManager.getActivityInfo( + activityName, PackageManager.GET_META_DATA).metaData; + if (metaData != null) { + int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME); + if (iconResId != 0) { + Resources res = packageManager.getResourcesForActivity(activityName); + toolbarIcon = res.getDrawable(iconResId); + } + } + } catch (NameNotFoundException e) { + // Do nothing + } + // If we were unable to find the icon via the meta-data, use a generic one + if (toolbarIcon == null) { + button.setImageResource(fallbackDrawableId); + } else { + button.setImageDrawable(toolbarIcon); + } + } + + private void updateGlobalSearchIcon() { + if (LauncherApplication.isScreenXLarge()) { + final SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + ComponentName activityName = searchManager.getGlobalSearchActivity(); + if (activityName != null) { + updateButtonWithIconFromExternalActivity( + R.id.search_button, activityName, R.drawable.search_button_generic); + } + } + } + + /** + * Sets the app market icon (shown when all apps is visible on x-large screens) + */ + private void updateAppMarketIcon() { + if (LauncherApplication.isScreenXLarge()) { + Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET); + // Find the app market activity by resolving an intent. + // (If multiple app markets are installed, it will return the ResolverActivity.) + ComponentName activityName = intent.resolveActivity(getPackageManager()); + if (activityName != null) { + mAppMarketIntent = intent; + updateButtonWithIconFromExternalActivity( + R.id.market_button, activityName, R.drawable.app_market_generic); + } + } + } + /** * Displays the shortcut creation dialog and launches, if necessary, the * appropriate activity. @@ -2043,7 +2837,6 @@ public final class Launcher extends Activity switch (which) { case AddAdapter.ITEM_SHORTCUT: { - // Insert extra item to handle picking application pickShortcut(); break; } @@ -2091,7 +2884,7 @@ public final class Launcher extends Activity } public void onShow(DialogInterface dialog) { - mWaitingForResult = true; + mWaitingForResult = true; } } @@ -2108,7 +2901,7 @@ public final class Launcher extends Activity if (mPaused || "lock".equals(reason)) { animate = false; } - closeAllApps(animate); + showWorkspace(animate); } } } @@ -2156,12 +2949,16 @@ public final class Launcher extends Activity */ public int getCurrentWorkspaceScreen() { if (mWorkspace != null) { - return mWorkspace.getCurrentScreen(); + return mWorkspace.getCurrentPage(); } else { return SCREEN_COUNT / 2; } } + void setAllAppsPagedView(PagedView view) { + mAllAppsPagedView = view; + } + /** * Refreshes the shortcuts shown on the workspace. * @@ -2211,15 +3008,15 @@ public final class Launcher extends Activity break; case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, - (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()), - (UserFolderInfo) item); + (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), + (UserFolderInfo) item, mIconCache); workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1, false); break; case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: final FolderIcon newLiveFolder = LiveFolderIcon.fromXml( R.layout.live_folder_icon, this, - (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()), + (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), (LiveFolderInfo) item); workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1, false); @@ -2287,7 +3084,7 @@ public final class Launcher extends Activity if (mSavedState != null) { if (!mWorkspace.hasFocus()) { - mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus(); + mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); } final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS); @@ -2322,6 +3119,11 @@ public final class Launcher extends Activity */ public void bindAllApplications(ArrayList<ApplicationInfo> apps) { mAllAppsGrid.setApps(apps); + if (mCustomizePagedView != null) { + mCustomizePagedView.setApps(apps); + } + updateAppMarketIcon(); + updateGlobalSearchIcon(); } /** @@ -2333,6 +3135,11 @@ public final class Launcher extends Activity setLoadOnResume(); removeDialog(DIALOG_CREATE_SHORTCUT); mAllAppsGrid.addApps(apps); + if (mCustomizePagedView != null) { + mCustomizePagedView.addApps(apps); + } + updateAppMarketIcon(); + updateGlobalSearchIcon(); } /** @@ -2345,6 +3152,11 @@ public final class Launcher extends Activity removeDialog(DIALOG_CREATE_SHORTCUT); mWorkspace.updateShortcuts(apps); mAllAppsGrid.updateApps(apps); + if (mCustomizePagedView != null) { + mCustomizePagedView.updateApps(apps); + } + updateAppMarketIcon(); + updateGlobalSearchIcon(); } /** @@ -2358,6 +3170,21 @@ public final class Launcher extends Activity mWorkspace.removeItems(apps); } mAllAppsGrid.removeApps(apps); + if (mCustomizePagedView != null) { + mCustomizePagedView.removeApps(apps); + } + updateAppMarketIcon(); + updateGlobalSearchIcon(); + } + + /** + * A number of packages were updated. + */ + public void bindPackagesUpdated() { + // update the customization drawer contents + if (mCustomizePagedView != null) { + mCustomizePagedView.update(); + } } /** diff --git a/src/com/android/launcher2/LauncherAnimatorListenerAdapter.java b/src/com/android/launcher2/LauncherAnimatorListenerAdapter.java new file mode 100644 index 0000000..3ab4868 --- /dev/null +++ b/src/com/android/launcher2/LauncherAnimatorListenerAdapter.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.animation.Animator; + +import java.util.HashSet; + +/** + * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}. + * Any custom listener that cares only about a subset of the methods of this listener can + * simply subclass this adapter class instead of implementing the interface directly. + */ +public abstract class LauncherAnimatorListenerAdapter implements Animator.AnimatorListener { + HashSet<Animator> cancelled = new HashSet<Animator>(); + + /** + * {@inheritDoc} + */ + @Override + public final void onAnimationCancel(Animator animation) { + onAnimationEndOrCancel(animation); + cancelled.add(animation); + } + + /** + * {@inheritDoc} + */ + @Override + public final void onAnimationEnd(Animator animation) { + if (!cancelled.contains(animation)) onAnimationEndOrCancel(animation); + cancelled.remove(animation); + } + + /** + * Like onAnimationEnd, except it's called immediately in the case on onAnimationCancel, and + * it's only called once in that case + */ + public void onAnimationEndOrCancel(Animator animation) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationRepeat(Animator animation) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAnimationStart(Animator animation) { + } + +} diff --git a/src/com/android/launcher2/LauncherAppWidgetHost.java b/src/com/android/launcher2/LauncherAppWidgetHost.java index a5761ec..46e66e7 100644 --- a/src/com/android/launcher2/LauncherAppWidgetHost.java +++ b/src/com/android/launcher2/LauncherAppWidgetHost.java @@ -30,7 +30,7 @@ public class LauncherAppWidgetHost extends AppWidgetHost { public LauncherAppWidgetHost(Context context, int hostId) { super(context, hostId); } - + @Override protected AppWidgetHostView onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { diff --git a/src/com/android/launcher2/LauncherAppWidgetInfo.java b/src/com/android/launcher2/LauncherAppWidgetInfo.java index 8499ebb..32c92aa 100644 --- a/src/com/android/launcher2/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher2/LauncherAppWidgetInfo.java @@ -17,25 +17,50 @@ package com.android.launcher2; import android.appwidget.AppWidgetHostView; +import android.content.ComponentName; import android.content.ContentValues; /** - * Represents a widget, which just contains an identifier. + * Represents a widget (either instantiated or about to be) in the Launcher. */ class LauncherAppWidgetInfo extends ItemInfo { /** + * Indicates that the widget hasn't been instantiated yet. + */ + static final int NO_ID = -1; + + /** * Identifier for this widget when talking with * {@link android.appwidget.AppWidgetManager} for updates. */ - int appWidgetId; + int appWidgetId = NO_ID; + + ComponentName providerName; + // TODO: Are these necessary here? + int minWidth = -1; + int minHeight = -1; + /** * View that holds this widget after it's been created. This view isn't created * until Launcher knows it's needed. */ AppWidgetHostView hostView = null; + /** + * Constructor for use with AppWidgets that haven't been instantiated yet. + */ + LauncherAppWidgetInfo(ComponentName providerName) { + itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; + this.providerName = providerName; + + // Since the widget isn't instantiated yet, we don't know these values. Set them to -1 + // to indicate that they should be calculated based on the layout and minWidth/minHeight + spanX = -1; + spanY = -1; + } + LauncherAppWidgetInfo(int appWidgetId) { itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; this.appWidgetId = appWidgetId; @@ -52,7 +77,6 @@ class LauncherAppWidgetInfo extends ItemInfo { return "AppWidget(id=" + Integer.toString(appWidgetId) + ")"; } - @Override void unbind() { super.unbind(); diff --git a/src/com/android/launcher2/LauncherApplication.java b/src/com/android/launcher2/LauncherApplication.java index eda92d9..ed007dd 100644 --- a/src/com/android/launcher2/LauncherApplication.java +++ b/src/com/android/launcher2/LauncherApplication.java @@ -20,6 +20,7 @@ import android.app.Application; import android.content.ContentResolver; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Configuration; import android.database.ContentObserver; import android.os.Handler; import dalvik.system.VMRuntime; @@ -27,6 +28,9 @@ import dalvik.system.VMRuntime; public class LauncherApplication extends Application { public LauncherModel mModel; public IconCache mIconCache; + private static boolean sIsScreenXLarge; + private static float sScreenDensity; + private static final boolean ENABLE_ROTATION = false; @Override public void onCreate() { @@ -34,6 +38,10 @@ public class LauncherApplication extends Application { super.onCreate(); + // set sIsScreenXLarge and sScreenDensity *before* creating icon cache + sIsScreenXLarge = (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE; + sScreenDensity = getResources().getDisplayMetrics().density; + mIconCache = new IconCache(this); mModel = new LauncherModel(this, mIconCache); @@ -46,6 +54,7 @@ public class LauncherApplication extends Application { filter = new IntentFilter(); filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + filter.addAction(Intent.ACTION_LOCALE_CHANGED); registerReceiver(mModel, filter); // Register for changes to the favorites @@ -89,4 +98,16 @@ public class LauncherApplication extends Application { LauncherModel getModel() { return mModel; } + + public static boolean isInPlaceRotationEnabled() { + return sIsScreenXLarge && ENABLE_ROTATION; + } + + public static boolean isScreenXLarge() { + return sIsScreenXLarge; + } + + public static float getScreenDensity() { + return sScreenDensity; + } } diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java index b819510..713268a 100644 --- a/src/com/android/launcher2/LauncherModel.java +++ b/src/com/android/launcher2/LauncherModel.java @@ -16,6 +16,15 @@ package com.android.launcher2; +import java.lang.ref.WeakReference; +import java.net.URISyntaxException; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; @@ -23,9 +32,9 @@ import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.Intent; import android.content.Intent.ShortcutIconResource; -import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; @@ -38,22 +47,13 @@ import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.Parcelable; -import android.os.RemoteException; -import android.util.Log; import android.os.Process; +import android.os.RemoteException; import android.os.SystemClock; - -import java.lang.ref.WeakReference; -import java.net.URISyntaxException; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; +import android.util.Log; import com.android.launcher.R; +import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData; /** * Maintains in-memory state of the Launcher. It is expected that there should be only one @@ -95,6 +95,9 @@ public class LauncherModel extends BroadcastReceiver { private Bitmap mDefaultIcon; + private static int mCellCountX; + private static int mCellCountY; + public interface Callbacks { public boolean setLoadOnResume(); public int getCurrentWorkspaceScreen(); @@ -107,6 +110,7 @@ public class LauncherModel extends BroadcastReceiver { public void bindAppsAdded(ArrayList<ApplicationInfo> apps); public void bindAppsUpdated(ArrayList<ApplicationInfo> apps); public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent); + public void bindPackagesUpdated(); public boolean isAllAppsVisible(); } @@ -116,7 +120,7 @@ public class LauncherModel extends BroadcastReceiver { mIconCache = iconCache; mDefaultIcon = Utilities.createIconBitmap( - app.getPackageManager().getDefaultActivityIcon(), app); + mIconCache.getFullResDefaultActivityIcon(), app); mAllAppsLoadDelay = app.getResources().getInteger(R.integer.config_allAppsBatchLoadDelay); @@ -147,6 +151,7 @@ public class LauncherModel extends BroadcastReceiver { */ static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen, int cellX, int cellY) { + item.container = container; item.screen = screen; item.cellX = cellX; @@ -157,8 +162,8 @@ public class LauncherModel extends BroadcastReceiver { final ContentResolver cr = context.getContentResolver(); values.put(LauncherSettings.Favorites.CONTAINER, item.container); - values.put(LauncherSettings.Favorites.CELLX, item.cellX); - values.put(LauncherSettings.Favorites.CELLY, item.cellY); + values.put(LauncherSettings.Favorites.CELLX, cellX); + values.put(LauncherSettings.Favorites.CELLY, cellY); values.put(LauncherSettings.Favorites.SCREEN, item.screen); sWorker.post(new Runnable() { @@ -187,6 +192,48 @@ public class LauncherModel extends BroadcastReceiver { } /** + * Returns an ItemInfo array containing all the items in the LauncherModel. + * The ItemInfo.id is not set through this function. + */ + static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) { + ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); + final ContentResolver cr = context.getContentResolver(); + Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] { + LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER, + LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY, + LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null); + + final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); + final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); + final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); + final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); + final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); + final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX); + final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY); + + try { + while (c.moveToNext()) { + ItemInfo item = new ItemInfo(); + item.cellX = c.getInt(cellXIndex); + item.cellY = c.getInt(cellYIndex); + item.spanX = c.getInt(spanXIndex); + item.spanY = c.getInt(spanYIndex); + item.container = c.getInt(containerIndex); + item.itemType = c.getInt(itemTypeIndex); + item.screen = c.getInt(screenIndex); + + items.add(item); + } + } catch (Exception e) { + items.clear(); + } finally { + c.close(); + } + + return items; + } + + /** * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList. */ FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) { @@ -245,9 +292,10 @@ public class LauncherModel extends BroadcastReceiver { final ContentValues values = new ContentValues(); final ContentResolver cr = context.getContentResolver(); - item.onAddToDatabase(values); + item.updateValuesWithCoordinates(values, cellX, cellY); + Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); @@ -257,6 +305,32 @@ public class LauncherModel extends BroadcastReceiver { } /** + * Creates a new unique child id, for a given cell span across all layouts. + */ + static int getCellLayoutChildId( + int cellId, int screen, int localCellX, int localCellY, int spanX, int spanY) { + return ((cellId & 0xFF) << 24) + | (screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF); + } + + static int getCellCountX() { + return mCellCountX; + } + + static int getCellCountY() { + return mCellCountY; + } + + /** + * Updates the model orientation helper to take into account the current layout dimensions + * when performing local/canonical coordinate transformations. + */ + static void updateWorkspaceLayoutCells(int shortAxisCellCount, int longAxisCellCount) { + mCellCountX = shortAxisCellCount; + mCellCountY = longAxisCellCount; + } + + /** * Update an item to the database in a specified container. */ static void updateItemInDatabase(Context context, ItemInfo item) { @@ -264,6 +338,7 @@ public class LauncherModel extends BroadcastReceiver { final ContentResolver cr = context.getContentResolver(); item.onAddToDatabase(values); + item.updateValuesWithCoordinates(values, item.cellX, item.cellY); cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null); } @@ -309,7 +384,7 @@ public class LauncherModel extends BroadcastReceiver { */ public void onReceive(Context context, Intent intent) { if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent); - + final String action = intent.getAction(); if (Intent.ACTION_PACKAGE_CHANGED.equals(action) @@ -350,25 +425,41 @@ public class LauncherModel extends BroadcastReceiver { String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages)); // Then, rebind everything. - boolean runLoader = true; - if (mCallbacks != null) { - Callbacks callbacks = mCallbacks.get(); - if (callbacks != null) { - // If they're paused, we can skip loading, because they'll do it again anyway - if (callbacks.setLoadOnResume()) { - runLoader = false; - } - } - } - if (runLoader) { - startLoader(mApp, false); - } - + startLoaderFromBackground(); } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); enqueuePackageUpdated(new PackageUpdatedTask( PackageUpdatedTask.OP_UNAVAILABLE, packages)); + } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { + // If we have changed locale we need to clear out the labels in all apps. + // Do this here because if the launcher activity is running it will be restarted. + // If it's not running startLoaderFromBackground will merely tell it that it needs + // to reload. Either way, mAllAppsLoaded will be cleared so it re-reads everything + // next time. + mAllAppsLoaded = false; + startLoaderFromBackground(); + } + } + /** + * When the launcher is in the background, it's possible for it to miss paired + * configuration changes. So whenever we trigger the loader from the background + * tell the launcher that it needs to re-run the loader when it comes back instead + * of doing it now. + */ + public void startLoaderFromBackground() { + boolean runLoader = false; + if (mCallbacks != null) { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + // Only actually run the loader if they're not paused. + if (!callbacks.setLoadOnResume()) { + runLoader = true; + } + } + } + if (runLoader) { + startLoader(mApp, false); } } @@ -474,7 +565,7 @@ public class LauncherModel extends BroadcastReceiver { } if (DEBUG_LOADERS) { Log.d(TAG, "waited " - + (SystemClock.uptimeMillis()-workspaceWaitTime) + + (SystemClock.uptimeMillis()-workspaceWaitTime) + "ms for previous step to finish binding"); } } @@ -494,7 +585,6 @@ public class LauncherModel extends BroadcastReceiver { android.os.Process.setThreadPriority(mIsLaunching ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); } - if (loadWorkspaceFirst) { if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); loadAndBindWorkspace(); @@ -596,14 +686,13 @@ public class LauncherModel extends BroadcastReceiver { if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { return true; } - for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { if (occupied[item.screen][x][y] != null) { Log.e(TAG, "Error loading shortcut " + item - + " into cell (" + item.screen + ":" + + " into cell (" + item.screen + ":" + x + "," + y - + ") occupied by " + + ") occupied by " + occupied[item.screen][x][y]); return false; } @@ -635,7 +724,8 @@ public class LauncherModel extends BroadcastReceiver { final Cursor c = contentResolver.query( LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); - final ItemInfo occupied[][][] = new ItemInfo[Launcher.SCREEN_COUNT][Launcher.NUMBER_CELLS_X][Launcher.NUMBER_CELLS_Y]; + final ItemInfo occupied[][][] = + new ItemInfo[Launcher.SCREEN_COUNT][mCellCountX][mCellCountY]; try { final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); @@ -744,7 +834,6 @@ public class LauncherModel extends BroadcastReceiver { UserFolderInfo folderInfo = findOrMakeUserFolder(mFolders, id); folderInfo.title = c.getString(titleIndex); - folderInfo.id = id; container = c.getInt(containerIndex); folderInfo.container = container; @@ -756,7 +845,6 @@ public class LauncherModel extends BroadcastReceiver { if (!checkItemPlacement(occupied, folderInfo)) { break; } - switch (container) { case LauncherSettings.Favorites.CONTAINER_DESKTOP: mItems.add(folderInfo); @@ -779,7 +867,6 @@ public class LauncherModel extends BroadcastReceiver { itemsToRemove.add(id); } else { LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(mFolders, id); - intentDescription = c.getString(intentIndex); intent = null; if (intentDescription != null) { @@ -825,7 +912,7 @@ public class LauncherModel extends BroadcastReceiver { final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(appWidgetId); - + if (!isSafeMode && (provider == null || provider.provider == null || provider.provider.getPackageName() == null)) { Log.e(TAG, "Deleting widget that isn't installed anymore: id=" @@ -886,13 +973,13 @@ public class LauncherModel extends BroadcastReceiver { if (DEBUG_LOADERS) { Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); Log.d(TAG, "workspace layout: "); - for (int y = 0; y < Launcher.NUMBER_CELLS_Y; y++) { + for (int y = 0; y < mCellCountY; y++) { String line = ""; for (int s = 0; s < Launcher.SCREEN_COUNT; s++) { if (s > 0) { line += " | "; } - for (int x = 0; x < Launcher.NUMBER_CELLS_X; x++) { + for (int x = 0; x < mCellCountX; x++) { line += ((occupied[s][x][y] != null) ? "#" : "."); } } @@ -1116,7 +1203,7 @@ public class LauncherModel extends BroadcastReceiver { startIndex = i; for (int j=0; i<N && j<batchSize; j++) { // This builds the icon bitmaps. - mAllAppsList.add(new ApplicationInfo(apps.get(i), mIconCache)); + mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i), mIconCache)); i++; } @@ -1279,6 +1366,15 @@ public class LauncherModel extends BroadcastReceiver { } }); } + + mHandler.post(new Runnable() { + @Override + public void run() { + if (callbacks == mCallbacks.get()) { + callbacks.bindPackagesUpdated(); + } + } + }); } } @@ -1374,7 +1470,8 @@ public class LauncherModel extends BroadcastReceiver { Resources resources = packageManager.getResourcesForApplication(packageName); if (resources != null) { final int id = resources.getIdentifier(resourceName, null, null); - icon = Utilities.createIconBitmap(resources.getDrawable(id), context); + icon = Utilities.createIconBitmap( + mIconCache.getFullResIcon(resources, id), context); } } catch (Exception e) { // drop this. we have other places to look for icons @@ -1423,16 +1520,69 @@ public class LauncherModel extends BroadcastReceiver { } ShortcutInfo addShortcut(Context context, Intent data, - CellLayout.CellInfo cellInfo, boolean notify) { + int screen, int cellX, int cellY, boolean notify) { - final ShortcutInfo info = infoFromShortcutIntent(context, data); + final ShortcutInfo info = infoFromShortcutIntent(context, data, null); addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, - cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify); + screen, cellX, cellY, notify); return info; } - private ShortcutInfo infoFromShortcutIntent(Context context, Intent data) { + /** + * Attempts to find an AppWidgetProviderInfo that matches the given component. + */ + AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context, + ComponentName component) { + List<AppWidgetProviderInfo> widgets = + AppWidgetManager.getInstance(context).getInstalledProviders(); + for (AppWidgetProviderInfo info : widgets) { + if (info.provider.equals(component)) { + return info; + } + } + return null; + } + + /** + * Returns a list of all the widgets that can handle configuration with a particular mimeType. + */ + List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) { + final PackageManager packageManager = context.getPackageManager(); + final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities = + new ArrayList<WidgetMimeTypeHandlerData>(); + + final Intent supportsIntent = + new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE); + supportsIntent.setType(mimeType); + + // Create a set of widget configuration components that we can test against + final List<AppWidgetProviderInfo> widgets = + AppWidgetManager.getInstance(context).getInstalledProviders(); + final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget = + new HashMap<ComponentName, AppWidgetProviderInfo>(); + for (AppWidgetProviderInfo info : widgets) { + configurationComponentToWidget.put(info.configure, info); + } + + // Run through each of the intents that can handle this type of clip data, and cross + // reference them with the components that are actual configuration components + final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent, + PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo info : activities) { + final ActivityInfo activityInfo = info.activityInfo; + final ComponentName infoComponent = new ComponentName(activityInfo.packageName, + activityInfo.name); + if (configurationComponentToWidget.containsKey(infoComponent)) { + supportedConfigurationActivities.add( + new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info, + configurationComponentToWidget.get(infoComponent))); + } + } + return supportedConfigurationActivities; + } + + ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) { Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); @@ -1455,7 +1605,8 @@ public class LauncherModel extends BroadcastReceiver { Resources resources = packageManager.getResourcesForApplication( iconResource.packageName); final int id = resources.getIdentifier(iconResource.resourceName, null, null); - icon = Utilities.createIconBitmap(resources.getDrawable(id), context); + icon = Utilities.createIconBitmap( + mIconCache.getFullResIcon(resources, id), context); } catch (Exception e) { Log.w(TAG, "Could not load shortcut icon: " + extra); } @@ -1465,8 +1616,12 @@ public class LauncherModel extends BroadcastReceiver { final ShortcutInfo info = new ShortcutInfo(); if (icon == null) { - icon = getFallbackIcon(); - info.usingFallbackIcon = true; + if (fallbackIcon != null) { + icon = fallbackIcon; + } else { + icon = getFallbackIcon(); + info.usingFallbackIcon = true; + } } info.setIcon(icon); @@ -1478,7 +1633,7 @@ public class LauncherModel extends BroadcastReceiver { return info; } - private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex, + private void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) { int iconType = c.getInt(iconTypeIndex); @@ -1488,13 +1643,14 @@ public class LauncherModel extends BroadcastReceiver { String resourceName = c.getString(iconResourceIndex); PackageManager packageManager = context.getPackageManager(); try { - Resources resources = packageManager.getResourcesForApplication(packageName); - final int id = resources.getIdentifier(resourceName, null, null); - liveFolderInfo.icon = Utilities.createIconBitmap(resources.getDrawable(id), - context); + Resources appResources = packageManager.getResourcesForApplication(packageName); + final int id = appResources.getIdentifier(resourceName, null, null); + liveFolderInfo.icon = Utilities.createIconBitmap( + mIconCache.getFullResIcon(appResources, id), context); } catch (Exception e) { + Resources resources = context.getResources(); liveFolderInfo.icon = Utilities.createIconBitmap( - context.getResources().getDrawable(R.drawable.ic_launcher_folder), + mIconCache.getFullResIcon(resources, R.drawable.ic_launcher_folder), context); } liveFolderInfo.iconResource = new Intent.ShortcutIconResource(); @@ -1502,8 +1658,9 @@ public class LauncherModel extends BroadcastReceiver { liveFolderInfo.iconResource.resourceName = resourceName; break; default: + Resources resources = context.getResources(); liveFolderInfo.icon = Utilities.createIconBitmap( - context.getResources().getDrawable(R.drawable.ic_launcher_folder), + mIconCache.getFullResIcon(resources, R.drawable.ic_launcher_folder), context); } } diff --git a/src/com/android/launcher2/LiveFolderInfo.java b/src/com/android/launcher2/LiveFolderInfo.java index 7d0a0f5..74b0217 100644 --- a/src/com/android/launcher2/LiveFolderInfo.java +++ b/src/com/android/launcher2/LiveFolderInfo.java @@ -18,7 +18,6 @@ package com.android.launcher2; import android.content.ContentValues; import android.content.Intent; -import android.graphics.drawable.Drawable; import android.graphics.Bitmap; import android.net.Uri; diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java new file mode 100644 index 0000000..e9829fb --- /dev/null +++ b/src/com/android/launcher2/PagedView.java @@ -0,0 +1,1272 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import java.util.ArrayList; +import java.util.HashMap; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ActionMode; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.AnimationUtils; +import android.widget.Checkable; +import android.widget.LinearLayout; +import android.widget.Scroller; + +import com.android.launcher.R; + +/** + * An abstraction of the original Workspace which supports browsing through a + * sequential list of "pages" + */ +public abstract class PagedView extends ViewGroup { + private static final String TAG = "PagedView"; + protected static final int INVALID_PAGE = -1; + + // the min drag distance for a fling to register, to prevent random page shifts + private static final int MIN_LENGTH_FOR_FLING = 25; + // The min drag distance to trigger a page shift (regardless of velocity) + private static final int MIN_LENGTH_FOR_MOVE = 200; + + private static final int PAGE_SNAP_ANIMATION_DURATION = 1000; + protected static final float NANOTIME_DIV = 1000000000.0f; + + // the velocity at which a fling gesture will cause us to snap to the next page + protected int mSnapVelocity = 500; + + protected float mSmoothingTime; + protected float mTouchX; + + protected boolean mFirstLayout = true; + + protected int mCurrentPage; + protected int mNextPage = INVALID_PAGE; + protected Scroller mScroller; + private VelocityTracker mVelocityTracker; + + private float mDownMotionX; + private float mLastMotionX; + private float mLastMotionY; + private int mLastScreenCenter = -1; + + protected final static int TOUCH_STATE_REST = 0; + protected final static int TOUCH_STATE_SCROLLING = 1; + protected final static int TOUCH_STATE_PREV_PAGE = 2; + protected final static int TOUCH_STATE_NEXT_PAGE = 3; + protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f; + + protected int mTouchState = TOUCH_STATE_REST; + + protected OnLongClickListener mLongClickListener; + + private boolean mAllowLongPress = true; + + private int mTouchSlop; + private int mPagingTouchSlop; + private int mMaximumVelocity; + protected int mPageSpacing; + protected int mPageLayoutPaddingTop; + protected int mPageLayoutPaddingBottom; + protected int mPageLayoutPaddingLeft; + protected int mPageLayoutPaddingRight; + protected int mPageLayoutWidthGap; + protected int mPageLayoutHeightGap; + protected int mCellCountX; + protected int mCellCountY; + protected boolean mCenterPagesVertically; + + protected static final int INVALID_POINTER = -1; + + protected int mActivePointerId = INVALID_POINTER; + + private PageSwitchListener mPageSwitchListener; + + private ArrayList<Boolean> mDirtyPageContent; + private boolean mDirtyPageAlpha; + + // choice modes + protected static final int CHOICE_MODE_NONE = 0; + protected static final int CHOICE_MODE_SINGLE = 1; + // Multiple selection mode is not supported by all Launcher actions atm + protected static final int CHOICE_MODE_MULTIPLE = 2; + + protected int mChoiceMode; + private ActionMode mActionMode; + + protected PagedViewIconCache mPageViewIconCache; + + // If true, syncPages and syncPageItems will be called to refresh pages + protected boolean mContentIsRefreshable = true; + + // If true, modify alpha of neighboring pages as user scrolls left/right + protected boolean mFadeInAdjacentScreens = true; + + // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding + // to switch to a new page + protected boolean mUsePagingTouchSlop = true; + + // If true, the subclass should directly update mScrollX itself in its computeScroll method + // (SmoothPagedView does this) + protected boolean mDeferScrollUpdate = false; + + protected boolean mIsPageMoving = false; + + /** + * Simple cache mechanism for PagedViewIcon outlines. + */ + class PagedViewIconCache { + private final HashMap<Object, Bitmap> iconOutlineCache = new HashMap<Object, Bitmap>(); + + public void clear() { + iconOutlineCache.clear(); + } + public void addOutline(Object key, Bitmap b) { + iconOutlineCache.put(key, b); + } + public void removeOutline(Object key) { + if (iconOutlineCache.containsKey(key)) { + iconOutlineCache.remove(key); + } + } + public Bitmap getOutline(Object key) { + return iconOutlineCache.get(key); + } + } + + public interface PageSwitchListener { + void onPageSwitch(View newPage, int newPageIndex); + } + + public PagedView(Context context) { + this(context, null); + } + + public PagedView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mChoiceMode = CHOICE_MODE_NONE; + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.PagedView, defStyle, 0); + mPageSpacing = a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0); + mPageLayoutPaddingTop = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingTop, 10); + mPageLayoutPaddingBottom = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingBottom, 10); + mPageLayoutPaddingLeft = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingLeft, 10); + mPageLayoutPaddingRight = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingRight, 10); + mPageLayoutWidthGap = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutWidthGap, -1); + mPageLayoutHeightGap = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutHeightGap, -1); + a.recycle(); + + setHapticFeedbackEnabled(false); + init(); + } + + /** + * Initializes various states for this workspace. + */ + protected void init() { + mDirtyPageContent = new ArrayList<Boolean>(); + mDirtyPageContent.ensureCapacity(32); + mPageViewIconCache = new PagedViewIconCache(); + mScroller = new Scroller(getContext()); + mCurrentPage = 0; + mCenterPagesVertically = true; + + final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + } + + public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { + mPageSwitchListener = pageSwitchListener; + if (mPageSwitchListener != null) { + mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); + } + } + + /** + * Returns the index of the currently displayed page. + * + * @return The index of the currently displayed page. + */ + int getCurrentPage() { + return mCurrentPage; + } + + int getPageCount() { + return getChildCount(); + } + + View getPageAt(int index) { + return getChildAt(index); + } + + int getScrollWidth() { + return getWidth(); + } + + /** + * Sets the current page. + */ + void setCurrentPage(int currentPage) { + if (!mScroller.isFinished()) mScroller.abortAnimation(); + if (getChildCount() == 0) return; + + mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1)); + int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage); + scrollTo(newX, 0); + mScroller.setFinalX(newX); + + invalidate(); + notifyPageSwitchListener(); + } + + protected void notifyPageSwitchListener() { + if (mPageSwitchListener != null) { + mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); + } + } + + private void pageBeginMoving() { + mIsPageMoving = true; + onPageBeginMoving(); + } + + private void pageEndMoving() { + onPageEndMoving(); + mIsPageMoving = false; + } + + // a method that subclasses can override to add behavior + protected void onPageBeginMoving() { + } + + // a method that subclasses can override to add behavior + protected void onPageEndMoving() { + } + + /** + * Registers the specified listener on each page contained in this workspace. + * + * @param l The listener used to respond to long clicks. + */ + @Override + public void setOnLongClickListener(OnLongClickListener l) { + mLongClickListener = l; + final int count = getPageCount(); + for (int i = 0; i < count; i++) { + getPageAt(i).setOnLongClickListener(l); + } + } + + @Override + public void scrollTo(int x, int y) { + super.scrollTo(x, y); + mTouchX = x; + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + } + + // we moved this functionality to a helper function so SmoothPagedView can reuse it + protected boolean computeScrollHelper() { + if (mScroller.computeScrollOffset()) { + mDirtyPageAlpha = true; + scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); + invalidate(); + return true; + } else if (mNextPage != INVALID_PAGE) { + mDirtyPageAlpha = true; + mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1)); + mNextPage = INVALID_PAGE; + notifyPageSwitchListener(); + pageEndMoving(); + return true; + } + return false; + } + + @Override + public void computeScroll() { + computeScrollHelper(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + if (widthMode != MeasureSpec.EXACTLY) { + throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); + } + + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + if (heightMode != MeasureSpec.EXACTLY) { + throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); + } + + // The children are given the same width and height as the workspace + // unless they were set to WRAP_CONTENT + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + // disallowing padding in paged view (just pass 0) + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + int childWidthMode; + if (lp.width == LayoutParams.WRAP_CONTENT) { + childWidthMode = MeasureSpec.AT_MOST; + } else { + childWidthMode = MeasureSpec.EXACTLY; + } + + int childHeightMode; + if (lp.height == LayoutParams.WRAP_CONTENT) { + childHeightMode = MeasureSpec.AT_MOST; + } else { + childHeightMode = MeasureSpec.EXACTLY; + } + + final int childWidthMeasureSpec = + MeasureSpec.makeMeasureSpec(widthSize, childWidthMode); + final int childHeightMeasureSpec = + MeasureSpec.makeMeasureSpec(heightSize, childHeightMode); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + setMeasuredDimension(widthSize, heightSize); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { + setHorizontalScrollBarEnabled(false); + int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage); + scrollTo(newX, 0); + mScroller.setFinalX(newX); + setHorizontalScrollBarEnabled(true); + mFirstLayout = false; + } + + final int childCount = getChildCount(); + int childLeft = 0; + if (childCount > 0) { + childLeft = getRelativeChildOffset(0); + } + + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != View.GONE) { + final int childWidth = child.getMeasuredWidth(); + final int childHeight = (mCenterPagesVertically ? + (getMeasuredHeight() - child.getMeasuredHeight()) / 2 : 0); + child.layout(childLeft, childHeight, + childLeft + childWidth, childHeight + child.getMeasuredHeight()); + childLeft += childWidth + mPageSpacing; + } + } + } + + protected void updateAdjacentPagesAlpha() { + if (mFadeInAdjacentScreens) { + if (mDirtyPageAlpha || (mTouchState == TOUCH_STATE_SCROLLING) || !mScroller.isFinished()) { + int halfScreenSize = getMeasuredWidth() / 2; + int screenCenter = mScrollX + halfScreenSize; + final int childCount = getChildCount(); + for (int i = 0; i < childCount; ++i) { + View layout = (View) getChildAt(i); + int childWidth = layout.getMeasuredWidth(); + int halfChildWidth = (childWidth / 2); + int childCenter = getChildOffset(i) + halfChildWidth; + + // On the first layout, we may not have a width nor a proper offset, so for now + // we should just assume full page width (and calculate the offset according to + // that). + if (childWidth <= 0) { + childWidth = getMeasuredWidth(); + childCenter = (i * childWidth) + (childWidth / 2); + } + + int d = halfChildWidth; + int distanceFromScreenCenter = childCenter - screenCenter; + if (distanceFromScreenCenter > 0) { + if (i > 0) { + d += getChildAt(i - 1).getMeasuredWidth() / 2; + } + } else { + if (i < childCount - 1) { + d += getChildAt(i + 1).getMeasuredWidth() / 2; + } + } + d += mPageSpacing; + + // Preventing potential divide-by-zero + d = Math.max(1, d); + + float dimAlpha = (float) (Math.abs(distanceFromScreenCenter)) / d; + dimAlpha = Math.max(0.0f, Math.min(1.0f, (dimAlpha * dimAlpha))); + float alpha = 1.0f - dimAlpha; + + if (alpha < ALPHA_QUANTIZE_LEVEL) { + alpha = 0.0f; + } else if (alpha > 1.0f - ALPHA_QUANTIZE_LEVEL) { + alpha = 1.0f; + } + + if (Float.compare(alpha, layout.getAlpha()) != 0) { + layout.setAlpha(alpha); + } + } + mDirtyPageAlpha = false; + } + } + } + + protected void screenScrolled(int screenCenter) { + } + + @Override + protected void dispatchDraw(Canvas canvas) { + int halfScreenSize = getMeasuredWidth() / 2; + int screenCenter = mScrollX + halfScreenSize; + + if (screenCenter != mLastScreenCenter) { + screenScrolled(screenCenter); + updateAdjacentPagesAlpha(); + mLastScreenCenter = screenCenter; + } + + // Find out which screens are visible; as an optimization we only call draw on them + // As an optimization, this code assumes that all pages have the same width as the 0th + // page. + final int pageCount = getChildCount(); + if (pageCount > 0) { + final int pageWidth = getChildAt(0).getMeasuredWidth(); + final int screenWidth = getMeasuredWidth(); + int x = getRelativeChildOffset(0) + pageWidth; + int leftScreen = 0; + int rightScreen = 0; + while (x <= mScrollX) { + leftScreen++; + x += pageWidth + mPageSpacing; + // replace above line with this if you don't assume all pages have same width as 0th + // page: + // x += getChildAt(leftScreen).getMeasuredWidth(); + } + rightScreen = leftScreen; + while (x < mScrollX + screenWidth) { + rightScreen++; + x += pageWidth + mPageSpacing; + // replace above line with this if you don't assume all pages have same width as 0th + // page: + //if (rightScreen < pageCount) { + // x += getChildAt(rightScreen).getMeasuredWidth(); + //} + } + rightScreen = Math.min(getChildCount() - 1, rightScreen); + + final long drawingTime = getDrawingTime(); + for (int i = leftScreen; i <= rightScreen; i++) { + drawChild(canvas, getChildAt(i), drawingTime); + } + } + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + int page = indexOfChild(child); + if (page != mCurrentPage || !mScroller.isFinished()) { + snapToPage(page); + return true; + } + return false; + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + int focusablePage; + if (mNextPage != INVALID_PAGE) { + focusablePage = mNextPage; + } else { + focusablePage = mCurrentPage; + } + View v = getPageAt(focusablePage); + if (v != null) { + v.requestFocus(direction, previouslyFocusedRect); + } + return false; + } + + @Override + public boolean dispatchUnhandledMove(View focused, int direction) { + if (direction == View.FOCUS_LEFT) { + if (getCurrentPage() > 0) { + snapToPage(getCurrentPage() - 1); + return true; + } + } else if (direction == View.FOCUS_RIGHT) { + if (getCurrentPage() < getPageCount() - 1) { + snapToPage(getCurrentPage() + 1); + return true; + } + } + return super.dispatchUnhandledMove(focused, direction); + } + + @Override + public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { + if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) { + getPageAt(mCurrentPage).addFocusables(views, direction); + } + if (direction == View.FOCUS_LEFT) { + if (mCurrentPage > 0) { + getPageAt(mCurrentPage - 1).addFocusables(views, direction); + } + } else if (direction == View.FOCUS_RIGHT){ + if (mCurrentPage < getPageCount() - 1) { + getPageAt(mCurrentPage + 1).addFocusables(views, direction); + } + } + } + + /** + * If one of our descendant views decides that it could be focused now, only + * pass that along if it's on the current page. + * + * This happens when live folders requery, and if they're off page, they + * end up calling requestFocus, which pulls it on page. + */ + @Override + public void focusableViewAvailable(View focused) { + View current = getPageAt(mCurrentPage); + View v = focused; + while (true) { + if (v == current) { + super.focusableViewAvailable(focused); + return; + } + if (v == this) { + return; + } + ViewParent parent = v.getParent(); + if (parent instanceof View) { + v = (View)v.getParent(); + } else { + return; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (disallowIntercept) { + // We need to make sure to cancel our long press if + // a scrollable widget takes over touch events + final View currentPage = getChildAt(mCurrentPage); + currentPage.cancelLongPress(); + } + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + /* + * This method JUST determines whether we want to intercept the motion. + * If we return true, onTouchEvent will be called and we do the actual + * scrolling there. + */ + + /* + * Shortcut the most recurring case: the user is in the dragging + * state and he is moving his finger. We want to intercept this + * motion. + */ + final int action = ev.getAction(); + if ((action == MotionEvent.ACTION_MOVE) && + (mTouchState == TOUCH_STATE_SCROLLING)) { + return true; + } + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_MOVE: { + /* + * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check + * whether the user has moved far enough from his original down touch. + */ + if (mActivePointerId != INVALID_POINTER) { + determineScrollingStart(ev); + break; + } + // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN + // event. in that case, treat the first occurence of a move event as a ACTION_DOWN + // i.e. fall through to the next case (don't break) + // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events + // while it's small- this was causing a crash before we checked for INVALID_POINTER) + } + + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + // Remember location of down touch + mDownMotionX = x; + mLastMotionX = x; + mLastMotionY = y; + mActivePointerId = ev.getPointerId(0); + mAllowLongPress = true; + + /* + * If being flinged and user touches the screen, initiate drag; + * otherwise don't. mScroller.isFinished should be false when + * being flinged. + */ + final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); + final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop); + if (finishedScrolling) { + mTouchState = TOUCH_STATE_REST; + mScroller.abortAnimation(); + } else { + mTouchState = TOUCH_STATE_SCROLLING; + } + + // check if this can be the beginning of a tap on the side of the pages + // to scroll the current page + if ((mTouchState != TOUCH_STATE_PREV_PAGE) && !handlePagingClicks() && + (mTouchState != TOUCH_STATE_NEXT_PAGE)) { + if (getChildCount() > 0) { + int width = getMeasuredWidth(); + int offset = getRelativeChildOffset(mCurrentPage); + if (x < offset - mPageSpacing) { + mTouchState = TOUCH_STATE_PREV_PAGE; + } else if (x > (width - offset + mPageSpacing)) { + mTouchState = TOUCH_STATE_NEXT_PAGE; + } + } + } + break; + } + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mTouchState = TOUCH_STATE_REST; + mAllowLongPress = false; + mActivePointerId = INVALID_POINTER; + break; + + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + break; + } + + /* + * The only time we want to intercept motion events is if we are in the + * drag mode. + */ + return mTouchState != TOUCH_STATE_REST; + } + + protected void animateClickFeedback(View v, final Runnable r) { + // animate the view slightly to show click feedback running some logic after it is "pressed" + Animation anim = AnimationUtils.loadAnimation(getContext(), + R.anim.paged_view_click_feedback); + anim.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + @Override + public void onAnimationRepeat(Animation animation) { + r.run(); + } + @Override + public void onAnimationEnd(Animation animation) {} + }); + v.startAnimation(anim); + } + + /* + * Determines if we should change the touch state to start scrolling after the + * user moves their touch point too far. + */ + protected void determineScrollingStart(MotionEvent ev) { + /* + * Locally do absolute value. mLastMotionX is set to the y value + * of the down event. + */ + final int pointerIndex = ev.findPointerIndex(mActivePointerId); + final float x = ev.getX(pointerIndex); + final float y = ev.getY(pointerIndex); + final int xDiff = (int) Math.abs(x - mLastMotionX); + final int yDiff = (int) Math.abs(y - mLastMotionY); + + final int touchSlop = mTouchSlop; + boolean xPaged = xDiff > mPagingTouchSlop; + boolean xMoved = xDiff > touchSlop; + boolean yMoved = yDiff > touchSlop; + + if (xMoved || yMoved) { + if (mUsePagingTouchSlop ? xPaged : xMoved) { + // Scroll if the user moved far enough along the X axis + mTouchState = TOUCH_STATE_SCROLLING; + mLastMotionX = x; + mTouchX = mScrollX; + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + pageBeginMoving(); + } + // Either way, cancel any pending longpress + if (mAllowLongPress) { + mAllowLongPress = false; + // Try canceling the long press. It could also have been scheduled + // by a distant descendant, so use the mAllowLongPress flag to block + // everything + final View currentPage = getPageAt(mCurrentPage); + if (currentPage != null) { + currentPage.cancelLongPress(); + } + } + } + } + + protected boolean handlePagingClicks() { + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + acquireVelocityTrackerAndAddMovement(ev); + + final int action = ev.getAction(); + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + /* + * If being flinged and user touches, stop the fling. isFinished + * will be false if being flinged. + */ + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + + // Remember where the motion event started + mDownMotionX = mLastMotionX = ev.getX(); + mActivePointerId = ev.getPointerId(0); + if (mTouchState == TOUCH_STATE_SCROLLING) { + pageBeginMoving(); + } + break; + + case MotionEvent.ACTION_MOVE: + if (mTouchState == TOUCH_STATE_SCROLLING) { + // Scroll to follow the motion event + final int pointerIndex = ev.findPointerIndex(mActivePointerId); + final float x = ev.getX(pointerIndex); + final int deltaX = (int) (mLastMotionX - x); + mLastMotionX = x; + + int sx = getScrollX(); + if (deltaX < 0) { + if (sx > 0) { + mTouchX += Math.max(-mTouchX, deltaX); + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + if (!mDeferScrollUpdate) { + scrollBy(Math.max(-sx, deltaX), 0); + } else { + // This will trigger a call to computeScroll() on next drawChild() call + invalidate(); + } + } + } else if (deltaX > 0) { + final int lastChildIndex = getChildCount() - 1; + final int availableToScroll = getChildOffset(lastChildIndex) - + getRelativeChildOffset(lastChildIndex) - sx; + if (availableToScroll > 0) { + mTouchX += Math.min(availableToScroll, deltaX); + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + if (!mDeferScrollUpdate) { + scrollBy(Math.min(availableToScroll, deltaX), 0); + } else { + // This will trigger a call to computeScroll() on next drawChild() call + invalidate(); + } + } + } else { + awakenScrollBars(); + } + } else { + determineScrollingStart(ev); + } + break; + + case MotionEvent.ACTION_UP: + if (mTouchState == TOUCH_STATE_SCROLLING) { + final int activePointerId = mActivePointerId; + final int pointerIndex = ev.findPointerIndex(activePointerId); + final float x = ev.getX(pointerIndex); + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int velocityX = (int) velocityTracker.getXVelocity(activePointerId); + final int deltaX = (int) (x - mDownMotionX); + boolean isfling = Math.abs(deltaX) > MIN_LENGTH_FOR_FLING; + boolean isSignificantMove = Math.abs(deltaX) > MIN_LENGTH_FOR_MOVE; + + final int snapVelocity = mSnapVelocity; + if ((isSignificantMove && deltaX > 0 || + (isfling && velocityX > snapVelocity)) && + mCurrentPage > 0) { + snapToPageWithVelocity(mCurrentPage - 1, velocityX); + } else if ((isSignificantMove && deltaX < 0 || + (isfling && velocityX < -snapVelocity)) && + mCurrentPage < getChildCount() - 1) { + snapToPageWithVelocity(mCurrentPage + 1, velocityX); + } else { + snapToDestination(); + } + } else if (mTouchState == TOUCH_STATE_PREV_PAGE && !handlePagingClicks()) { + // at this point we have not moved beyond the touch slop + // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so + // we can just page + int nextPage = Math.max(0, mCurrentPage - 1); + if (nextPage != mCurrentPage) { + snapToPage(nextPage); + } else { + snapToDestination(); + } + } else if (mTouchState == TOUCH_STATE_NEXT_PAGE && !handlePagingClicks()) { + // at this point we have not moved beyond the touch slop + // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so + // we can just page + int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1); + if (nextPage != mCurrentPage) { + snapToPage(nextPage); + } else { + snapToDestination(); + } + } + mTouchState = TOUCH_STATE_REST; + mActivePointerId = INVALID_POINTER; + releaseVelocityTracker(); + break; + + case MotionEvent.ACTION_CANCEL: + if (mTouchState == TOUCH_STATE_SCROLLING) { + snapToDestination(); + } + mTouchState = TOUCH_STATE_REST; + mActivePointerId = INVALID_POINTER; + releaseVelocityTracker(); + break; + + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + break; + } + + return true; + } + + private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + } + + private void releaseVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> + MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + // TODO: Make this decision more intelligent. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionX = mDownMotionX = ev.getX(newPointerIndex); + mLastMotionY = ev.getY(newPointerIndex); + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + @Override + public void requestChildFocus(View child, View focused) { + super.requestChildFocus(child, focused); + int page = indexOfChild(child); + if (page >= 0 && !isInTouchMode()) { + snapToPage(page); + } + } + + protected int getChildIndexForRelativeOffset(int relativeOffset) { + final int childCount = getChildCount(); + int left; + int right; + for (int i = 0; i < childCount; ++i) { + left = getRelativeChildOffset(i); + right = (left + getChildAt(i).getMeasuredWidth()); + if (left <= relativeOffset && relativeOffset <= right) { + return i; + } + } + return -1; + } + + protected int getRelativeChildOffset(int index) { + return (getMeasuredWidth() - getChildAt(index).getMeasuredWidth()) / 2; + } + + protected int getChildOffset(int index) { + if (getChildCount() == 0) + return 0; + + int offset = getRelativeChildOffset(0); + for (int i = 0; i < index; ++i) { + offset += getChildAt(i).getMeasuredWidth() + mPageSpacing; + } + return offset; + } + + int getPageNearestToCenterOfScreen() { + int minDistanceFromScreenCenter = getMeasuredWidth(); + int minDistanceFromScreenCenterIndex = -1; + int screenCenter = mScrollX + (getMeasuredWidth() / 2); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; ++i) { + View layout = (View) getChildAt(i); + int childWidth = layout.getMeasuredWidth(); + int halfChildWidth = (childWidth / 2); + int childCenter = getChildOffset(i) + halfChildWidth; + int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); + if (distanceFromScreenCenter < minDistanceFromScreenCenter) { + minDistanceFromScreenCenter = distanceFromScreenCenter; + minDistanceFromScreenCenterIndex = i; + } + } + return minDistanceFromScreenCenterIndex; + } + + protected void snapToDestination() { + snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION); + } + + protected void snapToPageWithVelocity(int whichPage, int velocity) { + // We ignore velocity in this implementation, but children (e.g. SmoothPagedView) + // can use it + snapToPage(whichPage); + } + + protected void snapToPage(int whichPage) { + snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); + } + + protected void snapToPage(int whichPage, int duration) { + whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1)); + + int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); + final int sX = getScrollX(); + final int delta = newX - sX; + snapToPage(whichPage, delta, duration); + } + + protected void snapToPage(int whichPage, int delta, int duration) { + mNextPage = whichPage; + + View focusedChild = getFocusedChild(); + if (focusedChild != null && whichPage != mCurrentPage && + focusedChild == getChildAt(mCurrentPage)) { + focusedChild.clearFocus(); + } + + pageBeginMoving(); + awakenScrollBars(duration); + if (duration == 0) { + duration = Math.abs(delta); + } + + if (!mScroller.isFinished()) mScroller.abortAnimation(); + mScroller.startScroll(getScrollX(), 0, delta, 0, duration); + + // only load some associated pages + loadAssociatedPages(mNextPage); + notifyPageSwitchListener(); + invalidate(); + } + + @Override + protected Parcelable onSaveInstanceState() { + final SavedState state = new SavedState(super.onSaveInstanceState()); + state.currentPage = mCurrentPage; + return state; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState) state; + super.onRestoreInstanceState(savedState.getSuperState()); + if (savedState.currentPage != -1) { + mCurrentPage = savedState.currentPage; + } + } + + public void scrollLeft() { + if (mScroller.isFinished()) { + if (mCurrentPage > 0) snapToPage(mCurrentPage - 1); + } else { + if (mNextPage > 0) snapToPage(mNextPage - 1); + } + } + + public void scrollRight() { + if (mScroller.isFinished()) { + if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1); + } else { + if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1); + } + } + + public int getPageForView(View v) { + int result = -1; + if (v != null) { + ViewParent vp = v.getParent(); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + if (vp == getChildAt(i)) { + return i; + } + } + } + return result; + } + + /** + * @return True is long presses are still allowed for the current touch + */ + public boolean allowLongPress() { + return mAllowLongPress; + } + + /** + * Set true to allow long-press events to be triggered, usually checked by + * {@link Launcher} to accept or block dpad-initiated long-presses. + */ + public void setAllowLongPress(boolean allowLongPress) { + mAllowLongPress = allowLongPress; + } + + public static class SavedState extends BaseSavedState { + int currentPage = -1; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + currentPage = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(currentPage); + } + + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + public void loadAssociatedPages(int page) { + if (mContentIsRefreshable) { + final int count = getChildCount(); + if (page < count) { + int lowerPageBound = getAssociatedLowerPageBound(page); + int upperPageBound = getAssociatedUpperPageBound(page); + for (int i = 0; i < count; ++i) { + final ViewGroup layout = (ViewGroup) getChildAt(i); + final int childCount = layout.getChildCount(); + if (lowerPageBound <= i && i <= upperPageBound) { + if (mDirtyPageContent.get(i)) { + syncPageItems(i); + mDirtyPageContent.set(i, false); + } + } else { + if (childCount > 0) { + layout.removeAllViews(); + } + mDirtyPageContent.set(i, true); + } + } + } + } + } + + protected int getAssociatedLowerPageBound(int page) { + return Math.max(0, page - 1); + } + protected int getAssociatedUpperPageBound(int page) { + final int count = getChildCount(); + return Math.min(page + 1, count - 1); + } + + protected void startChoiceMode(int mode, ActionMode.Callback callback) { + if (isChoiceMode(CHOICE_MODE_NONE)) { + mChoiceMode = mode; + mActionMode = startActionMode(callback); + } + } + + public void endChoiceMode() { + if (!isChoiceMode(CHOICE_MODE_NONE)) { + mChoiceMode = CHOICE_MODE_NONE; + resetCheckedGrandchildren(); + if (mActionMode != null) mActionMode.finish(); + mActionMode = null; + } + } + + protected boolean isChoiceMode(int mode) { + return mChoiceMode == mode; + } + + protected ArrayList<Checkable> getCheckedGrandchildren() { + ArrayList<Checkable> checked = new ArrayList<Checkable>(); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; ++i) { + final ViewGroup layout = (ViewGroup) getChildAt(i); + final int grandChildCount = layout.getChildCount(); + for (int j = 0; j < grandChildCount; ++j) { + final View v = layout.getChildAt(j); + if (v instanceof Checkable && ((Checkable) v).isChecked()) { + checked.add((Checkable) v); + } + } + } + return checked; + } + + /** + * If in CHOICE_MODE_SINGLE and an item is checked, returns that item. + * Otherwise, returns null. + */ + protected Checkable getSingleCheckedGrandchild() { + if (mChoiceMode == CHOICE_MODE_SINGLE) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; ++i) { + final ViewGroup layout = (ViewGroup) getChildAt(i); + final int grandChildCount = layout.getChildCount(); + for (int j = 0; j < grandChildCount; ++j) { + final View v = layout.getChildAt(j); + if (v instanceof Checkable && ((Checkable) v).isChecked()) { + return (Checkable) v; + } + } + } + } + return null; + } + + public Object getChosenItem() { + View checkedView = (View) getSingleCheckedGrandchild(); + if (checkedView != null) { + return checkedView.getTag(); + } + return null; + } + + protected void resetCheckedGrandchildren() { + // loop through children, and set all of their children to _not_ be checked + final ArrayList<Checkable> checked = getCheckedGrandchildren(); + for (int i = 0; i < checked.size(); ++i) { + final Checkable c = checked.get(i); + c.setChecked(false); + } + } + + /** + * This method is called ONLY to synchronize the number of pages that the paged view has. + * To actually fill the pages with information, implement syncPageItems() below. It is + * guaranteed that syncPageItems() will be called for a particular page before it is shown, + * and therefore, individual page items do not need to be updated in this method. + */ + public abstract void syncPages(); + + /** + * This method is called to synchronize the items that are on a particular page. If views on + * the page can be reused, then they should be updated within this method. + */ + public abstract void syncPageItems(int page); + + public void invalidatePageData() { + if (mContentIsRefreshable) { + // Update all the pages + syncPages(); + + // Mark each of the pages as dirty + final int count = getChildCount(); + mDirtyPageContent.clear(); + for (int i = 0; i < count; ++i) { + mDirtyPageContent.add(true); + } + + // Load any pages that are necessary for the current window of views + loadAssociatedPages(mCurrentPage); + mDirtyPageAlpha = true; + updateAdjacentPagesAlpha(); + requestLayout(); + } + } +} diff --git a/src/com/android/launcher2/PagedViewCellLayout.java b/src/com/android/launcher2/PagedViewCellLayout.java new file mode 100644 index 0000000..b779a97 --- /dev/null +++ b/src/com/android/launcher2/PagedViewCellLayout.java @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; + +/** + * An abstraction of the original CellLayout which supports laying out items + * which span multiple cells into a grid-like layout. Also supports dimming + * to give a preview of its contents. + */ +public class PagedViewCellLayout extends ViewGroup { + static final String TAG = "PagedViewCellLayout"; + + private float mHolographicAlpha; + + private boolean mCenterContent; + + private int mCellCountX; + private int mCellCountY; + private int mCellWidth; + private int mCellHeight; + private int mWidthGap; + private int mHeightGap; + private static int sDefaultCellDimensions = 96; + + public PagedViewCellLayout(Context context) { + this(context, null); + } + + public PagedViewCellLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + setAlwaysDrawnWithCacheEnabled(false); + + // setup default cell parameters + mCellWidth = mCellHeight = sDefaultCellDimensions; + mCellCountX = LauncherModel.getCellCountX(); + mCellCountY = LauncherModel.getCellCountY(); + mHolographicAlpha = 0.0f; + mWidthGap = mHeightGap = -1; + } + + @Override + protected boolean onSetAlpha(int alpha) { + return true; + } + + @Override + public void setAlpha(float alpha) { + mHolographicAlpha = 1.0f - alpha; + setChildrenAlpha(alpha); + super.setAlpha(alpha); + } + + @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(); + } + } + + public boolean addViewToCellLayout(View child, int index, int childId, + PagedViewCellLayout.LayoutParams params) { + final PagedViewCellLayout.LayoutParams lp = params; + + // Generate an id for each view, this assumes we have at most 256x256 cells + // per workspace screen + if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) && + lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) { + // If the horizontal or vertical span is set to -1, it is taken to + // mean that it spans the extent of the CellLayout + if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX; + if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY; + + child.setId(childId); + + // We might be in the middle or end of shrinking/fading to a dimmed view + // Make sure this view's alpha is set the same as all the rest of the views + child.setAlpha(1.0f - mHolographicAlpha); + + addView(child, index, lp); + return true; + } + return false; + } + + @Override + public void requestChildFocus(View child, View focused) { + super.requestChildFocus(child, focused); + if (child != null) { + Rect r = new Rect(); + child.getDrawingRect(r); + requestRectangleOnScreen(r); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // TODO: currently ignoring padding + + int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + + int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { + throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); + } + + final int cellWidth = mCellWidth; + final int cellHeight = mCellHeight; + + int numWidthGaps = mCellCountX - 1; + int numHeightGaps = mCellCountY - 1; + + int vSpaceLeft = heightSpecSize - mPaddingTop + - mPaddingBottom - (cellHeight * mCellCountY); + int heightGap = vSpaceLeft / numHeightGaps; + + int hSpaceLeft = widthSpecSize - mPaddingLeft + - mPaddingRight - (cellWidth * mCellCountX); + int widthGap = hSpaceLeft / numWidthGaps; + + // center it around the min gaps + int minGap = Math.min(widthGap, heightGap); + int paddingLeft = mPaddingLeft; + int paddingTop = mPaddingTop; + /* + if (minGap < heightGap) { + // vertical space has shrunken, so change padding accordingly + paddingTop += ((heightGap - minGap) * (mCellCountY - 1)) / 2; + } else if (minGap < widthGap) { + // horizontal space has shrunken, so change padding accordingly + paddingLeft += ((widthGap - minGap) * (mCellCountX - 1)) / 2; + } + */ + if (mWidthGap > -1 && mHeightGap > -1) { + widthGap = mWidthGap; + heightGap = mHeightGap; + } else { + widthGap = heightGap = minGap; + } + + int newWidth = mPaddingLeft + mPaddingRight + (mCellCountX * cellWidth) + + ((mCellCountX - 1) * widthGap); + int newHeight = mPaddingTop + mPaddingBottom + (mCellCountY * cellHeight) + + ((mCellCountY - 1) * heightGap); + + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + PagedViewCellLayout.LayoutParams lp = + (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); + lp.setup(cellWidth, cellHeight, widthGap, heightGap, + paddingLeft, paddingTop); + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, + MeasureSpec.EXACTLY); + int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, + MeasureSpec.EXACTLY); + + child.measure(childWidthMeasureSpec, childheightMeasureSpec); + } + + setMeasuredDimension(newWidth, newHeight); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int count = getChildCount(); + + int offsetX = 0; + if (mCenterContent) { + // determine the max width of all the rows and center accordingly + int maxRowWidth = 0; + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + PagedViewCellLayout.LayoutParams lp = + (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); + maxRowWidth = Math.max(maxRowWidth, lp.x + lp.width); + } + } + offsetX = (getMeasuredWidth() / 2) - (maxRowWidth / 2); + } + + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + PagedViewCellLayout.LayoutParams lp = + (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); + + int childLeft = offsetX + lp.x; + int childTop = lp.y; + child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height); + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return super.onTouchEvent(event) || true; + } + + public void enableCenteredContent(boolean enabled) { + mCenterContent = enabled; + } + + @Override + protected void setChildrenDrawingCacheEnabled(boolean enabled) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View view = getChildAt(i); + view.setDrawingCacheEnabled(enabled); + // Update the drawing caches + if (!view.isHardwareAccelerated()) { + view.buildDrawingCache(true); + } + } + } + + public void setCellCount(int xCount, int yCount) { + mCellCountX = xCount; + mCellCountY = yCount; + requestLayout(); + } + + public void setGap(int widthGap, int heightGap) { + mWidthGap = widthGap; + mHeightGap = heightGap; + } + + public void setCellDimensions(int width, int height) { + mCellWidth = width; + mCellHeight = height; + requestLayout(); + } + + public int getDefaultCellDimensions() { + return sDefaultCellDimensions; + } + + private void setChildrenAlpha(float alpha) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + getChildAt(i).setAlpha(alpha); + } + } + + public int[] getCellCountForDimensions(int width, int height) { + // Always assume we're working with the smallest span to make sure we + // reserve enough space in both orientations + int smallerSize = Math.min(mCellWidth, mCellHeight); + + // Always round up to next largest cell + int spanX = (width + smallerSize) / smallerSize; + int spanY = (height + smallerSize) / smallerSize; + + return new int[] { spanX, spanY }; + } + + /** + * Start dragging the specified child + * + * @param child The child that is being dragged + */ + void onDragChild(View child) { + PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); + lp.isDragging = true; + } + + /** + * Estimates the number of cells that the specified width would take up. + */ + public int estimateCellHSpan(int width) { + // TODO: we need to take widthGap into effect + return (width + mCellWidth) / mCellWidth; + } + + /** + * Estimates the number of cells that the specified height would take up. + */ + public int estimateCellVSpan(int height) { + // TODO: we need to take heightGap into effect + return (height + mCellHeight) / mCellHeight; + } + + /** + * Estimates the width that the number of vSpan cells will take up. + */ + public int estimateCellWidth(int hSpan) { + // TODO: we need to take widthGap into effect + return hSpan * mCellWidth; + } + + /** + * Estimates the height that the number of vSpan cells will take up. + */ + public int estimateCellHeight(int vSpan) { + // TODO: we need to take heightGap into effect + return vSpan * mCellHeight; + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new PagedViewCellLayout.LayoutParams(getContext(), attrs); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof PagedViewCellLayout.LayoutParams; + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new PagedViewCellLayout.LayoutParams(p); + } + + public static class LayoutParams extends ViewGroup.MarginLayoutParams { + /** + * Horizontal location of the item in the grid. + */ + @ViewDebug.ExportedProperty + public int cellX; + + /** + * Vertical location of the item in the grid. + */ + @ViewDebug.ExportedProperty + public int cellY; + + /** + * Number of cells spanned horizontally by the item. + */ + @ViewDebug.ExportedProperty + public int cellHSpan; + + /** + * Number of cells spanned vertically by the item. + */ + @ViewDebug.ExportedProperty + public int cellVSpan; + + /** + * Is this item currently being dragged + */ + public boolean isDragging; + + // a data object that you can bind to this layout params + private Object mTag; + + // X coordinate of the view in the layout. + @ViewDebug.ExportedProperty + int x; + // Y coordinate of the view in the layout. + @ViewDebug.ExportedProperty + int y; + + public LayoutParams() { + super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + cellHSpan = 1; + cellVSpan = 1; + } + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + cellHSpan = 1; + cellVSpan = 1; + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + cellHSpan = 1; + cellVSpan = 1; + } + + public LayoutParams(LayoutParams source) { + super(source); + this.cellX = source.cellX; + this.cellY = source.cellY; + this.cellHSpan = source.cellHSpan; + this.cellVSpan = source.cellVSpan; + } + + public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { + super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + this.cellX = cellX; + this.cellY = cellY; + this.cellHSpan = cellHSpan; + this.cellVSpan = cellVSpan; + } + + public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap, + int hStartPadding, int vStartPadding) { + + final int myCellHSpan = cellHSpan; + final int myCellVSpan = cellVSpan; + final int myCellX = cellX; + final int myCellY = cellY; + + width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - + leftMargin - rightMargin; + height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - + topMargin - bottomMargin; + + x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; + y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; + } + + public Object getTag() { + return mTag; + } + + public void setTag(Object tag) { + mTag = tag; + } + + public String toString() { + return "(" + this.cellX + ", " + this.cellY + ", " + + this.cellHSpan + ", " + this.cellVSpan + ")"; + } + } +} diff --git a/src/com/android/launcher2/PagedViewIcon.java b/src/com/android/launcher2/PagedViewIcon.java new file mode 100644 index 0000000..89cf331 --- /dev/null +++ b/src/com/android/launcher2/PagedViewIcon.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import com.android.launcher.R; +import com.android.launcher2.PagedView.PagedViewIconCache; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.util.AttributeSet; +import android.widget.Checkable; +import android.widget.TextView; + + + +/** + * An icon on a PagedView, specifically for items in the launcher's paged view (with compound + * drawables on the top). + */ +public class PagedViewIcon extends TextView implements Checkable { + private static final String TAG = "PagedViewIcon"; + + // holographic outline + private final Paint mPaint = new Paint(); + private static HolographicOutlineHelper sHolographicOutlineHelper; + private Bitmap mCheckedOutline; + private Bitmap mHolographicOutline; + private Bitmap mIcon; + + private Object mIconCacheKey; + private PagedViewIconCache mIconCache; + + private int mAlpha; + private int mHolographicAlpha; + + private boolean mIsChecked; + + // Highlight colors + private int mHoloBlurColor; + private int mHoloOutlineColor; + private int mCheckedBlurColor; + private int mCheckedOutlineColor; + + private static final HandlerThread sWorkerThread = new HandlerThread("pagedviewicon-helper"); + static { + sWorkerThread.start(); + } + + private static final int MESSAGE_CREATE_HOLOGRAPHIC_OUTLINE = 1; + + private static final Handler sWorker = new Handler(sWorkerThread.getLooper()) { + private DeferredHandler mHandler = new DeferredHandler(); + private Paint mPaint = new Paint(); + public void handleMessage(Message msg) { + final PagedViewIcon icon = (PagedViewIcon) msg.obj; + + final Bitmap holographicOutline = Bitmap.createBitmap( + icon.mIcon.getWidth(), icon.mIcon.getHeight(), Bitmap.Config.ARGB_8888); + Canvas holographicOutlineCanvas = new Canvas(holographicOutline); + holographicOutlineCanvas.drawBitmap(icon.mIcon, 0, 0, mPaint); + + sHolographicOutlineHelper.applyExpensiveOutlineWithBlur(holographicOutline, + holographicOutlineCanvas, icon.mHoloBlurColor, icon.mHoloOutlineColor); + + mHandler.post(new Runnable() { + public void run() { + icon.mHolographicOutline = holographicOutline; + icon.mIconCache.addOutline(icon.mIconCacheKey, holographicOutline); + icon.invalidate(); + } + }); + } + }; + + public PagedViewIcon(Context context) { + this(context, null); + } + + public PagedViewIcon(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedViewIcon(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedViewIcon, defStyle, 0); + mHoloBlurColor = a.getColor(R.styleable.PagedViewIcon_blurColor, 0); + mHoloOutlineColor = a.getColor(R.styleable.PagedViewIcon_outlineColor, 0); + mCheckedBlurColor = a.getColor(R.styleable.PagedViewIcon_checkedBlurColor, 0); + mCheckedOutlineColor = a.getColor(R.styleable.PagedViewIcon_checkedOutlineColor, 0); + + a.recycle(); + + if (sHolographicOutlineHelper == null) { + sHolographicOutlineHelper = new HolographicOutlineHelper(); + } + + setFocusable(true); + setBackgroundDrawable(null); + } + + private void queueHolographicOutlineCreation() { + // Generate the outline in the background + if (mHolographicOutline == null) { + Message m = sWorker.obtainMessage(MESSAGE_CREATE_HOLOGRAPHIC_OUTLINE); + m.obj = this; + sWorker.sendMessage(m); + } + } + + public void applyFromApplicationInfo(ApplicationInfo info, PagedViewIconCache cache, + boolean scaleUp) { + mIconCache = cache; + mIconCacheKey = info; + mHolographicOutline = mIconCache.getOutline(mIconCacheKey); + + mIcon = info.iconBitmap; + setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null); + setText(info.title); + setTag(info); + + queueHolographicOutlineCreation(); + } + + public void applyFromResolveInfo(ResolveInfo info, PackageManager packageManager, + PagedViewIconCache cache, IconCache modelIconCache) { + mIconCache = cache; + mIconCacheKey = info; + mHolographicOutline = mIconCache.getOutline(mIconCacheKey); + + mIcon = Utilities.createIconBitmap( + modelIconCache.getFullResIcon(info, packageManager), mContext); + setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null); + setText(info.loadLabel(packageManager)); + setTag(info); + + queueHolographicOutlineCreation(); + } + + @Override + public void setAlpha(float alpha) { + final float viewAlpha = sHolographicOutlineHelper.viewAlphaInterpolator(alpha); + final float holographicAlpha = sHolographicOutlineHelper.highlightAlphaInterpolator(alpha); + mAlpha = (int) (viewAlpha * 255); + mHolographicAlpha = (int) (holographicAlpha * 255); + super.setAlpha(viewAlpha); + } + + public void invalidateCheckedImage() { + if (mCheckedOutline != null) { + mCheckedOutline.recycle(); + mCheckedOutline = null; + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (mAlpha > 0) { + super.onDraw(canvas); + } + + Bitmap overlay = null; + + // draw any blended overlays + if (mCheckedOutline == null) { + if (mHolographicOutline != null && mHolographicAlpha > 0) { + mPaint.setAlpha(mHolographicAlpha); + overlay = mHolographicOutline; + } + } else { + mPaint.setAlpha(255); + overlay = mCheckedOutline; + } + + if (overlay != null) { + final int compoundPaddingLeft = getCompoundPaddingLeft(); + final int compoundPaddingRight = getCompoundPaddingRight(); + int hspace = getWidth() - compoundPaddingRight - compoundPaddingLeft; + canvas.drawBitmap(overlay, + compoundPaddingLeft + (hspace - overlay.getWidth()) / 2, + mPaddingTop, + mPaint); + } + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + sWorker.removeMessages(MESSAGE_CREATE_HOLOGRAPHIC_OUTLINE, this); + } + + @Override + public boolean isChecked() { + return mIsChecked; + } + + @Override + public void setChecked(boolean checked) { + if (mIsChecked != checked) { + mIsChecked = checked; + + if (mIsChecked) { + mCheckedOutline = Bitmap.createBitmap(mIcon.getWidth(), mIcon.getHeight(), + Bitmap.Config.ARGB_8888); + Canvas checkedOutlineCanvas = new Canvas(mCheckedOutline); + mPaint.setAlpha(255); + checkedOutlineCanvas.drawBitmap(mIcon, 0, 0, mPaint); + + sHolographicOutlineHelper.applyExpensiveOutlineWithBlur(mCheckedOutline, + checkedOutlineCanvas, mCheckedBlurColor, mCheckedOutlineColor); + } else { + invalidateCheckedImage(); + } + + invalidate(); + } + } + + @Override + public void toggle() { + setChecked(!mIsChecked); + } +} diff --git a/src/com/android/launcher2/PagedViewWidgetIcon.java b/src/com/android/launcher2/PagedViewWidgetIcon.java new file mode 100644 index 0000000..f285dab --- /dev/null +++ b/src/com/android/launcher2/PagedViewWidgetIcon.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Checkable; +import android.widget.LinearLayout; + +import com.android.launcher.R; + +/** + * An widget icon for use specifically in the CustomizePagedView. In class form so that + * we can add logic for how it will look when checked/unchecked. + */ +public class PagedViewWidgetIcon extends LinearLayout implements Checkable { + private static final String TAG = "PagedViewIcon"; + + // Holographic outline + private final Paint mPaint = new Paint(); + private static HolographicOutlineHelper sHolographicOutlineHelper; + private final Paint mErasePaint = new Paint(); + private Bitmap mCheckedOutline; + private Canvas mHolographicOutlineCanvas; + private boolean mIsHolographicUpdatePass; + + private int mAlpha; + + private boolean mIsChecked; + + // Highlight colours + private int mCheckedBlurColor; + private int mCheckedOutlineColor; + + + public PagedViewWidgetIcon(Context context) { + this(context, null); + } + + public PagedViewWidgetIcon(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedViewWidgetIcon(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedViewWidgetIcon, + defStyle, 0); + mCheckedBlurColor = a.getColor(R.styleable.PagedViewWidgetIcon_checkedBlurColor, 0); + mCheckedOutlineColor = a.getColor(R.styleable.PagedViewWidgetIcon_checkedOutlineColor, 0); + mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + mErasePaint.setFilterBitmap(true); + a.recycle(); + + if (sHolographicOutlineHelper == null) { + sHolographicOutlineHelper = new HolographicOutlineHelper(); + } + + setWillNotDraw(false); + } + + public void invalidateCheckedImage() { + if (mCheckedOutline != null) { + mCheckedOutline.recycle(); + mCheckedOutline = null; + } + } + + @Override + protected void onDraw(Canvas canvas) { + // Draw the view itself + if (mIsHolographicUpdatePass) { + canvas.save(); + final float alpha = getAlpha(); + super.setAlpha(1.0f); + super.onDraw(canvas); + super.setAlpha(alpha); + canvas.restore(); + } else { + if (mAlpha > 0) { + super.onDraw(canvas); + } + } + + // Draw the holographic checked overlay if necessary + if (!mIsHolographicUpdatePass) { + if (mCheckedOutline != null) { + mPaint.setAlpha(255); + canvas.drawBitmap(mCheckedOutline, 0, 0, mPaint); + } + } + } + + @Override + public boolean isChecked() { + return mIsChecked; + } + + @Override + public void setChecked(boolean checked) { + if (mIsChecked != checked) { + mIsChecked = checked; + + if (mIsChecked) { + // set a flag to indicate that we are going to draw the view at full alpha + mIsHolographicUpdatePass = true; + final int width = getMeasuredWidth(); + final int height = getMeasuredHeight(); + mCheckedOutline = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mHolographicOutlineCanvas = new Canvas(mCheckedOutline); + mHolographicOutlineCanvas.concat(getMatrix()); + draw(mHolographicOutlineCanvas); + sHolographicOutlineHelper.applyExpensiveOutlineWithBlur(mCheckedOutline, + mHolographicOutlineCanvas, mCheckedBlurColor, mCheckedOutlineColor); + + // Unlike PagedViewIcon, we can't seem to properly set the clip rect for all the + // children to respect when drawing... so for now, we erase over those parts in the + // checked highlight image + mHolographicOutlineCanvas.drawRect(0, findViewById(R.id.divider).getTop(), + width, height, mErasePaint); + + mIsHolographicUpdatePass = false; + mHolographicOutlineCanvas = null; + } else { + invalidateCheckedImage(); + } + + invalidate(); + } + } + + @Override + public void toggle() { + setChecked(!mIsChecked); + } +} diff --git a/src/com/android/launcher2/PagedViewWidgetLayout.java b/src/com/android/launcher2/PagedViewWidgetLayout.java new file mode 100644 index 0000000..4666873 --- /dev/null +++ b/src/com/android/launcher2/PagedViewWidgetLayout.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.LinearLayout; + +/** + * The linear layout used strictly for the widget tab of the customization tray + */ +public class PagedViewWidgetLayout extends LinearLayout { + static final String TAG = "PagedViewWidgetLayout"; + + public PagedViewWidgetLayout(Context context) { + this(context, null); + } + + public PagedViewWidgetLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedViewWidgetLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // We eat up the touch events here, since the PagedView (which uses the same swiping + // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when + // the user is scrolling between pages. This means that if the pages themselves don't + // handle touch events, it gets forwarded up to PagedView itself, and it's own + // onTouchEvent() handling will prevent further intercept touch events from being called + // (it's the same view in that case). This is not ideal, but to prevent more changes, + // we just always mark the touch event as handled. + return super.onTouchEvent(event) || true; + } + + @Override + protected boolean onSetAlpha(int alpha) { + return true; + } + + @Override + public void setAlpha(float alpha) { + setChildrenAlpha(alpha); + super.setAlpha(alpha); + } + + private void setChildrenAlpha(float alpha) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + getChildAt(i).setAlpha(alpha); + } + } +} diff --git a/src/com/android/launcher2/PendingAddItemInfo.java b/src/com/android/launcher2/PendingAddItemInfo.java new file mode 100644 index 0000000..7b564e0 --- /dev/null +++ b/src/com/android/launcher2/PendingAddItemInfo.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.os.Parcelable; + +/** + * We pass this object with a drag from the customization tray + */ +class PendingAddItemInfo extends ItemInfo { + /** + * The component that will be created. + */ + ComponentName componentName; +} + +class PendingAddWidgetInfo extends PendingAddItemInfo { + int minWidth; + int minHeight; + + // Any configuration data that we want to pass to a configuration activity when + // starting up a widget + String mimeType; + Parcelable configurationData; + + public PendingAddWidgetInfo(AppWidgetProviderInfo i, String dataMimeType, Parcelable data) { + itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; + componentName = i.provider; + minWidth = i.minWidth; + minHeight = i.minHeight; + if (dataMimeType != null && data != null) { + mimeType = dataMimeType; + configurationData = data; + } + } +}
\ No newline at end of file diff --git a/src/com/android/launcher2/ShortcutInfo.java b/src/com/android/launcher2/ShortcutInfo.java index 2c5aec4..72f2d51 100644 --- a/src/com/android/launcher2/ShortcutInfo.java +++ b/src/com/android/launcher2/ShortcutInfo.java @@ -16,16 +16,14 @@ package com.android.launcher2; +import java.util.ArrayList; + import android.content.ComponentName; import android.content.ContentValues; -import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; import android.util.Log; -import java.util.ArrayList; - /** * Represents a launchable icon on the workspaces and in folders. */ diff --git a/src/com/android/launcher2/ShortcutsAdapter.java b/src/com/android/launcher2/ShortcutsAdapter.java index 19c3af0..93c500a 100644 --- a/src/com/android/launcher2/ShortcutsAdapter.java +++ b/src/com/android/launcher2/ShortcutsAdapter.java @@ -16,16 +16,15 @@ package com.android.launcher2; +import java.util.ArrayList; + import android.content.Context; -import android.content.pm.PackageManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; -import java.util.ArrayList; - import com.android.launcher.R; /** diff --git a/src/com/android/launcher2/SmoothPagedView.java b/src/com/android/launcher2/SmoothPagedView.java new file mode 100644 index 0000000..56037ff --- /dev/null +++ b/src/com/android/launcher2/SmoothPagedView.java @@ -0,0 +1,193 @@ +/* + * 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.launcher2; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.animation.Interpolator; +import android.widget.Scroller; + + +public abstract class SmoothPagedView extends PagedView { + private static final float SMOOTHING_SPEED = 0.75f; + private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED)); + + private float mBaseLineFlingVelocity; + private float mFlingVelocityInfluence; + + static final int OVERSHOOT_MODE = 0; + static final int QUINTIC_MODE = 1; + + int mScrollMode; + + private Interpolator mScrollInterpolator; + + private static class WorkspaceOvershootInterpolator implements Interpolator { + private static final float DEFAULT_TENSION = 1.3f; + private float mTension; + + public WorkspaceOvershootInterpolator() { + mTension = DEFAULT_TENSION; + } + + public void setDistance(int distance) { + mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION; + } + + public void disableSettle() { + mTension = 0.f; + } + + public float getInterpolation(float t) { + // _o(t) = t * t * ((tension + 1) * t + tension) + // o(t) = _o(t - 1) + 1 + t -= 1.0f; + return t * t * ((mTension + 1) * t + mTension) + 1.0f; + } + } + + private static class QuinticInterpolator implements Interpolator { + public QuinticInterpolator() { + } + + public float getInterpolation(float t) { + t -= 1.0f; + return t*t*t*t*t + 1; + } + } + + /** + * Used to inflate the Workspace from XML. + * + * @param context The application's context. + * @param attrs The attributes set containing the Workspace's customization values. + */ + public SmoothPagedView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Used to inflate the Workspace from XML. + * + * @param context The application's context. + * @param attrs The attributes set containing the Workspace's customization values. + * @param defStyle Unused. + */ + public SmoothPagedView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + mUsePagingTouchSlop = false; + + // This means that we'll take care of updating the scroll parameter ourselves (we do it + // in computeScroll) + mDeferScrollUpdate = true; + } + + protected int getScrollMode() { + return OVERSHOOT_MODE; + } + + /** + * Initializes various states for this workspace. + */ + @Override + protected void init() { + super.init(); + + mScrollMode = getScrollMode(); + if (mScrollMode == QUINTIC_MODE) { + mBaseLineFlingVelocity = 700.0f; + mFlingVelocityInfluence = 0.8f; + mScrollInterpolator = new QuinticInterpolator(); + } else { // QUINTIC_MODE + mBaseLineFlingVelocity = 2500.0f; + mFlingVelocityInfluence = 0.4f; + mScrollInterpolator = new WorkspaceOvershootInterpolator(); + } + mScroller = new Scroller(getContext(), mScrollInterpolator); + } + + @Override + protected void snapToDestination() { + snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0); + } + + @Override + protected void snapToPageWithVelocity(int whichPage, int velocity) { + snapToPageWithVelocity(whichPage, 0, true); + } + + void snapToPageWithVelocity(int whichPage, int velocity, boolean settle) { + // if (!mScroller.isFinished()) return; + + whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1)); + + final int screenDelta = Math.max(1, Math.abs(whichPage - mCurrentPage)); + final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); + final int delta = newX - mScrollX; + int duration; + if (mScrollMode == OVERSHOOT_MODE) { + duration = (screenDelta + 1) * 100; + } else { // QUINTIC_MODE + duration = Math.round(Math.abs(delta) * 0.6f); + } + + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + + if (mScrollMode == OVERSHOOT_MODE) { + if (settle) { + ((WorkspaceOvershootInterpolator) mScrollInterpolator).setDistance(screenDelta); + } else { + ((WorkspaceOvershootInterpolator) mScrollInterpolator).disableSettle(); + } + } + + velocity = Math.abs(velocity); + if (velocity > 0) { + duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence; + } else { + duration += 100; + } + + snapToPage(whichPage, delta, duration); + } + + @Override + protected void snapToPage(int whichPage) { + snapToPageWithVelocity(whichPage, 0, false); + } + + @Override + public void computeScroll() { + boolean scrollComputed = computeScrollHelper(); + + if (!scrollComputed && mTouchState == TOUCH_STATE_SCROLLING) { + final float now = System.nanoTime() / NANOTIME_DIV; + final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT); + final float dx = mTouchX - mScrollX; + mScrollX += dx * e; + mSmoothingTime = now; + + // Keep generating points as long as we're more than 1px away from the target + if (dx > 1.f || dx < -1.f) { + invalidate(); + } + } + } +} diff --git a/src/com/android/launcher2/SymmetricalLinearTween.java b/src/com/android/launcher2/SymmetricalLinearTween.java index 2e0ed8f..da02242 100644 --- a/src/com/android/launcher2/SymmetricalLinearTween.java +++ b/src/com/android/launcher2/SymmetricalLinearTween.java @@ -17,9 +17,7 @@ package com.android.launcher2; import android.os.Handler; -import android.os.Message; import android.os.SystemClock; -import android.util.Log; /** * Provides an animation between 0.0f and 1.0f over a given duration. diff --git a/src/com/android/launcher2/UserFolder.java b/src/com/android/launcher2/UserFolder.java index d49c27a..d6799f7 100644 --- a/src/com/android/launcher2/UserFolder.java +++ b/src/com/android/launcher2/UserFolder.java @@ -3,10 +3,8 @@ package com.android.launcher2; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import android.widget.ArrayAdapter; import com.android.launcher.R; @@ -40,11 +38,6 @@ public class UserFolder extends Folder implements DropTarget { itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && item.container != mInfo.id; } - - public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, - DragView dragView, Object dragInfo, Rect recycle) { - return null; - } public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { @@ -79,6 +72,10 @@ public class UserFolder extends Folder implements DropTarget { } } + public boolean isDropEnabled() { + return true; + } + void bind(FolderInfo info) { super.bind(info); setContentAdapter(new ShortcutsAdapter(mContext, ((UserFolderInfo) info).contents)); @@ -91,4 +88,10 @@ public class UserFolder extends Folder implements DropTarget { super.onOpen(); requestFocus(); } + + @Override + public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, + DragView dragView, Object dragInfo) { + return null; + } } diff --git a/src/com/android/launcher2/Utilities.java b/src/com/android/launcher2/Utilities.java index 757e48e..03a2a52 100644 --- a/src/com/android/launcher2/Utilities.java +++ b/src/com/android/launcher2/Utilities.java @@ -16,9 +16,8 @@ package com.android.launcher2; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.PaintDrawable; +import android.content.Context; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; @@ -26,19 +25,19 @@ import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; -import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.TableMaskFilter; import android.graphics.Typeface; -import android.text.Layout.Alignment; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PaintDrawable; import android.text.StaticLayout; import android.text.TextPaint; +import android.text.Layout.Alignment; import android.util.DisplayMetrics; import android.util.Log; -import android.content.res.Resources; -import android.content.Context; import com.android.launcher.R; @@ -238,7 +237,7 @@ final class Utilities { final DisplayMetrics metrics = resources.getDisplayMetrics(); final float density = metrics.density; - sIconWidth = sIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size); + sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size); sIconTextureWidth = sIconTextureHeight = sIconWidth + 2; sBlurPaint.setMaskFilter(new BlurMaskFilter(5 * density, BlurMaskFilter.Blur.NORMAL)); diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java index c182209..df3d2de 100644 --- a/src/com/android/launcher2/Workspace.java +++ b/src/com/android/launcher2/Workspace.java @@ -18,147 +18,163 @@ package com.android.launcher2; import java.util.ArrayList; import java.util.HashSet; - +import java.util.List; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.app.AlertDialog; import android.app.WallpaperManager; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; +import android.content.ClipData; +import android.content.ClipDescription; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; +import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region.Op; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.IBinder; -import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; +import android.view.DragEvent; import android.view.MotionEvent; -import android.view.VelocityTracker; import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.animation.Interpolator; -import android.widget.Scroller; +import android.view.animation.DecelerateInterpolator; import android.widget.TextView; +import android.widget.Toast; import com.android.launcher.R; +import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData; /** - * The workspace is a wide area with a wallpaper and a finite number of screens. Each - * screen contains a number of icons, folders or widgets the user can interact with. - * A workspace is meant to be used with a fixed width only. + * The workspace is a wide area with a wallpaper and a finite number of pages. + * Each page contains a number of icons, folders or widgets the user can + * interact with. A workspace is meant to be used with a fixed width only. */ -public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller { +public class Workspace extends SmoothPagedView + implements DropTarget, DragSource, DragScroller, View.OnTouchListener { @SuppressWarnings({"UnusedDeclaration"}) private static final String TAG = "Launcher.Workspace"; - private static final int INVALID_SCREEN = -1; - - /** - * The velocity at which a fling gesture will cause us to snap to the next screen - */ - private static final int SNAP_VELOCITY = 600; + + // This is how much the workspace shrinks when we enter all apps or + // customization mode + private static final float SHRINK_FACTOR = 0.16f; + + // Y rotation to apply to the workspace screens + private static final float WORKSPACE_ROTATION = 12.5f; + + // These are extra scale factors to apply to the mini home screens + // so as to achieve the desired transform + private static final float EXTRA_SCALE_FACTOR_0 = 0.972f; + private static final float EXTRA_SCALE_FACTOR_1 = 1.0f; + private static final float EXTRA_SCALE_FACTOR_2 = 1.10f; + + private static final int BACKGROUND_FADE_OUT_DELAY = 300; + private static final int BACKGROUND_FADE_OUT_DURATION = 300; + private static final int BACKGROUND_FADE_IN_DURATION = 100; + + // These animators are used to fade the background + private ObjectAnimator mBackgroundFadeInAnimation; + private ObjectAnimator mBackgroundFadeOutAnimation; + private float mBackgroundAlpha = 0; private final WallpaperManager mWallpaperManager; - - private int mDefaultScreen; - private boolean mFirstLayout = true; + private int mDefaultPage; - private int mCurrentScreen; - private int mNextScreen = INVALID_SCREEN; - private Scroller mScroller; - private VelocityTracker mVelocityTracker; + private boolean mPageMoving = false; /** * 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; - - private final static int TOUCH_STATE_REST = 0; - private final static int TOUCH_STATE_SCROLLING = 1; - - private int mTouchState = TOUCH_STATE_REST; - - private OnLongClickListener mLongClickListener; + /** + * The CellLayout that is currently being dragged over + */ + private CellLayout mDragTargetLayout = null; private Launcher mLauncher; private IconCache mIconCache; private DragController mDragController; - - /** - * Cache of vacant cells, used during drag events and invalidated as needed. - */ - private CellLayout.CellInfo mVacantCache = null; - + + // These are temporary variables to prevent having to allocate a new object just to + // return an (x, y) value from helper functions. Do NOT use them to maintain other state. private int[] mTempCell = new int[2]; private int[] mTempEstimate = new int[2]; + private float[] mTempOriginXY = new float[2]; + private float[] mTempDragCoordinates = new float[2]; + private float[] mTempTouchCoordinates = new float[2]; + private float[] mTempCellLayoutCenterCoordinates = new float[2]; + private float[] mTempDragBottomRightCoordinates = new float[2]; + private Matrix mTempInverseMatrix = new Matrix(); - private boolean mAllowLongPress = true; - - private int mTouchSlop; - private int mMaximumVelocity; - - private static final int INVALID_POINTER = -1; + private static final int DEFAULT_CELL_COUNT_X = 4; + private static final int DEFAULT_CELL_COUNT_Y = 4; - private int mActivePointerId = INVALID_POINTER; - private Drawable mPreviousIndicator; private Drawable mNextIndicator; - - private static final float NANOTIME_DIV = 1000000000.0f; - private static final float SMOOTHING_SPEED = 0.75f; - private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED)); - private float mSmoothingTime; - private float mTouchX; - private WorkspaceOvershootInterpolator mScrollInterpolator; + // State variable that indicates whether the pages are small (ie when you're + // in all apps or customize mode) + private boolean mIsSmall = false; + private boolean mIsInUnshrinkAnimation = false; + private AnimatorListener mUnshrinkAnimationListener; + private enum ShrinkPosition { + SHRINK_TO_TOP, SHRINK_TO_MIDDLE, SHRINK_TO_BOTTOM_HIDDEN, SHRINK_TO_BOTTOM_VISIBLE }; + private ShrinkPosition mShrunkenState; + private boolean mWaitingToShrink = false; + private ShrinkPosition mWaitingToShrinkPosition; + private AnimatorSet mAnimator; + + private boolean mInScrollArea = false; + private boolean mInDragMode = false; + + private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper(); + private Bitmap mDragOutline = null; + private final Rect mTempRect = new Rect(); + private final int[] mTempXY = new int[2]; + + private ValueAnimator mDropAnim = null; + private TimeInterpolator mQuintEaseOutInterpolator = new DecelerateInterpolator(2.5f); + private View mDropView = null; + private int[] mDropViewPos = new int[] { -1, -1 }; + + // Paint used to draw external drop outline + private final Paint mExternalDragOutlinePaint = new Paint(); + + /** Used to trigger an animation as soon as the workspace stops scrolling. */ + private Animator mAnimOnPageEndMoving = null; - private static final float BASELINE_FLING_VELOCITY = 2500.f; - private static final float FLING_VELOCITY_INFLUENCE = 0.4f; - - private static class WorkspaceOvershootInterpolator implements Interpolator { - private static final float DEFAULT_TENSION = 1.3f; - private float mTension; - - public WorkspaceOvershootInterpolator() { - mTension = DEFAULT_TENSION; - } - - public void setDistance(int distance) { - mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION; - } - - public void disableSettle() { - mTension = 0.f; - } - - public float getInterpolation(float t) { - // _o(t) = t * t * ((tension + 1) * t + tension) - // o(t) = _o(t - 1) + 1 - t -= 1.0f; - return t * t * ((mTension + 1) * t + mTension) + 1.0f; - } - } - /** * Used to inflate the Workspace from XML. * * @param context The application's context. - * @param attrs The attribtues set containing the Workspace's customization values. + * @param attrs The attributes set containing the Workspace's customization values. */ public Workspace(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -168,37 +184,64 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag * Used to inflate the Workspace from XML. * * @param context The application's context. - * @param attrs The attribtues set containing the Workspace's customization values. + * @param attrs The attributes set containing the Workspace's customization values. * @param defStyle Unused. */ public Workspace(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mContentIsRefreshable = false; + + if (!LauncherApplication.isScreenXLarge()) { + mFadeInAdjacentScreens = false; + } mWallpaperManager = WallpaperManager.getInstance(context); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0); - mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.Workspace, defStyle, 0); + int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X); + int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y); + mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1); a.recycle(); + LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY); setHapticFeedbackEnabled(false); + initWorkspace(); } /** * Initializes various states for this workspace. */ - private void initWorkspace() { + protected void initWorkspace() { Context context = getContext(); - mScrollInterpolator = new WorkspaceOvershootInterpolator(); - mScroller = new Scroller(context, mScrollInterpolator); - mCurrentScreen = mDefaultScreen; - Launcher.setScreen(mCurrentScreen); + mCurrentPage = mDefaultPage; + Launcher.setScreen(mCurrentPage); LauncherApplication app = (LauncherApplication)context.getApplicationContext(); mIconCache = app.getIconCache(); + mExternalDragOutlinePaint.setAntiAlias(true); + setWillNotDraw(false); + + mUnshrinkAnimationListener = new LauncherAnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mIsInUnshrinkAnimation = true; + } + @Override + public void onAnimationEndOrCancel(Animator animation) { + mIsInUnshrinkAnimation = false; + } + }; + mSnapVelocity = 600; + } - final ViewConfiguration configuration = ViewConfiguration.get(getContext()); - mTouchSlop = configuration.getScaledTouchSlop(); - mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + @Override + protected int getScrollMode() { + if (LauncherApplication.isScreenXLarge()) { + return SmoothPagedView.QUINTIC_MODE; + } else { + return SmoothPagedView.OVERSHOOT_MODE; + } } @Override @@ -206,6 +249,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag if (!(child instanceof CellLayout)) { throw new IllegalArgumentException("A Workspace can only have CellLayout children."); } + ((CellLayout) child).setOnInterceptTouchListener(this); super.addView(child, index, params); } @@ -214,6 +258,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag if (!(child instanceof CellLayout)) { throw new IllegalArgumentException("A Workspace can only have CellLayout children."); } + ((CellLayout) child).setOnInterceptTouchListener(this); super.addView(child); } @@ -222,6 +267,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag if (!(child instanceof CellLayout)) { throw new IllegalArgumentException("A Workspace can only have CellLayout children."); } + ((CellLayout) child).setOnInterceptTouchListener(this); super.addView(child, index); } @@ -230,6 +276,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag if (!(child instanceof CellLayout)) { throw new IllegalArgumentException("A Workspace can only have CellLayout children."); } + ((CellLayout) child).setOnInterceptTouchListener(this); super.addView(child, width, height); } @@ -238,6 +285,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag if (!(child instanceof CellLayout)) { throw new IllegalArgumentException("A Workspace can only have CellLayout children."); } + ((CellLayout) child).setOnInterceptTouchListener(this); super.addView(child, params); } @@ -245,94 +293,52 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag * @return The open folder on the current screen, or null if there is none */ Folder getOpenFolder() { - CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen); - int count = currentScreen.getChildCount(); + CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); + int count = currentPage.getChildCount(); for (int i = 0; i < count; i++) { - View child = currentScreen.getChildAt(i); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { - return (Folder) child; + View child = currentPage.getChildAt(i); + if (child instanceof Folder) { + Folder folder = (Folder) child; + if (folder.getInfo().opened) + return folder; } } return null; } ArrayList<Folder> getOpenFolders() { - final int screens = getChildCount(); - ArrayList<Folder> folders = new ArrayList<Folder>(screens); + final int screenCount = getChildCount(); + ArrayList<Folder> folders = new ArrayList<Folder>(screenCount); - for (int screen = 0; screen < screens; screen++) { - CellLayout currentScreen = (CellLayout) getChildAt(screen); - int count = currentScreen.getChildCount(); + for (int screen = 0; screen < screenCount; screen++) { + CellLayout currentPage = (CellLayout) getChildAt(screen); + int count = currentPage.getChildCount(); for (int i = 0; i < count; i++) { - View child = currentScreen.getChildAt(i); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { - folders.add((Folder) child); + View child = currentPage.getChildAt(i); + if (child instanceof Folder) { + Folder folder = (Folder) child; + if (folder.getInfo().opened) + folders.add(folder); break; } } } - return folders; } - boolean isDefaultScreenShowing() { - return mCurrentScreen == mDefaultScreen; - } - - /** - * Returns the index of the currently displayed screen. - * - * @return The index of the currently displayed screen. - */ - int getCurrentScreen() { - return mCurrentScreen; + boolean isDefaultPageShowing() { + return mCurrentPage == mDefaultPage; } /** * Sets the current screen. * - * @param currentScreen - */ - void setCurrentScreen(int currentScreen) { - if (!mScroller.isFinished()) mScroller.abortAnimation(); - clearVacantCache(); - mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1)); - mPreviousIndicator.setLevel(mCurrentScreen); - mNextIndicator.setLevel(mCurrentScreen); - scrollTo(mCurrentScreen * getWidth(), 0); - updateWallpaperOffset(); - invalidate(); - } - - /** - * Adds the specified child in the current screen. The position and dimension of - * the child are defined by x, y, spanX and spanY. - * - * @param child The child to add in one of the workspace's screens. - * @param x The X position of the child in the screen's grid. - * @param y The Y position of the child in the screen's grid. - * @param spanX The number of cells spanned horizontally by the child. - * @param spanY The number of cells spanned vertically by the child. - */ - void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) { - addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false); - } - - /** - * Adds the specified child in the current screen. The position and dimension of - * the child are defined by x, y, spanX and spanY. - * - * @param child The child to add in one of the workspace's screens. - * @param x The X position of the child in the screen's grid. - * @param y The Y position of the child in the screen's grid. - * @param spanX The number of cells spanned horizontally by the child. - * @param spanY The number of cells spanned vertically by the child. - * @param insert When true, the child is inserted at the beginning of the children list. + * @param currentPage */ - void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) { - addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert); + @Override + void setCurrentPage(int currentPage) { + super.setCurrentPage(currentPage); + updateWallpaperOffset(mScrollX); } /** @@ -350,6 +356,10 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag addInScreen(child, screen, x, y, spanX, spanY, false); } + void addInFullScreen(View child, int screen) { + addInScreen(child, screen, 0, 0, -1, -1); + } + /** * Adds the specified child in the specified screen. The position and dimension of * the child are defined by x, y, spanX and spanY. @@ -369,8 +379,6 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag return; } - clearVacantCache(); - final CellLayout group = (CellLayout) getChildAt(screen); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); if (lp == null) { @@ -381,124 +389,181 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag lp.cellHSpan = spanX; lp.cellVSpan = spanY; } - group.addView(child, insert ? 0 : -1, lp); + + // Get the canonical child id to uniquely represent this view in this screen + int childId = LauncherModel.getCellLayoutChildId(-1, screen, x, y, spanX, spanY); + boolean markCellsAsOccupied = !(child instanceof Folder); + if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) { + // TODO: This branch occurs when the workspace is adding views + // outside of the defined grid + // maybe we should be deleting these items from the LauncherModel? + Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout"); + } + if (!(child instanceof Folder)) { child.setHapticFeedbackEnabled(false); child.setOnLongClickListener(mLongClickListener); } if (child instanceof DropTarget) { - mDragController.addDropTarget((DropTarget)child); + mDragController.addDropTarget((DropTarget) child); } } - CellLayout.CellInfo findAllVacantCells(boolean[] occupied) { - CellLayout group = (CellLayout) getChildAt(mCurrentScreen); - if (group != null) { - return group.findAllVacantCells(occupied, null); + public boolean onTouch(View v, MotionEvent event) { + // this is an intercepted event being forwarded from a cell layout + if (mIsSmall || mIsInUnshrinkAnimation) { + mLauncher.onWorkspaceClick((CellLayout) v); + return true; + } else if (!mPageMoving) { + if (v == getChildAt(mCurrentPage - 1)) { + snapToPage(mCurrentPage - 1); + return true; + } else if (v == getChildAt(mCurrentPage + 1)) { + snapToPage(mCurrentPage + 1); + return true; + } } - return null; + return false; } - private void clearVacantCache() { - if (mVacantCache != null) { - mVacantCache.clearVacantCells(); - mVacantCache = null; + @Override + public boolean dispatchUnhandledMove(View focused, int direction) { + if (mIsSmall || mIsInUnshrinkAnimation) { + // when the home screens are shrunken, shouldn't allow side-scrolling + return false; } + return super.dispatchUnhandledMove(focused, direction); } - /** - * Registers the specified listener on each screen contained in this workspace. - * - * @param l The listener used to respond to long clicks. - */ @Override - public void setOnLongClickListener(OnLongClickListener l) { - mLongClickListener = l; - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - getChildAt(i).setOnLongClickListener(l); + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mIsSmall || mIsInUnshrinkAnimation) { + // when the home screens are shrunken, shouldn't allow side-scrolling + return false; } + return super.onInterceptTouchEvent(ev); } - private void updateWallpaperOffset() { - updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft)); + @Override + protected void determineScrollingStart(MotionEvent ev) { + if (!mIsSmall && !mIsInUnshrinkAnimation) super.determineScrollingStart(ev); } - private void updateWallpaperOffset(int scrollRange) { - IBinder token = getWindowToken(); - if (token != null) { - mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 ); - mWallpaperManager.setWallpaperOffsets(getWindowToken(), - Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0); + protected void onPageBeginMoving() { + if (mNextPage != INVALID_PAGE) { + // we're snapping to a particular screen + enableChildrenCache(mCurrentPage, mNextPage); + } else { + // this is when user is actively dragging a particular screen, they might + // swipe it either left or right (but we won't advance by more than one screen) + enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1); } + showOutlines(); + mPageMoving = true; } - - @Override - public void scrollTo(int x, int y) { - super.scrollTo(x, y); - mTouchX = x; - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + + protected void onPageEndMoving() { + clearChildrenCache(); + // Hide the outlines, as long as we're not dragging + if (!mDragController.dragging()) { + hideOutlines(); + } + // Check for an animation that's waiting to be started + if (mAnimOnPageEndMoving != null) { + mAnimOnPageEndMoving.start(); + mAnimOnPageEndMoving = null; + } + + mPageMoving = false; } - + @Override - public void computeScroll() { - if (mScroller.computeScrollOffset()) { - mTouchX = mScrollX = mScroller.getCurrX(); - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - mScrollY = mScroller.getCurrY(); - updateWallpaperOffset(); - postInvalidate(); - } else if (mNextScreen != INVALID_SCREEN) { - mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1)); - mPreviousIndicator.setLevel(mCurrentScreen); - mNextIndicator.setLevel(mCurrentScreen); - Launcher.setScreen(mCurrentScreen); - mNextScreen = INVALID_SCREEN; - clearChildrenCache(); - } else if (mTouchState == TOUCH_STATE_SCROLLING) { - final float now = System.nanoTime() / NANOTIME_DIV; - final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT); - final float dx = mTouchX - mScrollX; - mScrollX += dx * e; - mSmoothingTime = now; - - // Keep generating points as long as we're more than 1px away from the target - if (dx > 1.f || dx < -1.f) { - updateWallpaperOffset(); - postInvalidate(); + protected void notifyPageSwitchListener() { + super.notifyPageSwitchListener(); + + if (mPreviousIndicator != null) { + // if we know the next page, we show the indication for it right away; it looks + // weird if the indicators are lagging + int page = mNextPage; + if (page == INVALID_PAGE) { + page = mCurrentPage; } + mPreviousIndicator.setLevel(page); + mNextIndicator.setLevel(page); } + Launcher.setScreen(mCurrentPage); + }; + + private void updateWallpaperOffset() { + updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft)); } - @Override - protected void dispatchDraw(Canvas canvas) { - boolean restore = false; - int restoreCount = 0; - - // ViewGroup.dispatchDraw() supports many features we don't need: - // clip to padding, layout animation, animation listener, disappearing - // children, etc. The following implementation attempts to fast-track - // the drawing dispatch by drawing only what we know needs to be drawn. - - boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN; - // If we are not scrolling or flinging, draw only the current screen - if (fastDraw) { - drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime()); - } else { - final long drawingTime = getDrawingTime(); - final float scrollPos = (float) mScrollX / getWidth(); - final int leftScreen = (int) scrollPos; - final int rightScreen = leftScreen + 1; - if (leftScreen >= 0) { - drawChild(canvas, getChildAt(leftScreen), drawingTime); - } - if (scrollPos != leftScreen && rightScreen < getChildCount()) { - drawChild(canvas, getChildAt(rightScreen), drawingTime); + private void updateWallpaperOffset(int scrollRange) { + final boolean isStaticWallpaper = (mWallpaperManager != null) && + (mWallpaperManager.getWallpaperInfo() == null); + if (LauncherApplication.isScreenXLarge() && !isStaticWallpaper) { + IBinder token = getWindowToken(); + if (token != null) { + mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 ); + mWallpaperManager.setWallpaperOffsets(getWindowToken(), + Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0); } } + } + + public void showOutlines() { + if (!mIsSmall && !mIsInUnshrinkAnimation) { + if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel(); + if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel(); + mBackgroundFadeInAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 1.0f); + mBackgroundFadeInAnimation.setDuration(BACKGROUND_FADE_IN_DURATION); + mBackgroundFadeInAnimation.start(); + } + } + + public void hideOutlines() { + if (!mIsSmall && !mIsInUnshrinkAnimation) { + if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel(); + if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel(); + mBackgroundFadeOutAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 0.0f); + mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION); + mBackgroundFadeOutAnimation.setStartDelay(BACKGROUND_FADE_OUT_DELAY); + mBackgroundFadeOutAnimation.start(); + } + } + + public void setBackgroundAlpha(float alpha) { + mBackgroundAlpha = alpha; + for (int i = 0; i < getChildCount(); i++) { + CellLayout cl = (CellLayout) getChildAt(i); + cl.setBackgroundAlpha(alpha); + } + } + + public float getBackgroundAlpha() { + return mBackgroundAlpha; + } - if (restore) { - canvas.restoreToCount(restoreCount); + @Override + protected void screenScrolled(int screenCenter) { + final int halfScreenSize = getMeasuredWidth() / 2; + for (int i = 0; i < getChildCount(); i++) { + CellLayout cl = (CellLayout) getChildAt(i); + if (cl != null) { + int totalDistance = cl.getMeasuredWidth() + mPageSpacing; + int delta = screenCenter - (getChildOffset(i) - + getRelativeChildOffset(i) + halfScreenSize); + + float scrollProgress = delta/(totalDistance*1.0f); + scrollProgress = Math.min(scrollProgress, 1.0f); + scrollProgress = Math.max(scrollProgress, -1.0f); + + float mult = mInDragMode ? 1.0f : Math.abs(scrollProgress); + cl.setBackgroundAlphaMultiplier(mult); + + float rotation = WORKSPACE_ROTATION * scrollProgress; + cl.setRotationY(rotation); + } } } @@ -509,59 +574,72 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); - final int width = MeasureSpec.getSize(widthMeasureSpec); - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - if (widthMode != MeasureSpec.EXACTLY) { - throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); + // if shrinkToBottom() is called on initialization, it has to be deferred + // until after the first call to onLayout so that it has the correct width + if (mWaitingToShrink) { + shrink(mWaitingToShrinkPosition, false); + mWaitingToShrink = false; } - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - if (heightMode != MeasureSpec.EXACTLY) { - throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); + if (LauncherApplication.isInPlaceRotationEnabled()) { + // When the device is rotated, the scroll position of the current screen + // needs to be refreshed + setCurrentPage(getCurrentPage()); } + } - // The children are given the same width and height as the workspace - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); - } + @Override + protected void dispatchDraw(Canvas canvas) { + if (mIsSmall || mIsInUnshrinkAnimation) { + // Draw all the workspaces if we're small + final int pageCount = getChildCount(); + final long drawingTime = getDrawingTime(); + for (int i = 0; i < pageCount; i++) { + final View page = (View) getChildAt(i); + drawChild(canvas, page, drawingTime); + } + } else { + super.dispatchDraw(canvas); - if (mFirstLayout) { - setHorizontalScrollBarEnabled(false); - scrollTo(mCurrentScreen * width, 0); - setHorizontalScrollBarEnabled(true); - updateWallpaperOffset(width * (getChildCount() - 1)); - mFirstLayout = false; - } - } + final int width = getWidth(); + final int height = getHeight(); - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - int childLeft = 0; + // In portrait orientation, draw the glowing edge when dragging to adjacent screens + if (mInScrollArea && (height > width)) { + final int pageHeight = getChildAt(0).getHeight(); - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() != View.GONE) { - final int childWidth = child.getMeasuredWidth(); - child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); - childLeft += childWidth; + // This determines the height of the glowing edge: 90% of the page height + final int padding = (int) ((height - pageHeight) * 0.5f + pageHeight * 0.1f); + + final CellLayout leftPage = (CellLayout) getChildAt(mCurrentPage - 1); + final CellLayout rightPage = (CellLayout) getChildAt(mCurrentPage + 1); + + if (leftPage != null && leftPage.getHover()) { + final Drawable d = getResources().getDrawable(R.drawable.page_hover_left); + d.setBounds(mScrollX, padding, mScrollX + d.getIntrinsicWidth(), height - padding); + d.draw(canvas); + } else if (rightPage != null && rightPage.getHover()) { + final Drawable d = getResources().getDrawable(R.drawable.page_hover_right); + d.setBounds(mScrollX + width - d.getIntrinsicWidth(), padding, mScrollX + width, height - padding); + d.draw(canvas); + } } - } - } - @Override - public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { - int screen = indexOfChild(child); - if (screen != mCurrentScreen || !mScroller.isFinished()) { - snapToScreen(screen); - return true; + if (mDropView != null) { + // We are animating an item that was just dropped on the home screen. + // Render its View in the current animation position. + canvas.save(Canvas.MATRIX_SAVE_FLAG); + final int xPos = mDropViewPos[0] - mDropView.getScrollX(); + final int yPos = mDropViewPos[1] - mDropView.getScrollY(); + canvas.translate(xPos, yPos); + mDropView.draw(canvas); + canvas.restore(); + } } - return false; } @Override @@ -571,51 +649,20 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag if (openFolder != null) { return openFolder.requestFocus(direction, previouslyFocusedRect); } else { - int focusableScreen; - if (mNextScreen != INVALID_SCREEN) { - focusableScreen = mNextScreen; - } else { - focusableScreen = mCurrentScreen; - } - getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect); + return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); } } return false; } @Override - public boolean dispatchUnhandledMove(View focused, int direction) { - if (direction == View.FOCUS_LEFT) { - if (getCurrentScreen() > 0) { - snapToScreen(getCurrentScreen() - 1); - return true; - } - } else if (direction == View.FOCUS_RIGHT) { - if (getCurrentScreen() < getChildCount() - 1) { - snapToScreen(getCurrentScreen() + 1); - return true; - } - } - return super.dispatchUnhandledMove(focused, direction); - } - - @Override public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { if (!mLauncher.isAllAppsVisible()) { final Folder openFolder = getOpenFolder(); - if (openFolder == null) { - getChildAt(mCurrentScreen).addFocusables(views, direction); - if (direction == View.FOCUS_LEFT) { - if (mCurrentScreen > 0) { - getChildAt(mCurrentScreen - 1).addFocusables(views, direction); - } - } else if (direction == View.FOCUS_RIGHT){ - if (mCurrentScreen < getChildCount() - 1) { - getChildAt(mCurrentScreen + 1).addFocusables(views, direction); - } - } - } else { + if (openFolder != null) { openFolder.addFocusables(views, direction); + } else { + super.addFocusables(views, direction, focusableMode); } } } @@ -623,195 +670,28 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { - if (mLauncher.isAllAppsVisible()) { + // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps + // ie when you click on a mini-screen, it zooms back to that screen) + if (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible()) { return false; } } return super.dispatchTouchEvent(ev); } - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - final boolean allAppsVisible = mLauncher.isAllAppsVisible(); - if (allAppsVisible) { - return false; // We don't want the events. Let them fall through to the all apps view. - } - - /* - * This method JUST determines whether we want to intercept the motion. - * If we return true, onTouchEvent will be called and we do the actual - * scrolling there. - */ - - /* - * Shortcut the most recurring case: the user is in the dragging - * state and he is moving his finger. We want to intercept this - * motion. - */ - final int action = ev.getAction(); - if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { - return true; - } - - acquireVelocityTrackerAndAddMovement(ev); - - switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_MOVE: { - /* - * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check - * whether the user has moved far enough from his original down touch. - */ - - /* - * Locally do absolute value. mLastMotionX is set to the y value - * of the down event. - */ - final int pointerIndex = ev.findPointerIndex(mActivePointerId); - final float x = ev.getX(pointerIndex); - final float y = ev.getY(pointerIndex); - final int xDiff = (int) Math.abs(x - mLastMotionX); - final int yDiff = (int) Math.abs(y - mLastMotionY); - - final int touchSlop = mTouchSlop; - boolean xMoved = xDiff > touchSlop; - boolean yMoved = yDiff > touchSlop; - - if (xMoved || yMoved) { - - if (xMoved) { - // Scroll if the user moved far enough along the X axis - mTouchState = TOUCH_STATE_SCROLLING; - mLastMotionX = x; - mTouchX = mScrollX; - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1); - } - // Either way, cancel any pending longpress - if (mAllowLongPress) { - mAllowLongPress = false; - // Try canceling the long press. It could also have been scheduled - // by a distant descendant, so use the mAllowLongPress flag to block - // everything - final View currentScreen = getChildAt(mCurrentScreen); - currentScreen.cancelLongPress(); - } - } - break; - } - - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - // Remember location of down touch - mLastMotionX = x; - mLastMotionY = y; - mActivePointerId = ev.getPointerId(0); - mAllowLongPress = true; - - /* - * If being flinged and user touches the screen, initiate drag; - * otherwise don't. mScroller.isFinished should be false when - * being flinged. - */ - mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; - break; - } - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - - if (mTouchState != TOUCH_STATE_SCROLLING) { - final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen); - if (!currentScreen.lastDownOnOccupiedCell()) { - getLocationOnScreen(mTempCell); - // Send a tap to the wallpaper if the last down was on empty space - final int pointerIndex = ev.findPointerIndex(mActivePointerId); - mWallpaperManager.sendWallpaperCommand(getWindowToken(), - "android.wallpaper.tap", - mTempCell[0] + (int) ev.getX(pointerIndex), - mTempCell[1] + (int) ev.getY(pointerIndex), 0, null); - } - } - - // Release the drag - clearChildrenCache(); - mTouchState = TOUCH_STATE_REST; - mActivePointerId = INVALID_POINTER; - mAllowLongPress = false; - releaseVelocityTracker(); - break; - - case MotionEvent.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - break; - } - - /* - * The only time we want to intercept motion events is if we are in the - * drag mode. - */ - return mTouchState != TOUCH_STATE_REST; - } - - private void onSecondaryPointerUp(MotionEvent ev) { - final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> - MotionEvent.ACTION_POINTER_INDEX_SHIFT; - final int pointerId = ev.getPointerId(pointerIndex); - if (pointerId == mActivePointerId) { - // This was our active pointer going up. Choose a new - // active pointer and adjust accordingly. - // TODO: Make this decision more intelligent. - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionX = ev.getX(newPointerIndex); - mLastMotionY = ev.getY(newPointerIndex); - mActivePointerId = ev.getPointerId(newPointerIndex); - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - } - } - } - - /** - * If one of our descendant views decides that it could be focused now, only - * pass that along if it's on the current screen. - * - * This happens when live folders requery, and if they're off screen, they - * end up calling requestFocus, which pulls it on screen. - */ - @Override - public void focusableViewAvailable(View focused) { - View current = getChildAt(mCurrentScreen); - View v = focused; - while (true) { - if (v == current) { - super.focusableViewAvailable(focused); - return; - } - if (v == this) { - return; - } - ViewParent parent = v.getParent(); - if (parent instanceof View) { - v = (View)v.getParent(); - } else { - return; - } + void enableChildrenCache(int fromPage, int toPage) { + if (fromPage > toPage) { + final int temp = fromPage; + fromPage = toPage; + toPage = temp; } - } - void enableChildrenCache(int fromScreen, int toScreen) { - if (fromScreen > toScreen) { - final int temp = fromScreen; - fromScreen = toScreen; - toScreen = temp; - } - - final int count = getChildCount(); + final int screenCount = getChildCount(); - fromScreen = Math.max(fromScreen, 0); - toScreen = Math.min(toScreen, count - 1); + fromPage = Math.max(fromPage, 0); + toPage = Math.min(toPage, screenCount - 1); - for (int i = fromScreen; i <= toScreen; i++) { + for (int i = fromPage; i <= toPage; i++) { final CellLayout layout = (CellLayout) getChildAt(i); layout.setChildrenDrawnWithCacheEnabled(true); layout.setChildrenDrawingCacheEnabled(true); @@ -819,8 +699,8 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } void clearChildrenCache() { - final int count = getChildCount(); - for (int i = 0; i < count; i++) { + final int screenCount = getChildCount(); + for (int i = 0; i < screenCount; i++) { final CellLayout layout = (CellLayout) getChildAt(i); layout.setChildrenDrawnWithCacheEnabled(false); } @@ -828,384 +708,1174 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag @Override public boolean onTouchEvent(MotionEvent ev) { - if (mLauncher.isAllAppsVisible()) { // Cancel any scrolling that is in progress. if (!mScroller.isFinished()) { mScroller.abortAnimation(); } - snapToScreen(mCurrentScreen); + setCurrentPage(mCurrentPage); return false; // We don't want the events. Let them fall through to the all apps view. } - acquireVelocityTrackerAndAddMovement(ev); + return super.onTouchEvent(ev); + } - final int action = ev.getAction(); + public boolean isSmall() { + return mIsSmall; + } - switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: - /* - * If being flinged and user touches, stop the fling. isFinished - * will be false if being flinged. - */ - if (!mScroller.isFinished()) { - mScroller.abortAnimation(); - } + void shrinkToTop(boolean animated) { + shrink(ShrinkPosition.SHRINK_TO_TOP, animated); + } + + void shrinkToMiddle() { + shrink(ShrinkPosition.SHRINK_TO_MIDDLE, true); + } + + void shrinkToBottom() { + shrinkToBottom(true); + } + + void shrinkToBottom(boolean animated) { + shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, animated); + } + + private float getYScaleForScreen(int screen) { + int x = Math.abs(screen - 2); + + // TODO: This should be generalized for use with arbitrary rotation angles. + switch(x) { + case 0: return EXTRA_SCALE_FACTOR_0; + case 1: return EXTRA_SCALE_FACTOR_1; + case 2: return EXTRA_SCALE_FACTOR_2; + } + return 1.0f; + } - // Remember where the motion event started - mLastMotionX = ev.getX(); - mActivePointerId = ev.getPointerId(0); - if (mTouchState == TOUCH_STATE_SCROLLING) { - enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1); + // we use this to shrink the workspace for the all apps view and the customize view + private void shrink(ShrinkPosition shrinkPosition, boolean animated) { + if (mFirstLayout) { + // (mFirstLayout == "first layout has not happened yet") + // if we get a call to shrink() as part of our initialization (for example, if + // Launcher is started in All Apps mode) then we need to wait for a layout call + // to get our width so we can layout the mini-screen views correctly + mWaitingToShrink = true; + mWaitingToShrinkPosition = shrinkPosition; + return; + } + mIsSmall = true; + mShrunkenState = shrinkPosition; + + // Stop any scrolling, move to the current page right away + setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage); + updateWhichPagesAcceptDrops(mShrunkenState); + + // we intercept and reject all touch events when we're small, so be sure to reset the state + mTouchState = TOUCH_STATE_REST; + mActivePointerId = INVALID_POINTER; + + CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); + currentPage.setBackgroundAlphaMultiplier(1.0f); + + final Resources res = getResources(); + final int screenWidth = getWidth(); + final int screenHeight = getHeight(); + + // Making the assumption that all pages have the same width as the 0th + final int pageWidth = getChildAt(0).getMeasuredWidth(); + final int pageHeight = getChildAt(0).getMeasuredHeight(); + + final int scaledPageWidth = (int) (SHRINK_FACTOR * pageWidth); + final int scaledPageHeight = (int) (SHRINK_FACTOR * pageHeight); + final float extraScaledSpacing = res.getDimension(R.dimen.smallScreenExtraSpacing); + + final int screenCount = getChildCount(); + float totalWidth = screenCount * scaledPageWidth + (screenCount - 1) * extraScaledSpacing; + + boolean isPortrait = getMeasuredHeight() > getMeasuredWidth(); + float newY = (isPortrait ? + getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginPortrait) : + getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginLandscape)); + float finalAlpha = 1.0f; + float extraShrinkFactor = 1.0f; + if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) { + newY = screenHeight - newY - scaledPageHeight; + } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) { + + // We shrink and disappear to nothing in the case of all apps + // (which is when we shrink to the bottom) + newY = screenHeight - newY - scaledPageHeight; + finalAlpha = 0.25f; + } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_MIDDLE) { + newY = screenHeight / 2 - scaledPageHeight / 2; + finalAlpha = 1.0f; + } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_TOP) { + newY = (isPortrait ? + getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginPortrait) : + getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginLandscape)); + } + + // We animate all the screens to the centered position in workspace + // At the same time, the screens become greyed/dimmed + + // newX is initialized to the left-most position of the centered screens + float newX = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2; + + // We are going to scale about the center of the view, so we need to adjust the positions + // of the views accordingly + newX -= (pageWidth - scaledPageWidth) / 2.0f; + newY -= (pageHeight - scaledPageHeight) / 2.0f; + + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = new AnimatorSet(); + for (int i = 0; i < screenCount; i++) { + CellLayout cl = (CellLayout) getChildAt(i); + + float rotation = (-i + 2) * WORKSPACE_ROTATION; + float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f)); + float rotationScaleY = getYScaleForScreen(i); + + if (animated) { + final int duration = res.getInteger(R.integer.config_workspaceShrinkTime); + ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(cl, + PropertyValuesHolder.ofFloat("x", newX), + PropertyValuesHolder.ofFloat("y", newY), + PropertyValuesHolder.ofFloat("scaleX", + SHRINK_FACTOR * rotationScaleX * extraShrinkFactor), + PropertyValuesHolder.ofFloat("scaleY", + SHRINK_FACTOR * rotationScaleY * extraShrinkFactor), + PropertyValuesHolder.ofFloat("backgroundAlpha", finalAlpha), + PropertyValuesHolder.ofFloat("alpha", finalAlpha), + PropertyValuesHolder.ofFloat("rotationY", rotation)); + anim.setDuration(duration); + mAnimator.playTogether(anim); + } else { + cl.setX((int)newX); + cl.setY((int)newY); + cl.setScaleX(SHRINK_FACTOR * rotationScaleX * extraShrinkFactor); + cl.setScaleY(SHRINK_FACTOR * rotationScaleY * extraShrinkFactor); + cl.setBackgroundAlpha(finalAlpha); + cl.setAlpha(finalAlpha); + cl.setRotationY(rotation); } - break; - case MotionEvent.ACTION_MOVE: - if (mTouchState == TOUCH_STATE_SCROLLING) { - // Scroll to follow the motion event - final int pointerIndex = ev.findPointerIndex(mActivePointerId); - final float x = ev.getX(pointerIndex); - final float deltaX = mLastMotionX - x; - mLastMotionX = x; - - if (deltaX < 0) { - if (mTouchX > 0) { - mTouchX += Math.max(-mTouchX, deltaX); - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - invalidate(); + // increment newX for the next screen + newX += scaledPageWidth + extraScaledSpacing; + } + if (animated) { + mAnimator.start(); + } + setChildrenDrawnWithCacheEnabled(true); + } + + + private void updateWhichPagesAcceptDrops(ShrinkPosition state) { + updateWhichPagesAcceptDropsHelper(state, false, 1, 1); + } + + + private void updateWhichPagesAcceptDropsDuringDrag(ShrinkPosition state, int spanX, int spanY) { + updateWhichPagesAcceptDropsHelper(state, true, spanX, spanY); + } + + private void updateWhichPagesAcceptDropsHelper( + ShrinkPosition state, boolean isDragHappening, int spanX, int spanY) { + final int screenCount = getChildCount(); + for (int i = 0; i < screenCount; i++) { + CellLayout cl = (CellLayout) getChildAt(i); + + switch (state) { + case SHRINK_TO_TOP: + if (!isDragHappening) { + boolean showDropHighlight = i == mCurrentPage; + cl.setAcceptsDrops(showDropHighlight); + break; } - } else if (deltaX > 0) { - final float availableToScroll = getChildAt(getChildCount() - 1).getRight() - - mTouchX - getWidth(); - if (availableToScroll > 0) { - mTouchX += Math.min(availableToScroll, deltaX); - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - invalidate(); + // otherwise, fall through below and mark non-full screens as accepting drops + case SHRINK_TO_BOTTOM_HIDDEN: + case SHRINK_TO_BOTTOM_VISIBLE: + if (!isDragHappening) { + // even if a drag isn't happening, we don't want to show a screen as + // accepting drops if it doesn't have at least one free cell + spanX = 1; + spanY = 1; } - } else { - awakenScrollBars(); - } - } - break; - case MotionEvent.ACTION_UP: - if (mTouchState == TOUCH_STATE_SCROLLING) { - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - final int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId); - - final int screenWidth = getWidth(); - final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth; - final float scrolledPos = (float) mScrollX / screenWidth; - - if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { - // Fling hard enough to move left. - // Don't fling across more than one screen at a time. - final int bound = scrolledPos < whichScreen ? - mCurrentScreen - 1 : mCurrentScreen; - snapToScreen(Math.min(whichScreen, bound), velocityX, true); - } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) { - // Fling hard enough to move right - // Don't fling across more than one screen at a time. - final int bound = scrolledPos > whichScreen ? - mCurrentScreen + 1 : mCurrentScreen; - snapToScreen(Math.max(whichScreen, bound), velocityX, true); - } else { - snapToScreen(whichScreen, 0, true); - } - } - mTouchState = TOUCH_STATE_REST; - mActivePointerId = INVALID_POINTER; - releaseVelocityTracker(); - break; - case MotionEvent.ACTION_CANCEL: - if (mTouchState == TOUCH_STATE_SCROLLING) { - final int screenWidth = getWidth(); - final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth; - snapToScreen(whichScreen, 0, true); + // the page accepts drops if we can find at least one empty spot + cl.setAcceptsDrops(cl.findCellForSpan(null, spanX, spanY)); + break; + default: + throw new RuntimeException( + "updateWhichPagesAcceptDropsHelper passed an unhandled ShrinkPosition"); } - mTouchState = TOUCH_STATE_REST; - mActivePointerId = INVALID_POINTER; - releaseVelocityTracker(); - break; - case MotionEvent.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - break; } + } - return true; + /* + * + * We call these methods (onDragStartedWithItemSpans/onDragStartedWithItemMinSize) whenever we + * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace + * + * These methods mark the appropriate pages as accepting drops (which alters their visual + * appearance) and, if the pages are hidden, makes them visible. + * + */ + public void onDragStartedWithItemSpans(int spanX, int spanY) { + updateWhichPagesAcceptDropsDuringDrag(mShrunkenState, spanX, spanY); + if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) { + shrink(ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE, true); + } } - - private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); + + public void onDragStartedWithItemMinSize(int minWidth, int minHeight) { + int[] spanXY = CellLayout.rectToCell(getResources(), minWidth, minHeight, null); + onDragStartedWithItemSpans(spanXY[0], spanXY[1]); + } + + // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was + // never dragged over + public void onDragStopped() { + updateWhichPagesAcceptDrops(mShrunkenState); + if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) { + shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, true); } - mVelocityTracker.addMovement(ev); } - private void releaseVelocityTracker() { - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; + // We call this when we trigger an unshrink by clicking on the CellLayout cl + public void unshrink(CellLayout clThatWasClicked) { + int newCurrentPage = mCurrentPage; + final int screenCount = getChildCount(); + for (int i = 0; i < screenCount; i++) { + if (getChildAt(i) == clThatWasClicked) { + newCurrentPage = i; + } } + unshrink(newCurrentPage); } - void snapToScreen(int whichScreen) { - snapToScreen(whichScreen, 0, false); + @Override + protected boolean handlePagingClicks() { + return true; } - private void snapToScreen(int whichScreen, int velocity, boolean settle) { - //if (!mScroller.isFinished()) return; + private void unshrink(int newCurrentPage) { + if (mIsSmall) { + int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage); + int delta = newX - mScrollX; - whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); - - clearVacantCache(); - enableChildrenCache(mCurrentScreen, whichScreen); + final int screenCount = getChildCount(); + for (int i = 0; i < screenCount; i++) { + CellLayout cl = (CellLayout) getChildAt(i); + cl.setX(cl.getX() + delta); + } + setCurrentPage(newCurrentPage); + unshrink(); + } + } - mNextScreen = whichScreen; + void unshrink() { + unshrink(true); + } - mPreviousIndicator.setLevel(mNextScreen); - mNextIndicator.setLevel(mNextScreen); + void unshrink(boolean animated) { + if (mIsSmall) { + mIsSmall = false; + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = new AnimatorSet(); + final int screenCount = getChildCount(); + + final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime); + for (int i = 0; i < screenCount; i++) { + final CellLayout cl = (CellLayout)getChildAt(i); + float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f; + float rotation = 0.0f; + + if (i < mCurrentPage) { + rotation = WORKSPACE_ROTATION; + } else if (i > mCurrentPage) { + rotation = -WORKSPACE_ROTATION; + } - View focusedChild = getFocusedChild(); - if (focusedChild != null && whichScreen != mCurrentScreen && - focusedChild == getChildAt(mCurrentScreen)) { - focusedChild.clearFocus(); + if (animated) { + mAnimator.playTogether( + ObjectAnimator.ofFloat(cl, "translationX", 0.0f).setDuration(duration), + ObjectAnimator.ofFloat(cl, "translationY", 0.0f).setDuration(duration), + ObjectAnimator.ofFloat(cl, "scaleX", 1.0f).setDuration(duration), + ObjectAnimator.ofFloat(cl, "scaleY", 1.0f).setDuration(duration), + ObjectAnimator.ofFloat(cl, "backgroundAlpha", 0.0f).setDuration(duration), + ObjectAnimator.ofFloat(cl, "alpha", finalAlphaValue).setDuration(duration), + ObjectAnimator.ofFloat(cl, "rotationY", rotation).setDuration(duration)); + } else { + cl.setTranslationX(0.0f); + cl.setTranslationY(0.0f); + cl.setScaleX(1.0f); + cl.setScaleY(1.0f); + cl.setBackgroundAlpha(0.0f); + cl.setAlpha(finalAlphaValue); + cl.setRotationY(rotation); + } + } + if (animated) { + // If we call this when we're not animated, onAnimationEnd is never called on + // the listener; make sure we only use the listener when we're actually animating + mAnimator.addListener(mUnshrinkAnimationListener); + mAnimator.start(); + } } - - final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen)); - final int newX = whichScreen * getWidth(); - final int delta = newX - mScrollX; - int duration = (screenDelta + 1) * 100; + } - if (!mScroller.isFinished()) { - mScroller.abortAnimation(); - } - - if (settle) { - mScrollInterpolator.setDistance(screenDelta); - } else { - mScrollInterpolator.disableSettle(); - } - - velocity = Math.abs(velocity); - if (velocity > 0) { - duration += (duration / (velocity / BASELINE_FLING_VELOCITY)) - * FLING_VELOCITY_INFLUENCE; - } else { - duration += 100; + /** + * Draw the View v into the given Canvas. + * + * @param v the view to draw + * @param destCanvas the canvas to draw on + * @param padding the horizontal and vertical padding to use when drawing + */ + private void drawDragView(View v, Canvas destCanvas, int padding) { + final Rect clipRect = mTempRect; + v.getDrawingRect(clipRect); + + // For a TextView, adjust the clip rect so that we don't include the text label + if (v instanceof TextView) { + final int iconHeight = ((TextView)v).getCompoundPaddingTop() - v.getPaddingTop(); + clipRect.bottom = clipRect.top + iconHeight; } - awakenScrollBars(duration); - mScroller.startScroll(mScrollX, 0, delta, 0, duration); - invalidate(); + // Draw the View into the bitmap. + // The translate of scrollX and scrollY is necessary when drawing TextViews, because + // they set scrollX and scrollY to large values to achieve centered text + + destCanvas.save(); + destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2); + destCanvas.clipRect(clipRect, Op.REPLACE); + v.draw(destCanvas); + destCanvas.restore(); + } + + /** + * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. + * Responsibility for the bitmap is transferred to the caller. + */ + private Bitmap createDragOutline(View v, Canvas canvas, int padding) { + final int outlineColor = getResources().getColor(R.color.drag_outline_color); + final Bitmap b = Bitmap.createBitmap( + v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); + + canvas.setBitmap(b); + drawDragView(v, canvas, padding); + mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true); + + return b; + } + + /** + * Creates a drag outline to represent a drop (that we don't have the actual information for + * yet). May be changed in the future to alter the drop outline slightly depending on the + * clip description mime data. + */ + private Bitmap createExternalDragOutline(Canvas canvas, int padding) { + Resources r = getResources(); + final int outlineColor = r.getColor(R.color.drag_outline_color); + final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width); + final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height); + final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius); + final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f); + final Bitmap b = Bitmap.createBitmap( + iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888); + + canvas.setBitmap(b); + canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset), + rectRadius, rectRadius, mExternalDragOutlinePaint); + mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true); + + return b; + } + + /** + * Returns a new bitmap to show when the given View is being dragged around. + * Responsibility for the bitmap is transferred to the caller. + */ + private Bitmap createDragBitmap(View v, Canvas canvas, int padding) { + final int outlineColor = getResources().getColor(R.color.drag_outline_color); + final Bitmap b = Bitmap.createBitmap( + mDragOutline.getWidth(), mDragOutline.getHeight(), Bitmap.Config.ARGB_8888); + + canvas.setBitmap(b); + canvas.drawBitmap(mDragOutline, 0, 0, null); + drawDragView(v, canvas, padding); + mOutlineHelper.applyOuterBlur(b, canvas, outlineColor); + + return b; } void startDrag(CellLayout.CellInfo cellInfo) { View child = cellInfo.cell; - + // Make sure the drag was started by a long press as opposed to a long click. if (!child.isInTouchMode()) { return; } - + mDragInfo = cellInfo; - mDragInfo.screen = mCurrentScreen; - - CellLayout current = ((CellLayout) getChildAt(mCurrentScreen)); + mDragInfo.screen = mCurrentPage; + + CellLayout current = getCurrentDropLayout(); current.onDragChild(child); - mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE); - invalidate(); + child.setVisibility(View.GONE); + + child.clearFocus(); + child.setPressed(false); + + final Canvas canvas = new Canvas(); + + // We need to add extra padding to the bitmap to make room for the glow effect + final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS; + + // The outline is used to visualize where the item will land if dropped + mDragOutline = createDragOutline(child, canvas, bitmapPadding); + + // The drag bitmap follows the touch point around on the screen + final Bitmap b = createDragBitmap(child, canvas, bitmapPadding); + + final int bmpWidth = b.getWidth(); + final int bmpHeight = b.getHeight(); + child.getLocationOnScreen(mTempXY); + final int screenX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2; + final int screenY = (int) mTempXY[1] + (child.getHeight() - bmpHeight) / 2; + mDragController.startDrag(b, screenX, screenY, 0, 0, bmpWidth, bmpHeight, this, + child.getTag(), DragController.DRAG_ACTION_MOVE, null); + b.recycle(); } - @Override - protected Parcelable onSaveInstanceState() { - final SavedState state = new SavedState(super.onSaveInstanceState()); - state.currentScreen = mCurrentScreen; - return state; + void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY, + boolean insertAtFirst, int intersectX, int intersectY) { + final CellLayout cellLayout = (CellLayout) getChildAt(screen); + View view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo) info); + + final int[] cellXY = new int[2]; + cellLayout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); + addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst); + LauncherModel.addOrMoveItemInDatabase(mLauncher, info, + LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, + cellXY[0], cellXY[1]); } - @Override - protected void onRestoreInstanceState(Parcelable state) { - SavedState savedState = (SavedState) state; - super.onRestoreInstanceState(savedState.getSuperState()); - if (savedState.currentScreen != -1) { - mCurrentScreen = savedState.currentScreen; - Launcher.setScreen(mCurrentScreen); - } + private void setPositionForDropAnimation( + View dragView, int dragViewX, int dragViewY, View parent, View child) { + final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); + + // Based on the position of the drag view, find the top left of the original view + int viewX = dragViewX + (dragView.getWidth() - child.getWidth()) / 2; + int viewY = dragViewY + (dragView.getHeight() - child.getHeight()) / 2; + viewX -= getResources().getInteger(R.integer.config_dragViewOffsetX); + viewY -= getResources().getInteger(R.integer.config_dragViewOffsetY); + + // Set its old pos (in the new parent's coordinates); it will be animated + // in animateViewIntoPosition after the next layout pass + lp.oldX = viewX - (parent.getLeft() - mScrollX); + lp.oldY = viewY - (parent.getTop() - mScrollY); } - void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo) { - addApplicationShortcut(info, cellInfo, false); + public void animateViewIntoPosition(final View view) { + final CellLayout parent = (CellLayout) view.getParent(); + final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); + + // Convert the animation params to be relative to the Workspace, not the CellLayout + final int fromX = lp.oldX + parent.getLeft(); + final int fromY = lp.oldY + parent.getTop(); + + final int dx = lp.x - lp.oldX; + final int dy = lp.y - lp.oldY; + + // Calculate the duration of the animation based on the object's distance + final float dist = (float) Math.sqrt(dx*dx + dy*dy); + final Resources res = getResources(); + final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); + int duration = res.getInteger(R.integer.config_dropAnimMaxDuration); + if (dist < maxDist) { + duration *= mQuintEaseOutInterpolator.getInterpolation(dist / maxDist); + } + + if (mDropAnim != null) { + // This should really be end(), but that will not be called synchronously, + // so instead we use LauncherAnimatorListenerAdapter.onAnimationEndOrCancel() + // and call cancel() here. + mDropAnim.cancel(); + } + mDropAnim = new ValueAnimator(); + mDropAnim.setInterpolator(mQuintEaseOutInterpolator); + + // The view is invisible during the animation; we render it manually. + mDropAnim.addListener(new LauncherAnimatorListenerAdapter() { + public void onAnimationStart(Animator animation) { + // Set this here so that we don't render it until the animation begins + mDropView = view; + } + + public void onAnimationEndOrCancel(Animator animation) { + if (mDropView != null) { + mDropView.setVisibility(View.VISIBLE); + mDropView = null; + } + } + }); + + mDropAnim.setDuration(duration); + mDropAnim.setFloatValues(0.0f, 1.0f); + mDropAnim.removeAllUpdateListeners(); + mDropAnim.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + final float percent = (Float) animation.getAnimatedValue(); + // Invalidate the old position + invalidate(mDropViewPos[0], mDropViewPos[1], + mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight()); + + mDropViewPos[0] = fromX + (int) (percent * dx + 0.5f); + mDropViewPos[1] = fromY + (int) (percent * dy + 0.5f); + invalidate(mDropViewPos[0], mDropViewPos[1], + mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight()); + } + }); + + + view.setVisibility(View.INVISIBLE); + + if (!mScroller.isFinished()) { + mAnimOnPageEndMoving = mDropAnim; + } else { + mDropAnim.start(); + } } - void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo, - boolean insertAtFirst) { - final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen); - final int[] result = new int[2]; + /** + * {@inheritDoc} + */ + public boolean acceptDrop(DragSource source, int x, int y, + int xOffset, int yOffset, DragView dragView, Object dragInfo) { + + // If it's an external drop (e.g. from All Apps), check if it should be accepted + if (source != this) { + // Don't accept the drop if we're not over a screen at time of drop + if (mDragTargetLayout == null) { + return false; + } + + final CellLayout.CellInfo dragCellInfo = mDragInfo; + final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX; + final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY; - layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result); - onDropExternal(result[0], result[1], info, layout, insertAtFirst); + final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell; + + // Don't accept the drop if there's no room for the item + if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) { + mLauncher.showOutOfSpaceMessage(); + return false; + } + } + return true; } public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { - final CellLayout cellLayout = getCurrentDropLayout(); + + int originX = x - xOffset; + int originY = y - yOffset; + + if (mIsSmall || mIsInUnshrinkAnimation) { + // get originX and originY in the local coordinate system of the screen + mTempOriginXY[0] = originX; + mTempOriginXY[1] = originY; + mapPointFromSelfToChild(mDragTargetLayout, mTempOriginXY); + originX = (int)mTempOriginXY[0]; + originY = (int)mTempOriginXY[1]; + } + if (source != this) { - onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout); + onDropExternal(originX, originY, dragInfo, mDragTargetLayout); + } else if (mDragInfo != null) { + final View cell = mDragInfo.cell; + if (mDragTargetLayout != null) { + // Move internally + mTargetCell = findNearestVacantArea(originX, originY, + mDragInfo.spanX, mDragInfo.spanY, cell, mDragTargetLayout, + mTargetCell); + + if (mTargetCell == null) { + snapToPage(mDragInfo.screen); + } else { + int screen = indexOfChild(mDragTargetLayout); + if (screen != mDragInfo.screen) { + // Reparent the view + ((CellLayout) getChildAt(mDragInfo.screen)).removeView(cell); + addInScreen(cell, screen, mTargetCell[0], mTargetCell[1], + mDragInfo.spanX, mDragInfo.spanY); + } + + // update the item's position after drop + final ItemInfo info = (ItemInfo) cell.getTag(); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); + mDragTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]); + lp.cellX = mTargetCell[0]; + lp.cellY = mTargetCell[1]; + cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen, + mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); + + LauncherModel.moveItemInDatabase(mLauncher, info, + LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, + lp.cellX, lp.cellY); + } + } + + final CellLayout parent = (CellLayout) cell.getParent(); + + // Prepare it to be animated into its new position + // This must be called after the view has been re-parented + setPositionForDropAnimation(dragView, originX, originY, parent, cell); + parent.onDropChild(cell); + } + } + + public void onDragEnter(DragSource source, int x, int y, int xOffset, + int yOffset, DragView dragView, Object dragInfo) { + mDragTargetLayout = null; // Reset the drag state + + if (!mIsSmall) { + mDragTargetLayout = getCurrentDropLayout(); + mDragTargetLayout.onDragEnter(); + showOutlines(); + mInDragMode = true; + CellLayout cl = (CellLayout) getChildAt(mCurrentPage); + cl.setBackgroundAlphaMultiplier(1.0f); + } + } + + public DropTarget getDropTargetDelegate(DragSource source, int x, int y, + int xOffset, int yOffset, DragView dragView, Object dragInfo) { + + if (mIsSmall || mIsInUnshrinkAnimation) { + // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens + return null; + } + // We may need to delegate the drag to a child view. If a 1x1 item + // would land in a cell occupied by a DragTarget (e.g. a Folder), + // then drag events should be handled by that child. + + ItemInfo item = (ItemInfo)dragInfo; + CellLayout currentLayout = getCurrentDropLayout(); + + int dragPointX, dragPointY; + if (item.spanX == 1 && item.spanY == 1) { + // For a 1x1, calculate the drop cell exactly as in onDragOver + dragPointX = x - xOffset; + dragPointY = y - yOffset; } else { - // Move internally - if (mDragInfo != null) { - final View cell = mDragInfo.cell; - int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen; - if (index != mDragInfo.screen) { - final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen); - originalCellLayout.removeView(cell); - cellLayout.addView(cell); + // Otherwise, use the exact drag coordinates + dragPointX = x; + dragPointY = y; + } + dragPointX += mScrollX - currentLayout.getLeft(); + dragPointY += mScrollY - currentLayout.getTop(); + + // If we are dragging over a cell that contains a DropTarget that will + // accept the drop, delegate to that DropTarget. + final int[] cellXY = mTempCell; + currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY); + View child = currentLayout.getChildAt(cellXY[0], cellXY[1]); + if (child instanceof DropTarget) { + DropTarget target = (DropTarget)child; + if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) { + return target; + } + } + return null; + } + + /** + * Global drag and drop handler + */ + @Override + public boolean onDragEvent(DragEvent event) { + final ClipDescription desc = event.getClipDescription(); + final CellLayout layout = (CellLayout) getChildAt(mCurrentPage); + final int[] pos = new int[2]; + layout.getLocationOnScreen(pos); + // We need to offset the drag coordinates to layout coordinate space + final int x = (int) event.getX() - pos[0]; + final int y = (int) event.getY() - pos[1]; + + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + // Check if we have enough space on this screen to add a new shortcut + if (!layout.findCellForSpan(pos, 1, 1)) { + Toast.makeText(mContext, mContext.getString(R.string.out_of_space), + Toast.LENGTH_SHORT).show(); + return false; + } + + // Create the drag outline + // We need to add extra padding to the bitmap to make room for the glow effect + final Canvas canvas = new Canvas(); + final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS; + mDragOutline = createExternalDragOutline(canvas, bitmapPadding); + + // Show the current page outlines to indicate that we can accept this drop + showOutlines(); + layout.setHover(true); + layout.onDragEnter(); + layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1); + + return true; + case DragEvent.ACTION_DRAG_LOCATION: + // Visualize the drop location + layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1); + return true; + case DragEvent.ACTION_DROP: + // Check if we have enough space on this screen to add a new shortcut + if (!layout.findCellForSpan(pos, 1, 1)) { + Toast.makeText(mContext, mContext.getString(R.string.out_of_space), + Toast.LENGTH_SHORT).show(); + return false; + } + + // Try and add any shortcuts + int newDropCount = 0; + final LauncherModel model = mLauncher.getModel(); + final ClipData data = event.getClipData(); + + // We assume that the mime types are ordered in descending importance of + // representation. So we enumerate the list of mime types and alert the + // user if any widgets can handle the drop. Only the most preferred + // representation will be handled. + pos[0] = x; + pos[1] = y; + final int mimeTypeCount = desc.getMimeTypeCount(); + for (int j = 0; j < mimeTypeCount; ++j) { + final String mimeType = desc.getMimeType(j); + + if (mimeType.equals(InstallShortcutReceiver.SHORTCUT_MIMETYPE)) { + final Intent intent = data.getItem(j).getIntent(); + Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon()); + onDropExternal(x, y, info, layout); + } else { + final List<WidgetMimeTypeHandlerData> widgets = + model.resolveWidgetsForMimeType(mContext, mimeType); + final int numWidgets = widgets.size(); + + if (numWidgets == 0) { + continue; + } else if (numWidgets == 1) { + // If there is only one item, then go ahead and add and configure + // that widget + final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo; + final PendingAddWidgetInfo createInfo = + new PendingAddWidgetInfo(widgetInfo, mimeType, data); + mLauncher.addAppWidgetFromDrop(createInfo, mCurrentPage, pos); + } else if (numWidgets > 1) { + // Show the widget picker dialog if there is more than one widget + // that can handle this data type + final InstallWidgetReceiver.WidgetListAdapter adapter = + new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType, + data, widgets, layout, mCurrentPage, pos); + final AlertDialog.Builder builder = + new AlertDialog.Builder(mContext); + builder.setAdapter(adapter, adapter); + builder.setCancelable(true); + builder.setTitle(mContext.getString( + R.string.external_drop_widget_pick_title)); + builder.setIcon(R.drawable.ic_no_applications); + builder.show(); + } } - 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(); - LauncherModel.moveItemInDatabase(mLauncher, info, - LauncherSettings.Favorites.CONTAINER_DESKTOP, index, lp.cellX, lp.cellY); + newDropCount++; + break; + } + + // Show error message if we couldn't accept any of the items + if (newDropCount <= 0) { + Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error), + Toast.LENGTH_SHORT).show(); } + + return true; + case DragEvent.ACTION_DRAG_ENDED: + // Hide the page outlines after the drop + layout.setHover(false); + layout.onDragExit(); + hideOutlines(); + return true; + } + return super.onDragEvent(event); + } + + /* + * + * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's + * coordinate space. The argument xy is modified with the return result. + * + */ + void mapPointFromSelfToChild(View v, float[] xy) { + mapPointFromSelfToChild(v, xy, null); + } + + /* + * + * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's + * coordinate space. The argument xy is modified with the return result. + * + * if cachedInverseMatrix is not null, this method will just use that matrix instead of + * computing it itself; we use this to avoid redundant matrix inversions in + * findMatchingPageForDragOver + * + */ + void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { + if (cachedInverseMatrix == null) { + v.getMatrix().invert(mTempInverseMatrix); + cachedInverseMatrix = mTempInverseMatrix; + } + xy[0] = xy[0] + mScrollX - v.getLeft(); + xy[1] = xy[1] + mScrollY - v.getTop(); + cachedInverseMatrix.mapPoints(xy); + } + + /* + * + * Convert the 2D coordinate xy from this CellLayout's coordinate space to + * the parent View's coordinate space. The argument xy is modified with the return result. + * + */ + void mapPointFromChildToSelf(View v, float[] xy) { + v.getMatrix().mapPoints(xy); + xy[0] -= (mScrollX - v.getLeft()); + xy[1] -= (mScrollY - v.getTop()); + } + + static private float squaredDistance(float[] point1, float[] point2) { + float distanceX = point1[0] - point2[0]; + float distanceY = point2[1] - point2[1]; + return distanceX * distanceX + distanceY * distanceY; + } + + /* + * + * Returns true if the passed CellLayout cl overlaps with dragView + * + */ + boolean overlaps(CellLayout cl, DragView dragView, + int dragViewX, int dragViewY, Matrix cachedInverseMatrix) { + // Transform the coordinates of the item being dragged to the CellLayout's coordinates + final float[] draggedItemTopLeft = mTempDragCoordinates; + draggedItemTopLeft[0] = dragViewX + dragView.getScaledDragRegionXOffset(); + draggedItemTopLeft[1] = dragViewY + dragView.getScaledDragRegionYOffset(); + final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; + draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getScaledDragRegionWidth(); + draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getScaledDragRegionHeight(); + + // Transform the dragged item's top left coordinates + // to the CellLayout's local coordinates + mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix); + float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]); + float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]); + + if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) { + // Transform the dragged item's bottom right coordinates + // to the CellLayout's local coordinates + mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix); + float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]); + float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]); + + if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) { + float overlap = (overlapRegionRight - overlapRegionLeft) * + (overlapRegionBottom - overlapRegionTop); + if (overlap > 0) { + return true; + } + } } + return false; } - public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, - DragView dragView, Object dragInfo) { - clearVacantCache(); + /* + * + * This method returns the CellLayout that is currently being dragged to. In order to drag + * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second + * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one + * + * Return null if no CellLayout is currently being dragged over + * + */ + private CellLayout findMatchingPageForDragOver( + DragView dragView, int originX, int originY, int offsetX, int offsetY) { + // We loop through all the screens (ie CellLayouts) and see which ones overlap + // with the item being dragged and then choose the one that's closest to the touch point + final int screenCount = getChildCount(); + CellLayout bestMatchingScreen = null; + float smallestDistSoFar = Float.MAX_VALUE; + + for (int i = 0; i < screenCount; i++) { + CellLayout cl = (CellLayout)getChildAt(i); + + final float[] touchXy = mTempTouchCoordinates; + touchXy[0] = originX + offsetX; + touchXy[1] = originY + offsetY; + + // Transform the touch coordinates to the CellLayout's local coordinates + // If the touch point is within the bounds of the cell layout, we can return immediately + cl.getMatrix().invert(mTempInverseMatrix); + mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); + + if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && + touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { + return cl; + } + + if (overlaps(cl, dragView, originX, originY, mTempInverseMatrix)) { + // Get the center of the cell layout in screen coordinates + final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; + cellLayoutCenter[0] = cl.getWidth()/2; + cellLayoutCenter[1] = cl.getHeight()/2; + mapPointFromChildToSelf(cl, cellLayoutCenter); + + touchXy[0] = originX + offsetX; + touchXy[1] = originY + offsetY; + + // Calculate the distance between the center of the CellLayout + // and the touch point + float dist = squaredDistance(touchXy, cellLayoutCenter); + + if (dist < smallestDistSoFar) { + smallestDistSoFar = dist; + bestMatchingScreen = cl; + } + } + } + return bestMatchingScreen; } public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { + // When touch is inside the scroll area, skip dragOver actions for the current screen + if (!mInScrollArea) { + CellLayout layout; + int originX = x - xOffset; + int originY = y - yOffset; + if (mIsSmall || mIsInUnshrinkAnimation) { + layout = findMatchingPageForDragOver( + dragView, originX, originY, xOffset, yOffset); + + if (layout != mDragTargetLayout) { + if (mDragTargetLayout != null) { + mDragTargetLayout.setHover(false); + } + mDragTargetLayout = layout; + if (mDragTargetLayout != null) { + mDragTargetLayout.setHover(true); + } + } + } else { + layout = getCurrentDropLayout(); + + final ItemInfo item = (ItemInfo)dragInfo; + if (dragInfo instanceof LauncherAppWidgetInfo) { + LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo; + + if (widgetInfo.spanX == -1) { + // Calculate the grid spans needed to fit this widget + int[] spans = layout.rectToCell( + widgetInfo.minWidth, widgetInfo.minHeight, null); + item.spanX = spans[0]; + item.spanY = spans[1]; + } + } + + if (source instanceof AllAppsPagedView) { + // This is a hack to fix the point used to determine which cell an icon from + // the all apps screen is over + if (item != null && item.spanX == 1 && layout != null) { + int dragRegionLeft = (dragView.getWidth() - layout.getCellWidth()) / 2; + + originX += dragRegionLeft - dragView.getDragRegionLeft(); + if (dragView.getDragRegionWidth() != layout.getCellWidth()) { + dragView.setDragRegion(dragView.getDragRegionLeft(), + dragView.getDragRegionTop(), + layout.getCellWidth(), + dragView.getDragRegionHeight()); + } + } + } + + if (layout != mDragTargetLayout) { + if (mDragTargetLayout != null) { + mDragTargetLayout.onDragExit(); + } + layout.onDragEnter(); + mDragTargetLayout = layout; + } + + // only visualize the drop locations for moving icons within the home screen on + // tablet on phone, we also visualize icons dragged in from All Apps + if ((!LauncherApplication.isScreenXLarge() || source == this) + && mDragTargetLayout != null) { + final View child = (mDragInfo == null) ? null : mDragInfo.cell; + int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX); + int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY); + mDragTargetLayout.visualizeDropLocation(child, mDragOutline, + localOriginX, localOriginY, item.spanX, item.spanY); + } + } + } } - public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, - DragView dragView, Object dragInfo) { - clearVacantCache(); + public void onDragExit(DragSource source, int x, int y, int xOffset, + int yOffset, DragView dragView, Object dragInfo) { + if (mDragTargetLayout != null) { + mDragTargetLayout.onDragExit(); + } + if (!mIsPageMoving) { + hideOutlines(); + mInDragMode = false; + } + clearAllHovers(); } - private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) { + private void onDropExternal(int x, int y, Object dragInfo, + CellLayout cellLayout) { onDropExternal(x, y, dragInfo, cellLayout, false); } - - private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout, - boolean insertAtFirst) { - // Drag from somewhere else + + /** + * Add the item specified by dragInfo to the given layout. + * This is basically the equivalent of onDropExternal, except it's not initiated + * by drag and drop. + * @return true if successful + */ + public boolean addExternalItemToScreen(Object dragInfo, View layout) { + CellLayout cl = (CellLayout) layout; + ItemInfo info = (ItemInfo) dragInfo; + + if (cl.findCellForSpan(mTempEstimate, info.spanX, info.spanY)) { + onDropExternal(-1, -1, dragInfo, cl, false); + return true; + } + mLauncher.showOutOfSpaceMessage(); + return false; + } + + // Drag from somewhere else + // NOTE: This can also be called when we are outside of a drag event, when we want + // to add an item to one of the workspace screens. + private void onDropExternal(int x, int y, Object dragInfo, + CellLayout cellLayout, boolean insertAtFirst) { + int screen = indexOfChild(cellLayout); + if (dragInfo instanceof PendingAddItemInfo) { + PendingAddItemInfo info = (PendingAddItemInfo) dragInfo; + // When dragging and dropping from customization tray, we deal with creating + // widgets/shortcuts/folders in a slightly different way + int[] touchXY = new int[2]; + touchXY[0] = x; + touchXY[1] = y; + switch (info.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) info, screen, touchXY); + break; + case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: + mLauncher.addLiveFolderFromDrop(info.componentName, screen, touchXY); + break; + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + mLauncher.processShortcutFromDrop(info.componentName, screen, touchXY); + break; + default: + throw new IllegalStateException("Unknown item type: " + info.itemType); + } + cellLayout.onDragExit(); + cellLayout.animateDrop(); + return; + } + + // This is for other drag/drop cases, like dragging from All Apps ItemInfo info = (ItemInfo) dragInfo; - View view; + View view = null; switch (info.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: if (info.container == NO_ID && info instanceof ApplicationInfo) { // Came from all apps -- make a copy - info = new ShortcutInfo((ApplicationInfo)info); + info = new ShortcutInfo((ApplicationInfo) info); } - view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo)info); + view = mLauncher.createShortcut(R.layout.application, cellLayout, + (ShortcutInfo) info); break; case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, - (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info)); + cellLayout, (UserFolderInfo) info, mIconCache); break; default: throw new IllegalStateException("Unknown item type: " + info.itemType); } - cellLayout.addView(view, insertAtFirst ? 0 : -1); - view.setHapticFeedbackEnabled(false); - view.setOnLongClickListener(mLongClickListener); - if (view instanceof DropTarget) { - mDragController.addDropTarget((DropTarget) view); - } - - mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell); - cellLayout.onDropChild(view, mTargetCell); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); + // If the view is null, it has already been added. + if (view == null) { + cellLayout.onDragExit(); + } else { + mTargetCell = findNearestVacantArea(x, y, 1, 1, null, cellLayout, mTargetCell); + addInScreen(view, indexOfChild(cellLayout), mTargetCell[0], + mTargetCell[1], info.spanX, info.spanY, insertAtFirst); + cellLayout.onDropChild(view); + cellLayout.animateDrop(); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); - LauncherModel.addOrMoveItemInDatabase(mLauncher, info, - LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY); + LauncherModel.addOrMoveItemInDatabase(mLauncher, info, + LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, + lp.cellX, lp.cellY); + } } - + /** * 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; + // if we're currently small, use findMatchingPageForDragOver instead + if (mIsSmall) return null; + int index = mScroller.isFinished() ? mCurrentPage : mNextPage; return (CellLayout) getChildAt(index); } /** - * {@inheritDoc} + * Return the current CellInfo describing our current drag; this method exists + * so that Launcher can sync this object with the correct info when the activity is created/ + * destroyed + * */ - public boolean acceptDrop(DragSource source, int x, int y, - int xOffset, int yOffset, DragView dragView, 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, DragView dragView, 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; + public CellLayout.CellInfo getDragInfo() { + return mDragInfo; } /** * Calculate the nearest cell where the given object would be dropped. */ - private int[] estimateDropCell(int pixelX, int pixelY, + private int[] findNearestVacantArea(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); - } + + int localPixelX = pixelX - (layout.getLeft() - mScrollX); + int localPixelY = pixelY - (layout.getTop() - mScrollY); // Find the best target drop location - return layout.findNearestVacantArea(pixelX, pixelY, - spanX, spanY, mVacantCache, recycle); + return layout.findNearestVacantArea( + localPixelX, localPixelY, spanX, spanY, ignoreView, recycle); } - + + /** + * Estimate the size that a child with the given dimensions will take in the current screen. + */ + void estimateChildSize(int minWidth, int minHeight, int[] result) { + ((CellLayout)getChildAt(mCurrentPage)).estimateChildSize(minWidth, minHeight, result); + } + void setLauncher(Launcher launcher) { mLauncher = launcher; } @@ -1215,61 +1885,80 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } public void onDropCompleted(View target, boolean success) { - clearVacantCache(); - - if (success){ + if (success) { if (target != this && mDragInfo != null) { final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); cellLayout.removeView(mDragInfo.cell); if (mDragInfo.cell instanceof DropTarget) { mDragController.removeDropTarget((DropTarget)mDragInfo.cell); } - //final Object tag = mDragInfo.cell.getTag(); - } - } else { - if (mDragInfo != null) { - final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); - cellLayout.onDropAborted(mDragInfo.cell); + // final Object tag = mDragInfo.cell.getTag(); } + } else if (mDragInfo != null) { + ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell); } + mDragOutline = null; mDragInfo = null; } + public boolean isDropEnabled() { + return true; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + super.onRestoreInstanceState(state); + Launcher.setScreen(mCurrentPage); + } + + @Override public void scrollLeft() { - clearVacantCache(); - if (mScroller.isFinished()) { - if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1); - } else { - if (mNextScreen > 0) snapToScreen(mNextScreen - 1); + if (!mIsSmall && !mIsInUnshrinkAnimation) { + super.scrollLeft(); } } + @Override public void scrollRight() { - clearVacantCache(); - if (mScroller.isFinished()) { - if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1); - } else { - if (mNextScreen < getChildCount() -1) snapToScreen(mNextScreen + 1); + if (!mIsSmall && !mIsInUnshrinkAnimation) { + super.scrollRight(); } } - public int getScreenForView(View v) { - int result = -1; - if (v != null) { - ViewParent vp = v.getParent(); - int count = getChildCount(); - for (int i = 0; i < count; i++) { - if (vp == getChildAt(i)) { - return i; + @Override + public void onEnterScrollArea(int direction) { + if (!mIsSmall && !mIsInUnshrinkAnimation) { + mInScrollArea = true; + final int screen = getCurrentPage() + ((direction == DragController.SCROLL_LEFT) ? -1 : 1); + if (0 <= screen && screen < getChildCount()) { + ((CellLayout) getChildAt(screen)).setHover(true); + + if (mDragTargetLayout != null) { + mDragTargetLayout.onDragExit(); + mDragTargetLayout = null; } } } - return result; + } + + private void clearAllHovers() { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + ((CellLayout) getChildAt(i)).setHover(false); + } + } + + @Override + public void onExitScrollArea() { + if (mInScrollArea) { + mInScrollArea = false; + clearAllHovers(); + } } public Folder getFolderForTag(Object tag) { - int screenCount = getChildCount(); + final int screenCount = getChildCount(); for (int screen = 0; screen < screenCount; screen++) { CellLayout currentScreen = ((CellLayout) getChildAt(screen)); int count = currentScreen.getChildCount(); @@ -1278,7 +1967,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { Folder f = (Folder) child; - if (f.getInfo() == tag) { + if (f.getInfo() == tag && f.getInfo().opened) { return f; } } @@ -1302,23 +1991,9 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag return null; } - /** - * @return True is long presses are still allowed for the current touch - */ - public boolean allowLongPress() { - return mAllowLongPress; - } - - /** - * Set true to allow long-press events to be triggered, usually checked by - * {@link Launcher} to accept or block dpad-initiated long-presses. - */ - public void setAllowLongPress(boolean allowLongPress) { - mAllowLongPress = allowLongPress; - } void removeItems(final ArrayList<ApplicationInfo> apps) { - final int count = getChildCount(); + final int screenCount = getChildCount(); final PackageManager manager = getContext().getPackageManager(); final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); @@ -1328,7 +2003,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag packageNames.add(apps.get(i).componentName.getPackageName()); } - for (int i = 0; i < count; i++) { + for (int i = 0; i < screenCount; i++) { final CellLayout layout = (CellLayout) getChildAt(i); // Avoid ANRs by treating each screen separately @@ -1336,17 +2011,17 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag public void run() { final ArrayList<View> childrenToRemove = new ArrayList<View>(); childrenToRemove.clear(); - + int childCount = layout.getChildCount(); for (int j = 0; j < childCount; j++) { final View view = layout.getChildAt(j); Object tag = view.getTag(); - + if (tag instanceof ShortcutInfo) { final ShortcutInfo info = (ShortcutInfo) tag; final Intent intent = info.intent; final ComponentName name = intent.getComponent(); - + if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { for (String packageName: packageNames) { if (packageName.equals(name.getPackageName())) { @@ -1361,12 +2036,12 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1); final int contentsCount = contents.size(); boolean removedFromFolder = false; - + for (int k = 0; k < contentsCount; k++) { final ShortcutInfo appInfo = contents.get(k); final Intent intent = appInfo.intent; final ComponentName name = intent.getComponent(); - + if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { for (String packageName: packageNames) { if (packageName.equals(name.getPackageName())) { @@ -1377,11 +2052,12 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } } } - + contents.removeAll(toRemove); if (removedFromFolder) { final Folder folder = getOpenFolder(); - if (folder != null) folder.notifyDataSetChanged(); + if (folder != null) + folder.notifyDataSetChanged(); } } else if (tag instanceof LiveFolderInfo) { final LiveFolderInfo info = (LiveFolderInfo) tag; @@ -1393,7 +2069,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag for (String packageName: packageNames) { if (packageName.equals(providerInfo.packageName)) { LauncherModel.deleteItemFromDatabase(mLauncher, info); - childrenToRemove.add(view); + childrenToRemove.add(view); } } } @@ -1405,13 +2081,13 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag for (String packageName: packageNames) { if (packageName.equals(provider.provider.getPackageName())) { LauncherModel.deleteItemFromDatabase(mLauncher, info); - childrenToRemove.add(view); + childrenToRemove.add(view); } } } } } - + childCount = childrenToRemove.size(); for (int j = 0; j < childCount; j++) { View child = childrenToRemove.get(j); @@ -1420,7 +2096,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag mDragController.removeDropTarget((DropTarget)child); } } - + if (childCount > 0) { layout.requestLayout(); layout.invalidate(); @@ -1431,10 +2107,8 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } void updateShortcuts(ArrayList<ApplicationInfo> apps) { - final PackageManager pm = mLauncher.getPackageManager(); - - final int count = getChildCount(); - for (int i = 0; i < count; i++) { + final int screenCount = getChildCount(); + for (int i = 0; i < screenCount; i++) { final CellLayout layout = (CellLayout) getChildAt(i); int childCount = layout.getChildCount(); for (int j = 0; j < childCount; j++) { @@ -1450,7 +2124,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { final int appCount = apps.size(); - for (int k=0; k<appCount; k++) { + for (int k = 0; k < appCount; k++) { ApplicationInfo app = apps.get(k); if (app.componentName.equals(name)) { info.setIcon(mIconCache.getIcon(info.intent)); @@ -1466,48 +2140,29 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } void moveToDefaultScreen(boolean animate) { - if (animate) { - snapToScreen(mDefaultScreen); + if (mIsSmall || mIsInUnshrinkAnimation) { + mLauncher.showWorkspace(animate, (CellLayout)getChildAt(mDefaultPage)); + } else if (animate) { + snapToPage(mDefaultPage); } else { - setCurrentScreen(mDefaultScreen); + setCurrentPage(mDefaultPage); } - getChildAt(mDefaultScreen).requestFocus(); + getChildAt(mDefaultPage).requestFocus(); } void setIndicators(Drawable previous, Drawable next) { mPreviousIndicator = previous; mNextIndicator = next; - previous.setLevel(mCurrentScreen); - next.setLevel(mCurrentScreen); + previous.setLevel(mCurrentPage); + next.setLevel(mCurrentPage); } - public static class SavedState extends BaseSavedState { - int currentScreen = -1; - - SavedState(Parcelable superState) { - super(superState); - } - - private SavedState(Parcel in) { - super(in); - currentScreen = in.readInt(); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeInt(currentScreen); - } - - public static final Parcelable.Creator<SavedState> CREATOR = - new Parcelable.Creator<SavedState>() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } + @Override + public void syncPages() { + } - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; + @Override + public void syncPageItems(int page) { } + } diff --git a/src/com/android/launcher2/allapps.rs b/src/com/android/launcher2/allapps.rs new file mode 100644 index 0000000..a9727fb --- /dev/null +++ b/src/com/android/launcher2/allapps.rs @@ -0,0 +1,389 @@ +#pragma version(1) + +#pragma rs java_package_name(com.android.launcher2) + +#include "rs_graphics.rsh" + +#define PI 3.14159f + +// Constants from Java +int COLUMNS_PER_PAGE_PORTRAIT; +int ROWS_PER_PAGE_PORTRAIT; +int COLUMNS_PER_PAGE_LANDSCAPE; +int ROWS_PER_PAGE_LANDSCAPE; + +int gIconCount; +int gSelectedIconIndex = -1; +rs_allocation gSelectedIconTexture; +rs_allocation gHomeButton; + +rs_program_fragment gPFTexNearest; +rs_program_fragment gPFTexMip; +rs_program_fragment gPFTexMipAlpha; +rs_program_vertex gPVCurve; +rs_program_store gPS; +rs_mesh gSMCell; + +rs_allocation *gIconIDs; +rs_allocation *gLabelIDs; + +typedef struct VpConsts { + rs_matrix4x4 Proj; + float4 Position; + float4 ScaleOffset; + float2 BendPos; + float2 ImgSize; +} VpConsts_t; +VpConsts_t *vpConstants; + + +#pragma rs export_func(move, moveTo, setZoom, fling) + + +// Attraction to center values from page edge to page center. +static float g_AttractionTable[9] = {20.f, 20.f, 20.f, 10.f, -10.f, -20.f, -20.f, -20.f, -20.f}; +static float g_FrictionTable[9] = {10.f, 10.f, 11.f, 15.f, 15.f, 11.f, 10.f, 10.f, 10.f}; +static float g_PhysicsTableSize = 7; + +static float gZoomTarget; +float gTargetPos; +static float g_PosPage = 0.f; +static float g_PosVelocity = 0.f; +static float g_LastPositionX = 0.f; +static bool g_LastTouchDown = false; +static float g_DT; +static int g_PosMax; +static float g_Zoom = 0.f; +static float g_Animation = 1.f; +static float g_OldPosPage; +static float g_OldPosVelocity; +static float g_OldZoom; +static float g_MoveToTotalTime = 0.2f; +static float g_MoveToTime = 0.f; +static float g_MoveToOldPos = 0.f; + +static int g_Cols; +static int g_Rows; + +rs_allocation g_VPConstAlloc; + +// Drawing constants, should be parameters ====== +#define VIEW_ANGLE 1.28700222f + + +static void updateReadback() { + if ((g_OldPosPage != g_PosPage) || + (g_OldPosVelocity != g_PosVelocity) || + (g_OldZoom != g_Zoom)) { + + g_OldPosPage = g_PosPage; + g_OldPosVelocity = g_PosVelocity; + g_OldZoom = g_Zoom; + + int i[3]; + i[0] = g_PosPage * (1 << 16); + i[1] = g_PosVelocity * (1 << 16); + i[2] = g_OldZoom * (1 << 16); + rsSendToClientBlocking(1, &i[0], sizeof(i)); + } +} + +void init() { +} + +void move(float newPos) { + if (g_LastTouchDown) { + float dx = -(newPos - g_LastPositionX); + g_PosVelocity = 0; + g_PosPage += dx * 5.2f; + + float pmin = -0.49f; + float pmax = g_PosMax + 0.49f; + g_PosPage = clamp(g_PosPage, pmin, pmax); + } + g_LastTouchDown = true; + g_LastPositionX = newPos; + g_MoveToTime = 0; +} + +void moveTo(float targetPos) { + gTargetPos = targetPos; + g_MoveToTime = g_MoveToTotalTime; + g_PosVelocity = 0; + g_MoveToOldPos = g_PosPage; +} + +void setZoom(float z, /*bool*/ int animate) { + gZoomTarget = z; + if (gZoomTarget < 0.001f) { + gZoomTarget = 0; + } + if (!animate) { + g_Zoom = gZoomTarget; + } + updateReadback(); +} + +void fling(float newPos, float vel) { + move(newPos); + + g_LastTouchDown = false; + g_PosVelocity = -vel * 4; + float av = fabs(g_PosVelocity); + float minVel = 3.5f; + + minVel *= 1.f - (fabs(rsFrac(g_PosPage + 0.5f) - 0.5f) * 0.45f); + + if (av < minVel && av > 0.2f) { + if (g_PosVelocity > 0) { + g_PosVelocity = minVel; + } else { + g_PosVelocity = -minVel; + } + } + + if (g_PosPage <= 0) { + g_PosVelocity = max(0.f, g_PosVelocity); + } + if (g_PosPage > g_PosMax) { + g_PosVelocity = min(0.f, g_PosVelocity); + } +} + +// Interpolates values in the range 0..1 to a curve that eases in +// and out. +static float getInterpolation(float input) { + return (cos((input + 1) * PI) * 0.5f) + 0.5f; +} + + +static void updatePos() { + if (g_LastTouchDown) { + return; + } + + float tablePosNorm = rsFrac(g_PosPage + 0.5f); + float tablePosF = tablePosNorm * g_PhysicsTableSize; + int tablePosI = tablePosF; + float tablePosFrac = tablePosF - tablePosI; + float accel = mix(g_AttractionTable[tablePosI], + g_AttractionTable[tablePosI + 1], + tablePosFrac) * g_DT; + float friction = mix(g_FrictionTable[tablePosI], + g_FrictionTable[tablePosI + 1], + tablePosFrac) * g_DT; + + if (g_MoveToTime) { + // New position is old posiition + (total distance) * (interpolated time) + g_PosPage = g_MoveToOldPos + (gTargetPos - g_MoveToOldPos) * getInterpolation((g_MoveToTotalTime - g_MoveToTime) / g_MoveToTotalTime); + g_MoveToTime -= g_DT; + if (g_MoveToTime <= 0) { + g_MoveToTime = 0; + g_PosPage = gTargetPos; + } + return; + } + + // If our velocity is low OR acceleration is opposing it, apply it. + if (fabs(g_PosVelocity) < 4.0f || (g_PosVelocity * accel) < 0) { + g_PosVelocity += accel; + } + //RS_DEBUG(g_PosPage); + //RS_DEBUG(g_PosVelocity); + //RS_DEBUG(friction); + //RS_DEBUG(accel); + + // Normal physics + if (g_PosVelocity > 0) { + g_PosVelocity -= friction; + g_PosVelocity = max(g_PosVelocity, 0.f); + } else { + g_PosVelocity += friction; + g_PosVelocity = min(g_PosVelocity, 0.f); + } + + if ((friction > fabs(g_PosVelocity)) && (friction > fabs(accel))) { + // Special get back to center and overcome friction physics. + float t = tablePosNorm - 0.5f; + if (fabs(t) < (friction * g_DT)) { + // really close, just snap + g_PosPage = round(g_PosPage); + g_PosVelocity = 0; + } else { + if (t > 0) { + g_PosVelocity = -friction; + } else { + g_PosVelocity = friction; + } + } + } + + // Check for out of boundry conditions. + if (g_PosPage < 0 && g_PosVelocity < 0) { + float damp = 1.0f + (g_PosPage * 4); + damp = clamp(damp, 0.f, 0.9f); + g_PosVelocity *= damp; + } + if (g_PosPage > g_PosMax && g_PosVelocity > 0) { + float damp = 1.0f - ((g_PosPage - g_PosMax) * 4); + damp = clamp(damp, 0.f, 0.9f); + g_PosVelocity *= damp; + } + + g_PosPage += g_PosVelocity * g_DT; + g_PosPage = clamp(g_PosPage, -0.49f, g_PosMax + 0.49f); +} + +static void +draw_home_button() +{ + rsgBindTexture(gPFTexNearest, 0, gHomeButton); + + float w = rsgGetWidth(); + float h = rsgGetHeight(); + float tw = rsAllocationGetDimX(gHomeButton); + float th = rsAllocationGetDimY(gHomeButton); + + float x; + float y; + if (w > h) { + x = w - (tw * (1 - g_Animation)) + 20; + y = (h - th) * 0.5f; + } else { + x = (w - tw) / 2; + y = -g_Animation * th; + y -= 30; // move the house to the edge of the screen as it doesn't fill the texture. + } + + rsgDrawSpriteScreenspace(x, y, 0, tw, th); +} + +static void drawFrontGrid(float rowOffset, float p) +{ + float h = rsgGetHeight(); + float w = rsgGetWidth(); + + int intRowOffset = rowOffset; + float rowFrac = rowOffset - intRowOffset; + float colWidth = 120.f;//w / 4; + float rowHeight = colWidth + 25.f; + float yoff = 0.5f * h + 1.5f * rowHeight; + + int row, col; + int colCount = 4; + if (w > h) { + colCount = 6; + rowHeight -= 12.f; + yoff = 0.47f * h + 1.0f * rowHeight; + } + + int iconNum = (intRowOffset - 5) * colCount; + + rsgBindProgramVertex(gPVCurve); + + vpConstants->Position.z = p; + + for (row = -5; row < 15; row++) { + float y = yoff - ((-rowFrac + row) * rowHeight); + + for (col=0; col < colCount; col++) { + if (iconNum >= gIconCount) { + return; + } + + if (iconNum >= 0) { + float x = colWidth * col + (colWidth / 2); + vpConstants->Position.x = x + 0.2f; + + if (gSelectedIconIndex == iconNum && !p && rsIsObject(gSelectedIconTexture)) { + rsgBindProgramFragment(gPFTexNearest); + rsgBindTexture(gPFTexNearest, 0, gSelectedIconTexture); + vpConstants->ImgSize.x = rsAllocationGetDimX(gSelectedIconTexture); + vpConstants->ImgSize.y = rsAllocationGetDimY(gSelectedIconTexture); + vpConstants->Position.y = y - (rsAllocationGetDimY(gSelectedIconTexture) + - rsAllocationGetDimY(gIconIDs[iconNum])) * 0.5f; + rsAllocationMarkDirty(g_VPConstAlloc); + rsgDrawMesh(gSMCell); + } + + rsgBindProgramFragment(gPFTexMip); + vpConstants->ImgSize.x = rsAllocationGetDimX(gIconIDs[iconNum]); + vpConstants->ImgSize.y = rsAllocationGetDimY(gIconIDs[iconNum]); + vpConstants->Position.y = y - 0.2f; + rsAllocationMarkDirty(g_VPConstAlloc); + rsgBindTexture(gPFTexMip, 0, gIconIDs[iconNum]); + rsgDrawMesh(gSMCell); + + rsgBindProgramFragment(gPFTexMipAlpha); + vpConstants->ImgSize.x = rsAllocationGetDimX(gLabelIDs[iconNum]); + vpConstants->ImgSize.y = rsAllocationGetDimY(gLabelIDs[iconNum]); + vpConstants->Position.y = y - 64.f - 0.2f; + rsAllocationMarkDirty(g_VPConstAlloc); + rsgBindTexture(gPFTexMipAlpha, 0, gLabelIDs[iconNum]); + rsgDrawMesh(gSMCell); + } + iconNum++; + } + } +} + + +int root() +{ + // Compute dt in seconds. + // physics may break if DT is large. + g_DT = min(rsGetDt(), 0.1f); + g_VPConstAlloc = rsGetAllocation(vpConstants); + + if (g_Zoom != gZoomTarget) { + float dz = g_DT * 1.7f; + if (gZoomTarget < 0.5f) { + dz = -dz; + } + if (fabs(g_Zoom - gZoomTarget) < fabs(dz)) { + g_Zoom = gZoomTarget; + } else { + g_Zoom += dz; + } + updateReadback(); + } + g_Animation = pow(1.f - g_Zoom, 3.f); + + // Set clear value to dim the background based on the zoom position. + if ((g_Zoom < 0.001f) && (gZoomTarget < 0.001f)) { + rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f); + // When we're zoomed out and not tracking motion events, reset the pos to 0. + if (!g_LastTouchDown) { + g_PosPage = 0; + } + return 0; + } else { + rsgClearColor(0.0f, 0.0f, 0.0f, g_Zoom); + } + + rsgBindProgramStore(gPS); + + // icons & labels + if (rsgGetWidth() > rsgGetHeight()) { + g_Cols = COLUMNS_PER_PAGE_LANDSCAPE; + g_Rows = ROWS_PER_PAGE_LANDSCAPE; + } else { + g_Cols = COLUMNS_PER_PAGE_PORTRAIT; + g_Rows = ROWS_PER_PAGE_PORTRAIT; + } + + g_PosMax = ((gIconCount + (g_Cols-1)) / g_Cols) - g_Rows; + if (g_PosMax < 0) g_PosMax = 0; + + updatePos(); + updateReadback(); + + // Draw the icons ======================================== + drawFrontGrid(g_PosPage, g_Animation); + + rsgBindProgramFragment(gPFTexNearest); + draw_home_button(); + return (g_PosVelocity != 0) || rsFrac(g_PosPage) || g_Zoom != gZoomTarget || (g_MoveToTime != 0); +} + + |